From 8bb96b90488b6e25425a2e29680b8a2388e3fd3c Mon Sep 17 00:00:00 2001 From: Malin Date: Fri, 6 Mar 2026 12:48:06 +0100 Subject: [PATCH] feat: initial WooAApanel plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full WooCommerce plugin for aaPanel hosting management: - aaPanel API client (all website + database endpoints) - Admin: server management, site/DB assignments, full site/DB management panels - Customer My Account: web hosting tab with sites and databases - WooDomains PowerDNS integration for DNS management - WooCommerce order auto-provisioning (product → server linking) - Permission model: admin has all actions, customers have scoped access Co-Authored-By: Claude Sonnet 4.6 --- assets/css/wooaapanel-account.css | 84 ++ assets/css/wooaapanel-admin.css | 94 +++ assets/js/wooaapanel-account.js | 389 ++++++++++ assets/js/wooaapanel-admin.js | 972 ++++++++++++++++++++++++ includes/class-wooaapanel-account.php | 550 ++++++++++++++ includes/class-wooaapanel-admin.php | 745 ++++++++++++++++++ includes/class-wooaapanel-api.php | 399 ++++++++++ includes/class-wooaapanel-installer.php | 56 ++ includes/class-wooaapanel-orders.php | 124 +++ wooaapanel.php | 45 ++ 10 files changed, 3458 insertions(+) create mode 100644 assets/css/wooaapanel-account.css create mode 100644 assets/css/wooaapanel-admin.css create mode 100644 assets/js/wooaapanel-account.js create mode 100644 assets/js/wooaapanel-admin.js create mode 100644 includes/class-wooaapanel-account.php create mode 100644 includes/class-wooaapanel-admin.php create mode 100644 includes/class-wooaapanel-api.php create mode 100644 includes/class-wooaapanel-installer.php create mode 100644 includes/class-wooaapanel-orders.php create mode 100644 wooaapanel.php diff --git a/assets/css/wooaapanel-account.css b/assets/css/wooaapanel-account.css new file mode 100644 index 0000000..e277141 --- /dev/null +++ b/assets/css/wooaapanel-account.css @@ -0,0 +1,84 @@ +/* WooAApanel Account (My Account) Styles */ + +#wooaapanel-account { margin-top: 16px; } + +/* ── Panels ───────────────────────────────────────────────────── */ +.wap-site-panel, .wap-db-panel { + border: 1px solid #e0e0e0; + border-radius: 6px; + margin-bottom: 20px; + overflow: hidden; +} + +.wap-panel-header { + background: #f8f9fa; + padding: 14px 16px; + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; + border-bottom: 1px solid #e0e0e0; +} + +.wap-site-name, .wap-db-name { font-weight: 700; font-size: 15px; } +.wap-domain { color: #0073aa; } +.wap-server-label { color: #888; font-size: 12px; margin-left: auto; } + +/* ── Action buttons ──────────────────────────────────────────── */ +.wap-panel-actions { display: flex; flex-wrap: wrap; gap: 6px; } +.wap-btn-sm { font-size: 12px !important; padding: 4px 10px !important; height: auto !important; line-height: 1.5 !important; } + +/* ── Sections ────────────────────────────────────────────────── */ +.wap-section { padding: 16px; border-top: 1px solid #f0f0f0; } +.wap-section h4, .wap-section h5 { margin-top: 0; margin-bottom: 10px; } + +/* ── Tables inside sections ──────────────────────────────────── */ +.wap-section table { width: 100%; border-collapse: collapse; font-size: 13px; } +.wap-section table th, +.wap-section table td { padding: 6px 10px; border-bottom: 1px solid #f0f0f0; text-align: left; } +.wap-section table th { background: #f8f8f8; font-weight: 600; } + +/* ── Input rows ───────────────────────────────────────────────── */ +.wap-section input[type="text"], +.wap-section input[type="password"], +.wap-section select { + padding: 6px 10px; + border: 1px solid #ddd; + border-radius: 3px; + font-size: 13px; + margin-right: 6px; +} + +.wap-section textarea { + width: 100%; + padding: 8px; + border: 1px solid #ddd; + border-radius: 3px; + font-family: monospace; + font-size: 12px; +} + +/* ── SSL info ─────────────────────────────────────────────────── */ +.wap-ssl-info p { margin: 4px 0; } +.wap-ssl-info .wap-ssl-valid { color: #155724; font-weight: 600; } +.wap-ssl-info .wap-ssl-invalid { color: #721c24; font-weight: 600; } + +/* ── DNS ──────────────────────────────────────────────────────── */ +.wap-dns-add-form label { display: block; margin-bottom: 10px; } +.wap-dns-add-form textarea { height: 70px; } + +/* ── Notices ──────────────────────────────────────────────────── */ +.wap-notice-inline { + display: inline-block; + padding: 4px 10px; + border-radius: 3px; + font-size: 12px; + margin-left: 6px; +} +.wap-notice-inline.ok { background: #d4edda; color: #155724; } +.wap-notice-inline.error { background: #f8d7da; color: #721c24; } + +/* ── DB action notice ─────────────────────────────────────────── */ +.wap-db-action-notice { padding: 0 16px; } +.wap-db-action-notice.ok { background: #d4edda; color: #155724; padding: 8px 16px; } +.wap-db-action-notice.error { background: #f8d7da; color: #721c24; padding: 8px 16px; } diff --git a/assets/css/wooaapanel-admin.css b/assets/css/wooaapanel-admin.css new file mode 100644 index 0000000..e2b82f4 --- /dev/null +++ b/assets/css/wooaapanel-admin.css @@ -0,0 +1,94 @@ +/* WooAApanel Admin Styles */ + +.wooaapanel-page { max-width: 1200px; } + +/* ── Cards / tables ─────────────────────────────────────────── */ +.wap-card { + background: #fff; + border: 1px solid #e0e0e0; + border-radius: 4px; + padding: 16px; + margin-bottom: 16px; +} + +.wap-table { + width: 100%; + border-collapse: collapse; + font-size: 13px; +} +.wap-table th, .wap-table td { + padding: 8px 12px; + border-bottom: 1px solid #f0f0f0; + text-align: left; + vertical-align: top; +} +.wap-table th { background: #f8f8f8; font-weight: 600; } +.wap-table tr:hover td { background: #fafafa; } + +/* ── Forms ───────────────────────────────────────────────────── */ +.wap-form { max-width: 600px; } +.wap-form label { display: block; margin-bottom: 12px; } +.wap-form label span { display: block; font-weight: 600; margin-bottom: 4px; font-size: 12px; } +.wap-form input[type="text"], +.wap-form input[type="url"], +.wap-form input[type="password"], +.wap-form select, +.wap-form textarea { + width: 100%; + padding: 6px 10px; + border: 1px solid #ddd; + border-radius: 3px; + font-size: 13px; +} +.wap-form textarea { min-height: 100px; font-family: monospace; } + +/* ── Toolbar ─────────────────────────────────────────────────── */ +.wap-toolbar { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 16px; + flex-wrap: wrap; +} +.wap-toolbar select, .wap-toolbar input { max-width: 200px; } + +/* ── Status / badges ─────────────────────────────────────────── */ +.wap-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 10px; + font-size: 11px; + font-weight: 600; +} +.wap-badge-active { background: #d4edda; color: #155724; } +.wap-badge-inactive{ background: #f8d7da; color: #721c24; } + +/* ── Action buttons ──────────────────────────────────────────── */ +.wap-action-btn { cursor: pointer; } +.wap-action-btn + .wap-action-btn { margin-left: 4px; } + +/* ── Inline edit form ────────────────────────────────────────── */ +.wap-inline-form { background: #f8f9fa; border: 1px solid #e0e0e0; border-radius: 4px; padding: 16px; margin-top: 16px; } +.wap-inline-form h4 { margin-top: 0; } + +/* ── Notices ─────────────────────────────────────────────────── */ +.wap-notice { padding: 8px 12px; border-radius: 3px; margin-top: 8px; display: none; } +.wap-notice.success { background: #d4edda; color: #155724; display: block; } +.wap-notice.error { background: #f8d7da; color: #721c24; display: block; } + +/* ── Server selector ──────────────────────────────────────────── */ +.wap-server-picker { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; } +.wap-server-picker label { font-weight: 600; margin: 0; } + +/* ── Detail panels ───────────────────────────────────────────── */ +.wap-detail-panel { margin-top: 8px; padding: 12px; background: #fafafa; border: 1px solid #e8e8e8; border-radius: 3px; } +.wap-detail-panel pre { margin: 0; white-space: pre-wrap; word-break: break-all; font-size: 12px; } + +/* ── Tabs ─────────────────────────────────────────────────────── */ +.wap-tabs { display: flex; gap: 0; border-bottom: 2px solid #0073aa; margin-bottom: 16px; } +.wap-tab { padding: 8px 16px; cursor: pointer; border: 1px solid transparent; border-bottom: none; font-size: 13px; border-radius: 3px 3px 0 0; } +.wap-tab.active { background: #0073aa; color: #fff; } +.wap-tab:hover:not(.active) { background: #e8f4fc; } + +.wap-tab-content { display: none; } +.wap-tab-content.active { display: block; } diff --git a/assets/js/wooaapanel-account.js b/assets/js/wooaapanel-account.js new file mode 100644 index 0000000..c328606 --- /dev/null +++ b/assets/js/wooaapanel-account.js @@ -0,0 +1,389 @@ +/** + * WooAApanel Account JS + * + * Customer My Account "Web Hosting" tab. + * Handles site and database panels. + * Integrates WooDomains PowerDNS for DNS management if active. + */ +(function ($) { + 'use strict'; + + var cfg = wooaapanelAcct; + var ajaxUrl = cfg.ajax_url; + var nonce = cfg.nonce; + var pdnsActive = parseInt(cfg.pdns_active, 10) === 1; + var pdnsNonce = cfg.pdns_nonce; + + /* ── Utility ──────────────────────────────────────────────── */ + + function post(action, data, cb) { + data = $.extend({ action: action, nonce: nonce }, data); + $.post(ajaxUrl, data, cb).fail(function () { + setNotice('#wap-acct-notices', 'Request failed. Please reload and try again.', 'error'); + }); + } + + function pdnsPost(action, data, cb) { + data = $.extend({ action: action, nonce: pdnsNonce }, data); + $.post(ajaxUrl, data, cb); + } + + function setNotice(selector, msg, type) { + var $el = $(selector); + $el.attr('class', 'woocommerce-' + (type === 'error' ? 'error' : 'message') + ' woocommerce-info') + .text(msg).show(); + setTimeout(function () { $el.fadeOut(); }, 5000); + } + + function inlineNotice($el, msg, type) { + $el.text(msg).attr('class', 'wap-notice-inline ' + (type === 'error' ? 'error' : 'ok')); + setTimeout(function () { $el.text('').removeClass('ok error'); }, 4000); + } + + function escHtml(s) { + return String(s) + .replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); + } + + function siteOpts($panel) { + return { + server_id: $panel.data('server-id'), + site_name: $panel.data('site-name') + }; + } + + function dbOpts($panel) { + return { + server_id: $panel.data('server-id'), + db_name: $panel.data('db-name') + }; + } + + /* ══════════════════════════════════════════════════════════ + SITE PANELS + ══════════════════════════════════════════════════════════ */ + + /* ── Domains ──────────────────────────────────────────────── */ + + $(document).on('click', '.wap-load-domains', function () { + var $panel = $(this).closest('.wap-site-panel'); + var $sec = $panel.find('.wap-domains-section'); + $sec.toggle(); + if ($sec.is(':visible')) { + loadDomains($panel); + } + }); + + function loadDomains($panel) { + var opts = siteOpts($panel); + var $list = $panel.find('.wap-domains-list').html('Loading…'); + post('wooaapanel_acct_site_domains', opts, function (res) { + var domains = res.data && res.data.data ? res.data.data : []; + if (!domains.length) { $list.html('

No extra domains.

'); return; } + var html = ''; + $.each(domains, function (i, d) { + html += '' + + ''; + }); + html += '
DomainPort
' + escHtml(d.name || d.domain || '') + '' + (d.port || '') + '
'; + $list.html(html); + + $list.find('.wap-del-domain').on('click', function () { + if (!confirm('Remove this domain?')) return; + post('wooaapanel_acct_site_del_domain', $.extend({}, opts, { + domain: $(this).data('domain'), + site_id: $(this).data('id') + }), function () { loadDomains($panel); }); + }); + }); + } + + $(document).on('click', '.wap-add-domain-btn', function () { + var $panel = $(this).closest('.wap-site-panel'); + var domain = $panel.find('.wap-new-domain').val(); + var $notice = $panel.find('.wap-domain-notice'); + if (!domain) { inlineNotice($notice, 'Enter a domain name.', 'error'); return; } + post('wooaapanel_acct_site_add_domain', $.extend({}, siteOpts($panel), { domain: domain }), function (res) { + inlineNotice($notice, res.success ? 'Domain added!' : (res.data || 'Error.'), res.success ? 'ok' : 'error'); + if (res.success) { $panel.find('.wap-new-domain').val(''); loadDomains($panel); } + }); + }); + + /* ── PHP Version ──────────────────────────────────────────── */ + + $(document).on('click', '.wap-load-php', function () { + var $panel = $(this).closest('.wap-site-panel'); + var $sec = $panel.find('.wap-php-section'); + $sec.toggle(); + if ($sec.is(':visible') && !$sec.data('loaded')) { + $sec.data('loaded', 1); + loadPhpSection($panel, $sec); + } + }); + + function loadPhpSection($panel, $sec) { + var opts = siteOpts($panel); + $sec.find('.wap-php-current').text('Loading…'); + post('wooaapanel_acct_site_php_get', opts, function (res) { + var current = res.data && res.data.version ? res.data.version : '?'; + $sec.find('.wap-php-current').html('Current PHP version: ' + escHtml(current) + ''); + post('wooaapanel_acct_site_php_versions', opts, function (res2) { + var versions = res2.data && res2.data.version ? res2.data.version : []; + var opts2 = ''; + $.each(versions, function (i, v) { + var ver = v.version || v; + opts2 += ''; + }); + $sec.find('.wap-php-select').html(opts2); + }); + }); + } + + $(document).on('click', '.wap-php-set-btn', function () { + var $panel = $(this).closest('.wap-site-panel'); + var $sec = $panel.find('.wap-php-section'); + var $notice = $sec.find('.wap-php-notice'); + var version = $sec.find('.wap-php-select').val(); + post('wooaapanel_acct_site_php_set', $.extend({}, siteOpts($panel), { version: version }), function (res) { + inlineNotice($notice, res.success ? 'PHP version updated!' : (res.data || 'Error.'), res.success ? 'ok' : 'error'); + if (res.success) { + $sec.data('loaded', 0); + loadPhpSection($panel, $sec); + } + }); + }); + + /* ── URL Rewrite ──────────────────────────────────────────── */ + + $(document).on('click', '.wap-load-rewrite', function () { + var $panel = $(this).closest('.wap-site-panel'); + var $sec = $panel.find('.wap-rewrite-section'); + $sec.toggle(); + if ($sec.is(':visible') && !$sec.data('loaded')) { + $sec.data('loaded', 1); + post('wooaapanel_acct_site_rewrite_get', siteOpts($panel), function (res) { + var content = res.data && res.data.data ? res.data.data : (res.data || ''); + $sec.find('.wap-rewrite-content').val(content); + }); + } + }); + + $(document).on('click', '.wap-rewrite-save-btn', function () { + var $panel = $(this).closest('.wap-site-panel'); + var $sec = $panel.find('.wap-rewrite-section'); + var $notice = $sec.find('.wap-rewrite-notice'); + var content = $sec.find('.wap-rewrite-content').val(); + post('wooaapanel_acct_site_rewrite_set', $.extend({}, siteOpts($panel), { content: content }), function (res) { + inlineNotice($notice, res.success ? 'Saved!' : (res.data || 'Error.'), res.success ? 'ok' : 'error'); + }); + }); + + /* ── SSL ──────────────────────────────────────────────────── */ + + $(document).on('click', '.wap-load-ssl', function () { + var $panel = $(this).closest('.wap-site-panel'); + var $sec = $panel.find('.wap-ssl-section'); + $sec.toggle(); + if ($sec.is(':visible') && !$sec.data('loaded')) { + $sec.data('loaded', 1); + post('wooaapanel_acct_site_ssl_get', siteOpts($panel), function (res) { + var d = res.data || {}; + var html = ''; + if (d.status !== undefined) { + var valid = d.status == 1 || d.status === true; + html += '

Status: ' + (valid ? 'Active' : 'Inactive') + '

'; + } + if (d.notAfter) html += '

Expires: ' + escHtml(d.notAfter) + '

'; + if (d.issuer) html += '

Issuer: ' + escHtml(d.issuer) + '

'; + if (d.subject) html += '

Domain: ' + escHtml(d.subject) + '

'; + if (!html) html = '
' + escHtml(JSON.stringify(d, null, 2)) + '
'; + $sec.find('.wap-ssl-info').html(html); + }); + } + }); + + /* ── Server Info ──────────────────────────────────────────── */ + + $(document).on('click', '.wap-load-server-info', function () { + var $panel = $(this).closest('.wap-site-panel'); + var $sec = $panel.find('.wap-server-section'); + $sec.toggle(); + if ($sec.is(':visible') && !$sec.data('loaded')) { + $sec.data('loaded', 1); + post('wooaapanel_acct_server_info', siteOpts($panel), function (res) { + if (!res.success) { $sec.find('.wap-server-info').text(res.data || 'Error.'); return; } + var d = res.data; + var html = '' + + '' + + '' + + ''; + if (d.panel_status) { + html += ''; + } + html += '
Server Name' + escHtml(d.server_name || '') + '
Server Host / IP' + escHtml(d.server_host || '') + '
Panel URL' + escHtml(d.panel_url || '') + '
Panel Status
' + escHtml(JSON.stringify(d.panel_status, null, 2)) + '
'; + $sec.find('.wap-server-info').html(html); + }); + } + }); + + /* ── DNS (WooDomains PowerDNS integration) ────────────────── */ + + if (pdnsActive) { + $(document).on('click', '.wap-load-dns', function () { + var $panel = $(this).closest('.wap-site-panel'); + var zone = $(this).data('zone'); + var $sec = $panel.find('.wap-dns-section'); + $sec.toggle(); + if ($sec.is(':visible')) { + loadDnsRecords($sec, zone); + } + }); + + function loadDnsRecords($sec, zone) { + $sec.find('.wap-dns-records-list').html('Loading DNS records…'); + pdnsPost('woodomains_dns_get', { zone: zone }, function (res) { + if (!res.success) { + $sec.find('.wap-dns-records-list').html('

' + escHtml(res.data && res.data.message ? res.data.message : 'Error.') + '

'); + return; + } + var records = res.data.records || []; + var html = '' + + '' + + ''; + $.each(records, function (i, rr) { + var contents = $.map(rr.records, function (r) { return escHtml(r.content); }).join('
'); + html += '' + + '' + + ''; + }); + html += '
NameTypeTTLContent
' + escHtml(rr.name) + '' + escHtml(rr.type) + '' + rr.ttl + '' + contents + '
'; + $sec.find('.wap-dns-records-list').html(html); + + $sec.find('.wap-dns-del').on('click', function () { + if (!confirm('Delete this DNS record?')) return; + pdnsPost('woodomains_dns_delete', { + zone: $(this).data('zone'), + name: $(this).data('name'), + type: $(this).data('type') + }, function () { loadDnsRecords($sec, zone); }); + }); + }); + } + + $(document).on('click', '.wap-dns-save-btn', function () { + var $panel = $(this).closest('.wap-site-panel'); + var $sec = $panel.find('.wap-dns-section'); + var zone = $sec.data('zone'); + var $notice = $sec.find('.wap-dns-notice'); + var contents = $sec.find('.wap-dns-content').val() + .split('\n').filter(function (s) { return s.trim(); }); + + pdnsPost('woodomains_dns_upsert', { + zone: zone, + name: $sec.find('.wap-dns-name').val(), + type: $sec.find('.wap-dns-type').val(), + ttl: parseInt($sec.find('.wap-dns-ttl').val(), 10) || 3600, + contents: contents + }, function (res) { + inlineNotice($notice, res.success ? 'Saved!' : (res.data && res.data.message ? res.data.message : 'Error.'), res.success ? 'ok' : 'error'); + if (res.success) loadDnsRecords($sec, zone); + }); + }); + } + + /* ══════════════════════════════════════════════════════════ + DATABASE PANELS + ══════════════════════════════════════════════════════════ */ + + /* ── Backup ───────────────────────────────────────────────── */ + + $(document).on('click', '.wap-db-backup-btn', function () { + var $panel = $(this).closest('.wap-db-panel'); + var $notice = $panel.find('.wap-db-action-notice'); + post('wooaapanel_acct_db_backup', dbOpts($panel), function (res) { + $notice.attr('class', 'wap-db-action-notice ' + (res.success ? 'ok' : 'error')) + .text(res.success ? 'Backup started!' : (res.data || 'Error.')); + }); + }); + + /* ── Backup list ──────────────────────────────────────────── */ + + $(document).on('click', '.wap-load-db-backups', function () { + var $panel = $(this).closest('.wap-db-panel'); + var $sec = $panel.find('.wap-db-backups-section'); + $sec.toggle(); + if ($sec.is(':visible')) { + loadDbBackups($panel, $sec); + } + }); + + function loadDbBackups($panel, $sec) { + $sec.find('.wap-db-backups-list').html('Loading…'); + post('wooaapanel_acct_db_backups', dbOpts($panel), function (res) { + var backups = res.data && res.data.data ? res.data.data : []; + if (!backups.length) { $sec.find('.wap-db-backups-list').html('

No backups found.

'); return; } + var html = ''; + $.each(backups, function (i, b) { + html += '' + + ''; + }); + html += '
FileSizeDate
' + escHtml(b.name || '') + '' + escHtml(b.size || '') + '' + escHtml(b.addtime || '') + '
'; + $sec.find('.wap-db-backups-list').html(html); + + $sec.find('.wap-del-db-backup').on('click', function () { + if (!confirm('Delete this backup?')) return; + post('wooaapanel_acct_db_backup_delete', $.extend({}, dbOpts($panel), { backup_id: $(this).data('id') }), function () { + loadDbBackups($panel, $sec); + }); + }); + }); + } + + /* ── Optimize ─────────────────────────────────────────────── */ + + $(document).on('click', '.wap-db-optimize-btn', function () { + var $panel = $(this).closest('.wap-db-panel'); + var $notice = $panel.find('.wap-db-action-notice'); + post('wooaapanel_acct_db_optimize', dbOpts($panel), function (res) { + $notice.attr('class', 'wap-db-action-notice ' + (res.success ? 'ok' : 'error')) + .text(res.success ? 'Database optimized!' : (res.data || 'Error.')); + }); + }); + + /* ── Repair ───────────────────────────────────────────────── */ + + $(document).on('click', '.wap-db-repair-btn', function () { + var $panel = $(this).closest('.wap-db-panel'); + var $notice = $panel.find('.wap-db-action-notice'); + post('wooaapanel_acct_db_repair', dbOpts($panel), function (res) { + $notice.attr('class', 'wap-db-action-notice ' + (res.success ? 'ok' : 'error')) + .text(res.success ? 'Database repaired!' : (res.data || 'Error.')); + }); + }); + + /* ── Change Password ──────────────────────────────────────── */ + + $(document).on('click', '.wap-db-password-btn', function () { + var $panel = $(this).closest('.wap-db-panel'); + $panel.find('.wap-db-password-section').toggle(); + }); + + $(document).on('click', '.wap-db-pass-save-btn', function () { + var $panel = $(this).closest('.wap-db-panel'); + var $sec = $panel.find('.wap-db-password-section'); + var $notice = $sec.find('.wap-db-pass-notice'); + var db_user = $sec.find('.wap-db-user').val(); + var password = $sec.find('.wap-db-new-pass').val(); + + if (!db_user || !password) { + inlineNotice($notice, 'Database user and password are required.', 'error'); return; + } + + post('wooaapanel_acct_db_password', $.extend({}, dbOpts($panel), { db_user: db_user, password: password }), function (res) { + inlineNotice($notice, res.success ? 'Password updated!' : (res.data || 'Error.'), res.success ? 'ok' : 'error'); + if (res.success) { $sec.find('.wap-db-user, .wap-db-new-pass').val(''); } + }); + }); + +})(jQuery); diff --git a/assets/js/wooaapanel-admin.js b/assets/js/wooaapanel-admin.js new file mode 100644 index 0000000..6cd9707 --- /dev/null +++ b/assets/js/wooaapanel-admin.js @@ -0,0 +1,972 @@ +/** + * WooAApanel Admin JS + * + * Handles all admin pages: Servers, Site Assignments, DB Assignments, + * Sites (full management), Databases (full management), WC Products. + */ +(function ($) { + 'use strict'; + + var ajaxUrl = wooaapanel.ajax_url; + var nonce = wooaapanel.nonce; + + /* ── Utility ──────────────────────────────────────────────────── */ + + function post(action, data, cb) { + data = $.extend({ action: action, nonce: nonce }, data); + $.post(ajaxUrl, data, cb).fail(function () { + alert('Request failed. Check your network and try again.'); + }); + } + + function notice($el, msg, type) { + $el.removeClass('success error').addClass(type).text(msg).show(); + } + + function confirm_action(msg) { + return window.confirm(msg || 'Are you sure?'); + } + + function pretty(obj) { + return JSON.stringify(obj, null, 2); + } + + /* ── Page detection ───────────────────────────────────────────── */ + + var $body = $('body'); + var page_id = ''; + if ($('#wooaapanel-servers-page').length) page_id = 'servers'; + if ($('#wooaapanel-site-assignments-page').length) page_id = 'site-assignments'; + if ($('#wooaapanel-db-assignments-page').length) page_id = 'db-assignments'; + if ($('#wooaapanel-sites-page').length) page_id = 'sites'; + if ($('#wooaapanel-databases-page').length) page_id = 'databases'; + if ($('#wooaapanel-products-page').length) page_id = 'products'; + + /* ══════════════════════════════════════════════════════════════ + SERVERS PAGE + ══════════════════════════════════════════════════════════════ */ + + if (page_id === 'servers') { + var $wrap = $('#wap-servers-wrap'); + + function loadServers() { + post('wooaapanel_servers_list', {}, function (res) { + if (!res.success) return; + var html = '' + + '' + + '' + + '' + + ''; + $.each(res.data, function (i, s) { + html += '' + + '' + + '' + + '' + + '' + + ''; + }); + html += '
IDNameURLActiveActions
' + s.id + '' + escHtml(s.name) + '' + escHtml(s.url) + '' + (s.active ? 'Active' : 'Inactive') + '' + + ' ' + + ' ' + + '' + + '
'; + $wrap.html(html); + bindServerEvents(); + }); + } + + function serverForm(id, name, url, api_key, active) { + return '

' + (id ? 'Edit Server' : 'Add Server') + '

' + + '' + + '' + + '' + + '' + + '' + + ' ' + + '
'; + } + + function bindServerEvents() { + $('#wap-server-add-btn').on('click', function () { + $('#wap-server-form').html(serverForm()).show(); + }); + + $wrap.on('click', '.wap-server-edit', function () { + var $btn = $(this); + $('#wap-server-form').html(serverForm( + $btn.data('id'), $btn.data('name'), $btn.data('url'), '', $btn.data('active') + )).show(); + // API key intentionally left blank on edit for security + }); + + $wrap.on('click', '.wap-s-save', function () { + var $form = $(this).closest('.wap-inline-form, #wap-server-form'); + var data = { + id: $form.find('.wap-s-id').val(), + name: $form.find('.wap-s-name').val(), + url: $form.find('.wap-s-url').val(), + api_key: $form.find('.wap-s-key').val(), + active: $form.find('.wap-s-active').val() + }; + if (!data.name || !data.url) { alert('Name and URL are required.'); return; } + if (!data.id || data.id == '0') { + if (!data.api_key) { alert('API key is required for new servers.'); return; } + } + post('wooaapanel_server_save', data, function (res) { + if (res.success) { loadServers(); } else { alert(res.data || 'Error saving.'); } + }); + }); + + $wrap.on('click', '.wap-s-cancel', function () { $('#wap-server-form').hide(); }); + + $wrap.on('click', '.wap-server-del', function () { + if (!confirm_action('Delete this server?')) return; + post('wooaapanel_server_delete', { id: $(this).data('id') }, function (res) { + if (res.success) loadServers(); else alert(res.data || 'Error.'); + }); + }); + + $wrap.on('click', '.wap-server-test', function () { + var id = $(this).data('id'); + var $btn = $(this); + $btn.text('Testing…').prop('disabled', true); + post('wooaapanel_server_test', { id: id }, function (res) { + $btn.text('Test').prop('disabled', false); + alert(res.success ? 'Connection OK! Response: ' + JSON.stringify(res.data) : 'Failed: ' + (res.data || res.error)); + }); + }); + } + + loadServers(); + } + + /* ══════════════════════════════════════════════════════════════ + SITE ASSIGNMENTS PAGE + ══════════════════════════════════════════════════════════════ */ + + if (page_id === 'site-assignments') { + var $wrap = $('#wap-site-assignments-wrap'); + + function loadSiteAssignments() { + post('wooaapanel_site_assignments_list', {}, function (res) { + if (!res.success) return; + var html = '' + + '' + + '' + + '' + + ''; + $.each(res.data, function (i, a) { + html += '' + + '' + + '' + + '' + + '' + + '' + + ''; + }); + html += '
IDCustomerServerSite NameDomainActions
' + a.id + '' + escHtml(a.customer_name || '') + '
' + escHtml(a.customer_email || '') + '
' + escHtml(a.server_name || '') + '' + escHtml(a.site_name) + '' + escHtml(a.domain || '—') + '' + + '' + + '
'; + $wrap.html(html); + bindSAEvents(); + }); + } + + function loadServersSelect(cb) { + post('wooaapanel_servers_list', {}, function (res) { + if (!res.success) return; + cb(res.data); + }); + } + + function bindSAEvents() { + $('#wap-sa-add-btn').on('click', function () { + loadServersSelect(function (servers) { + var opts = ''; + $.each(servers, function (i, s) { opts += ''; }); + var html = '

Assign Site to Customer

' + + '' + + '' + + '' + + '' + + ' ' + + '
'; + $('#wap-sa-form').html(html).show(); + bindSAFormEvents(); + }); + }); + + $wrap.on('click', '.wap-sa-del', function () { + if (!confirm_action('Remove this site assignment?')) return; + post('wooaapanel_site_assignment_delete', { id: $(this).data('id') }, function (res) { + if (res.success) loadSiteAssignments(); else alert(res.data || 'Error.'); + }); + }); + } + + function bindSAFormEvents() { + $('#wap-sa-load-sites').on('click', function () { + var sid = $('#wap-sa-server').val(); + if (!sid) { alert('Select a server first.'); return; } + post('wooaapanel_remote_sites', { server_id: sid }, function (res) { + var sites = res.data && res.data.data ? res.data.data : []; + var opts = ''; + $.each(sites, function (i, s) { + var name = s.name || s.siteName || s.webname || JSON.stringify(s); + opts += ''; + }); + $('#wap-sa-site').html(opts); + }); + }); + + var searchTimer; + $('#wap-sa-customer-q').on('keyup', function () { + clearTimeout(searchTimer); + var q = $(this).val(); + searchTimer = setTimeout(function () { + if (!q) return; + post('wooaapanel_customers_search', { q: q }, function (res) { + if (!res.success) return; + var $list = $('#wap-sa-customer-results').empty().show(); + $.each(res.data, function (i, u) { + $('
' + escHtml(u.label) + '
') + .on('click', function () { + $('#wap-sa-customer-id').val(u.id); + $('#wap-sa-customer-q').val(u.label); + $list.hide(); + }) + .appendTo($list); + }); + }); + }, 300); + }); + + $('#wap-sa-save').on('click', function () { + var data = { + server_id: $('#wap-sa-server').val(), + site_name: $('#wap-sa-site').val(), + domain: $('#wap-sa-domain').val(), + customer_id: $('#wap-sa-customer-id').val() + }; + if (!data.server_id || !data.site_name || !data.customer_id) { + alert('Server, site and customer are all required.'); return; + } + post('wooaapanel_site_assignment_save', data, function (res) { + if (res.success) { loadSiteAssignments(); } else { alert(res.data || 'Error.'); } + }); + }); + + $('#wap-sa-cancel').on('click', function () { $('#wap-sa-form').hide(); }); + } + + loadSiteAssignments(); + } + + /* ══════════════════════════════════════════════════════════════ + DB ASSIGNMENTS PAGE + ══════════════════════════════════════════════════════════════ */ + + if (page_id === 'db-assignments') { + var $wrap = $('#wap-db-assignments-wrap'); + + function loadDbAssignments() { + post('wooaapanel_db_assignments_list', {}, function (res) { + if (!res.success) return; + var html = '' + + '' + + '' + + '' + + ''; + $.each(res.data, function (i, a) { + html += '' + + '' + + '' + + '' + + '' + + '' + + ''; + }); + html += '
IDCustomerServerDatabaseActions
' + a.id + '' + escHtml(a.customer_name || '') + '
' + escHtml(a.customer_email || '') + '
' + escHtml(a.server_name || '') + '' + escHtml(a.db_name) + '
'; + $wrap.html(html); + bindDAEvents(); + }); + } + + function bindDAEvents() { + $('#wap-da-add-btn').on('click', function () { + post('wooaapanel_servers_list', {}, function (res) { + var opts = ''; + $.each(res.data || [], function (i, s) { opts += ''; }); + var html = '

Assign Database to Customer

' + + '' + + '' + + '' + + ' '; + $('#wap-da-form').html(html).show(); + + $('#wap-da-load-dbs').on('click', function () { + var sid = $('#wap-da-server').val(); + if (!sid) { alert('Select a server first.'); return; } + post('wooaapanel_remote_dbs', { server_id: sid }, function (res2) { + var dbs = res2.data && res2.data.data ? res2.data.data : []; + var opts2 = ''; + $.each(dbs, function (i, d) { + var name = d.name || d.database || JSON.stringify(d); + opts2 += ''; + }); + $('#wap-da-db').html(opts2); + }); + }); + + var searchTimer; + $('#wap-da-customer-q').on('keyup', function () { + clearTimeout(searchTimer); + var q = $(this).val(); + searchTimer = setTimeout(function () { + if (!q) return; + post('wooaapanel_customers_search', { q: q }, function (r) { + if (!r.success) return; + var $list = $('#wap-da-customer-results').empty().show(); + $.each(r.data, function (i, u) { + $('
' + escHtml(u.label) + '
') + .on('click', function () { + $('#wap-da-customer-id').val(u.id); + $('#wap-da-customer-q').val(u.label); + $list.hide(); + }).appendTo($list); + }); + }); + }, 300); + }); + + $('#wap-da-save').on('click', function () { + var data = { + server_id: $('#wap-da-server').val(), + db_name: $('#wap-da-db').val(), + customer_id: $('#wap-da-customer-id').val() + }; + if (!data.server_id || !data.db_name || !data.customer_id) { + alert('Server, database and customer are required.'); return; + } + post('wooaapanel_db_assignment_save', data, function (r) { + if (r.success) { loadDbAssignments(); } else { alert(r.data || 'Error.'); } + }); + }); + + $('#wap-da-cancel').on('click', function () { $('#wap-da-form').hide(); }); + }); + }); + + $wrap.on('click', '.wap-da-del', function () { + if (!confirm_action('Remove this database assignment?')) return; + post('wooaapanel_db_assignment_delete', { id: $(this).data('id') }, function (res) { + if (res.success) loadDbAssignments(); else alert(res.data || 'Error.'); + }); + }); + } + + loadDbAssignments(); + } + + /* ══════════════════════════════════════════════════════════════ + SITES PAGE (admin full management) + ══════════════════════════════════════════════════════════════ */ + + if (page_id === 'sites') { + var $wrap = $('#wap-sites-wrap'); + + function renderSitesToolbar(servers) { + var opts = ''; + $.each(servers, function (i, s) { opts += ''; }); + $wrap.html( + '
' + + '' + + '' + + '' + + '' + + '
' + + '' + + '' + + '
' + ); + + $('#wap-sites-load').on('click', loadSitesList); + + $('#wap-site-add-btn').on('click', function () { + var sid = $('#wap-sites-server').val(); + if (!sid) { alert('Select a server first.'); return; } + $('#wap-site-form').html(siteAddForm(sid)).show(); + $('#wap-site-detail').hide(); + }); + + $wrap.on('click', '.wap-site-manage', function () { + var $row = $(this).closest('tr'); + openSiteDetail({ + server_id: $row.data('server'), + site_name: $row.data('site') + }); + }); + + $wrap.on('click', '.wap-site-del', function () { + if (!confirm_action('Delete this site? This is irreversible.')) return; + var $row = $(this).closest('tr'); + post('wooaapanel_admin_site_delete', { + server_id: $row.data('server'), + site_name: $row.data('site') + }, function (res) { + alert(res.success ? 'Site deleted.' : 'Error: ' + JSON.stringify(res.data)); + loadSitesList(); + }); + }); + } + + function loadSitesList() { + var sid = $('#wap-sites-server').val(); + var search = $('#wap-sites-search').val(); + if (!sid) { alert('Select a server.'); return; } + post('wooaapanel_admin_site_list', { server_id: sid, search: search }, function (res) { + var sites = res.data && res.data.data ? res.data.data : []; + var html = '' + + '' + + ''; + $.each(sites, function (i, s) { + html += '' + + '' + + '' + + '' + + '' + + '' + + ''; + }); + html += '
IDNameDomainPHPStatusActions
' + (s.id || '—') + '' + escHtml(s.name || s.siteName || '') + '' + escHtml(s.domain || '') + '' + escHtml(s.php_version || s.phpVersion || '') + '' + escHtml(s.status || '') + '' + + ' ' + + '' + + '
'; + $('#wap-sites-list').html(html); + }); + } + + function siteAddForm(server_id) { + return '

Add PHP Project Site

' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
'; + } + + $wrap.on('click', '.wap-sf-save', function () { + var $f = $(this).closest('.wap-inline-form'); + post('wooaapanel_admin_site_add', { + server_id: $f.find('.wap-sf-server').val(), + webname: $f.find('.wap-sf-webname').val(), + version: $f.find('.wap-sf-phpver').val(), + port: $f.find('.wap-sf-port').val(), + ps: $f.find('.wap-sf-ps').val(), + sql: $f.find('.wap-sf-sql').val() ? 'MySQL' : '', + datauser: $f.find('.wap-sf-datauser').val(), + datapassword: $f.find('.wap-sf-datapass').val() + }, function (res) { + var $n = $f.find('#wap-site-form-notice'); + notice($n, res.success ? 'Site created!' : 'Error: ' + JSON.stringify(res.data), res.success ? 'success' : 'error'); + if (res.success) loadSitesList(); + }); + }); + + $wrap.on('click', '.wap-sf-cancel', function () { $('#wap-site-form').hide(); }); + + function openSiteDetail(opts) { + var $d = $('#wap-site-detail').show(); + $d.html( + '

Manage: ' + escHtml(opts.site_name) + '

' + + '
' + + '
Domains
' + + '
PHP
' + + '
URL Rewrite
' + + '
SSL
' + + '
Anti-XSS
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + ); + + // Tab switching + $d.find('.wap-tab').on('click', function () { + $d.find('.wap-tab').removeClass('active'); + $d.find('.wap-tab-content').removeClass('active'); + $(this).addClass('active'); + $d.find('#wap-site-tab-' + $(this).data('tab')).addClass('active'); + }); + + loadDomainsTab(opts, $d); + $d.find('.wap-tab[data-tab="php"]').on('click', function () { loadPhpTab(opts, $d); }); + $d.find('.wap-tab[data-tab="rewrite"]').on('click', function () { loadRewriteTab(opts, $d); }); + $d.find('.wap-tab[data-tab="ssl"]').on('click', function () { loadSslTab(opts, $d); }); + $d.find('.wap-tab[data-tab="xss"]').on('click', function () { loadXssTab(opts, $d); }); + } + + // ── Domains tab ────────────────────────────────────────── + + function loadDomainsTab(opts, $d) { + var $tab = $d.find('#wap-site-tab-domains'); + post('wooaapanel_admin_site_domains', opts, function (res) { + var domains = res.data && res.data.data ? res.data.data : []; + var html = ''; + $.each(domains, function (i, dm) { + html += '' + + ''; + }); + html += '
DomainPort
' + escHtml(dm.name || dm.domain || '') + '' + (dm.port || '') + '
' + + '
' + + '' + + '' + + '' + + '
'; + $tab.html(html); + + $tab.find('.wap-del-domain').on('click', function () { + if (!confirm_action('Delete this domain?')) return; + post('wooaapanel_admin_site_del_domain', $.extend({}, opts, { + domain: $(this).data('domain'), + site_id: $(this).data('id') + }), function () { loadDomainsTab(opts, $d); }); + }); + + $tab.find('.wap-add-domain-btn').on('click', function () { + var domain = $tab.find('.wap-add-domain-input').val(); + post('wooaapanel_admin_site_add_domain', $.extend({}, opts, { domain: domain }), function (res) { + $tab.find('.wap-domain-msg').text(res.success ? 'Added!' : JSON.stringify(res.data)); + if (res.success) loadDomainsTab(opts, $d); + }); + }); + }); + } + + // ── PHP tab ────────────────────────────────────────────── + + function loadPhpTab(opts, $d) { + var $tab = $d.find('#wap-site-tab-php'); + if ($tab.data('loaded')) return; + $tab.data('loaded', 1); + post('wooaapanel_admin_site_php_get', opts, function (res) { + var current = res.data && res.data.version ? res.data.version : '?'; + post('wooaapanel_admin_site_php_versions', opts, function (res2) { + var versions = res2.data && res2.data.version ? res2.data.version : []; + var opts2 = ''; + $.each(versions, function (i, v) { + var ver = v.version || v; + opts2 += ''; + }); + $tab.html( + '

Current: ' + escHtml(current) + '

' + + '' + + ' ' + + '' + ); + $tab.find('.wap-php-set').on('click', function () { + post('wooaapanel_admin_site_php_set', $.extend({}, opts, { version: $tab.find('#wap-php-select').val() }), function (res) { + $tab.find('#wap-php-notice').text(res.success ? 'Updated!' : JSON.stringify(res.data)); + }); + }); + }); + }); + } + + // ── Rewrite tab ────────────────────────────────────────── + + function loadRewriteTab(opts, $d) { + var $tab = $d.find('#wap-site-tab-rewrite'); + if ($tab.data('loaded')) return; + $tab.data('loaded', 1); + post('wooaapanel_admin_site_rewrite_get', opts, function (res) { + var content = res.data && res.data.data ? res.data.data : (res.data || ''); + $tab.html( + '' + + '' + + '' + ); + $tab.find('.wap-rewrite-save').on('click', function () { + post('wooaapanel_admin_site_rewrite_set', $.extend({}, opts, { content: $tab.find('#wap-rewrite-ta').val() }), function (res) { + $tab.find('#wap-rewrite-notice').text(res.success ? 'Saved!' : JSON.stringify(res.data)); + }); + }); + }); + } + + // ── SSL tab ────────────────────────────────────────────── + + function loadSslTab(opts, $d) { + var $tab = $d.find('#wap-site-tab-ssl'); + if ($tab.data('loaded')) return; + $tab.data('loaded', 1); + post('wooaapanel_admin_site_ssl_get', opts, function (res) { + var d = res.data || {}; + $tab.html('
' + escHtml(pretty(d)) + '
' + + '' + + ' '); + $tab.find('.wap-ssl-close').on('click', function () { + if (!confirm_action('Disable SSL for this site?')) return; + post('wooaapanel_admin_site_ssl_close', opts, function (res) { + $tab.find('#wap-ssl-notice').text(res.success ? 'SSL disabled.' : JSON.stringify(res.data)); + }); + }); + }); + } + + // ── XSS tab ────────────────────────────────────────────── + + function loadXssTab(opts, $d) { + var $tab = $d.find('#wap-site-tab-xss'); + if ($tab.data('loaded')) return; + $tab.data('loaded', 1); + post('wooaapanel_admin_site_xss_get', $.extend({ site_path: '/www/wwwroot/' + opts.site_name }, opts), function (res) { + var status = res.data; + $tab.html('
' + escHtml(pretty(status)) + '
' + + '' + + ' ' + + ' '); + $tab.find('.wap-xss-on').on('click', function () { + post('wooaapanel_admin_site_xss_set', $.extend({ site_path: '/www/wwwroot/' + opts.site_name, enable: 1 }, opts), function (res) { + $tab.find('#wap-xss-notice').text(res.success ? 'XSS enabled.' : JSON.stringify(res.data)); + }); + }); + $tab.find('.wap-xss-off').on('click', function () { + post('wooaapanel_admin_site_xss_set', $.extend({ site_path: '/www/wwwroot/' + opts.site_name, enable: 0 }, opts), function (res) { + $tab.find('#wap-xss-notice').text(res.success ? 'XSS disabled.' : JSON.stringify(res.data)); + }); + }); + }); + } + + // Bootstrap + post('wooaapanel_servers_list', {}, function (res) { + renderSitesToolbar(res.data || []); + }); + } + + /* ══════════════════════════════════════════════════════════════ + DATABASES PAGE (admin full management) + ══════════════════════════════════════════════════════════════ */ + + if (page_id === 'databases') { + var $wrap = $('#wap-databases-wrap'); + + function renderDbToolbar(servers) { + var opts = ''; + $.each(servers, function (i, s) { opts += ''; }); + $wrap.html( + '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '' + + '' + + '
' + ); + + $('#wap-dbs-load').on('click', loadDbList); + + $('#wap-db-add-btn').on('click', function () { + var sid = $('#wap-dbs-server').val(); + if (!sid) { alert('Select a server first.'); return; } + $('#wap-db-form').html(dbAddForm(sid)).show(); + }); + + $('#wap-db-sync-btn').on('click', function () { + var sid = $('#wap-dbs-server').val(); + if (!sid) { alert('Select a server.'); return; } + post('wooaapanel_admin_db_sync', { server_id: sid }, function (res) { + alert(res.success ? (res.data && res.data.message || 'Synced.') : 'Error: ' + JSON.stringify(res.data)); + }); + }); + + $('#wap-db-recycle-btn').on('click', function () { + var sid = $('#wap-dbs-server').val(); + if (!sid) { alert('Select a server.'); return; } + post('wooaapanel_admin_db_recycle', { server_id: sid }, function (res) { + alert(res.success ? 'Recycle bin:\n' + pretty(res.data) : 'Error.'); + }); + }); + + $wrap.on('click', '.wap-db-manage', function () { + openDbDetail({ + server_id: $(this).closest('tr').data('server'), + db_name: $(this).closest('tr').data('db') + }); + }); + + $wrap.on('click', '.wap-db-del-row', function () { + if (!confirm_action('Delete this database? This is irreversible.')) return; + var $row = $(this).closest('tr'); + post('wooaapanel_admin_db_delete', { + server_id: $row.data('server'), + db_name: $row.data('db') + }, function (res) { + alert(res.success ? 'Database deleted.' : 'Error: ' + JSON.stringify(res.data)); + loadDbList(); + }); + }); + } + + function loadDbList() { + var sid = $('#wap-dbs-server').val(); + var search = $('#wap-dbs-search').val(); + if (!sid) { alert('Select a server.'); return; } + post('wooaapanel_admin_db_list', { server_id: sid, search: search }, function (res) { + var dbs = res.data && res.data.data ? res.data.data : []; + var html = '' + + '' + + ''; + $.each(dbs, function (i, d) { + html += '' + + '' + + '' + + '' + + '' + + '' + + ''; + }); + html += '
IDNameUserTypeSizeActions
' + (d.id || '—') + '' + escHtml(d.name || '') + '' + escHtml(d.username || d.db_user || '') + '' + escHtml(d.type || 'MySQL') + '' + escHtml(d.dataLength || '') + '' + + ' ' + + '' + + '
'; + $('#wap-dbs-list').html(html); + }); + } + + function dbAddForm(server_id) { + return '

Add Database

' + + '' + + '' + + '' + + '' + + '' + + '' + + ' ' + + '
'; + } + + $wrap.on('click', '.wap-dbf-save', function () { + var $f = $(this).closest('.wap-inline-form'); + post('wooaapanel_admin_db_add', { + server_id: $f.find('.wap-dbf-server').val(), + db_name: $f.find('.wap-dbf-name').val(), + db_user: $f.find('.wap-dbf-user').val(), + password: $f.find('.wap-dbf-pass').val(), + codeing: $f.find('.wap-dbf-enc').val() + }, function (res) { + notice($f.find('#wap-db-form-notice'), res.success ? 'Created!' : 'Error: ' + JSON.stringify(res.data), res.success ? 'success' : 'error'); + if (res.success) loadDbList(); + }); + }); + + $wrap.on('click', '.wap-dbf-cancel', function () { $('#wap-db-form').hide(); }); + + function openDbDetail(opts) { + var $d = $('#wap-db-detail').show(); + $d.html( + '

Manage: ' + escHtml(opts.db_name) + '

' + + '
' + + '
Backups
' + + '
Access
' + + '
Password
' + + '
Quota
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '' + + ' ' + + ' ' + + ' ' + + '
' + ); + + $d.find('.wap-tab').on('click', function () { + $d.find('.wap-tab').removeClass('active'); + $d.find('.wap-tab-content').removeClass('active'); + $(this).addClass('active'); + $d.find('#wap-db-tab-' + $(this).data('tab')).addClass('active'); + }); + + loadDbBackupsTab(opts, $d); + $d.find('.wap-tab[data-tab="access"]').on('click', function () { loadDbAccessTab(opts, $d); }); + $d.find('.wap-tab[data-tab="password"]').on('click', function () { renderDbPasswordTab(opts, $d); }); + $d.find('.wap-tab[data-tab="quota"]').on('click', function () { renderDbQuotaTab(opts, $d); }); + + $d.find('.wap-db-backup-now').on('click', function () { + post('wooaapanel_admin_db_backup', opts, function (res) { + $d.find('#wap-db-quick-notice').text(res.success ? 'Backup started!' : JSON.stringify(res.data)); + loadDbBackupsTab(opts, $d); + }); + }); + + $d.find('.wap-db-optimize').on('click', function () { + post('wooaapanel_admin_db_optimize', opts, function (res) { + $d.find('#wap-db-quick-notice').text(res.success ? 'Optimized!' : JSON.stringify(res.data)); + }); + }); + + $d.find('.wap-db-repair').on('click', function () { + post('wooaapanel_admin_db_repair', opts, function (res) { + $d.find('#wap-db-quick-notice').text(res.success ? 'Repaired!' : JSON.stringify(res.data)); + }); + }); + } + + function loadDbBackupsTab(opts, $d) { + var $tab = $d.find('#wap-db-tab-backups'); + post('wooaapanel_admin_db_backups', opts, function (res) { + var backups = res.data && res.data.data ? res.data.data : []; + var html = ''; + $.each(backups, function (i, b) { + html += '' + + ''; + }); + html += '
IDFilenameSizeDate
' + (b.id || '') + '' + escHtml(b.name || '') + '' + escHtml(b.size || '') + '' + escHtml(b.addtime || '') + '
'; + $tab.html(html); + $tab.find('.wap-del-backup').on('click', function () { + if (!confirm_action('Delete this backup?')) return; + post('wooaapanel_admin_db_backup_delete', $.extend({}, opts, { backup_id: $(this).data('id') }), function () { loadDbBackupsTab(opts, $d); }); + }); + }); + } + + function loadDbAccessTab(opts, $d) { + var $tab = $d.find('#wap-db-tab-access'); + if ($tab.data('loaded')) return; + $tab.data('loaded', 1); + post('wooaapanel_admin_db_access_get', opts, function (res) { + var acc = res.data && res.data.access ? res.data.access : '%'; + $tab.html( + '

Current: ' + escHtml(acc) + '

' + + '' + + '' + + '' + ); + $tab.find('.wap-set-access').on('click', function () { + post('wooaapanel_admin_db_access_set', $.extend({}, opts, { access: $tab.find('#wap-access-input').val() }), function (res) { + $tab.find('#wap-access-notice').text(res.success ? 'Updated!' : JSON.stringify(res.data)); + }); + }); + }); + } + + function renderDbPasswordTab(opts, $d) { + var $tab = $d.find('#wap-db-tab-password'); + if ($tab.data('loaded')) return; + $tab.data('loaded', 1); + $tab.html( + '' + + '' + + '' + + '' + ); + $tab.find('.wap-set-password').on('click', function () { + post('wooaapanel_admin_db_password', $.extend({}, opts, { + db_user: $tab.find('#wap-pw-user').val(), + password: $tab.find('#wap-pw-pass').val() + }), function (res) { + $tab.find('#wap-pw-notice').text(res.success ? 'Password updated!' : JSON.stringify(res.data)); + }); + }); + } + + function renderDbQuotaTab(opts, $d) { + var $tab = $d.find('#wap-db-tab-quota'); + if ($tab.data('loaded')) return; + $tab.data('loaded', 1); + $tab.html( + '' + + '' + + '' + ); + $tab.find('.wap-set-quota').on('click', function () { + post('wooaapanel_admin_db_quota', $.extend({}, opts, { quota_mb: $tab.find('#wap-quota-input').val() }), function (res) { + $tab.find('#wap-quota-notice').text(res.success ? 'Quota set!' : JSON.stringify(res.data)); + }); + }); + } + + post('wooaapanel_servers_list', {}, function (res) { + renderDbToolbar(res.data || []); + }); + } + + /* ══════════════════════════════════════════════════════════════ + WC PRODUCTS PAGE + ══════════════════════════════════════════════════════════════ */ + + if (page_id === 'products') { + var $wrap = $('#wap-products-wrap'); + + post('wooaapanel_products_list', {}, function (res) { + if (!res.success) { $wrap.html('

Error loading products.

'); return; } + var products = res.data.products || []; + var servers = res.data.servers || []; + + var serverOpts = ''; + $.each(servers, function (i, s) { serverOpts += ''; }); + + var html = '

Assign an aaPanel server to a product. When an order for that product completes, a site and database will be automatically created on the linked server.

' + + ''; + $.each(products, function (i, p) { + var selOpts = serverOpts.replace('value="' + p.server_id + '"', 'value="' + p.server_id + '" selected'); + html += '' + + '' + + '' + + ''; + }); + html += '
ProductLinked Server
' + escHtml(p.name) + ' (#' + p.id + ')
'; + $wrap.html(html); + + $wrap.on('click', '.wap-prod-save', function () { + var $row = $(this).closest('tr'); + var prod_id = $(this).data('id'); + var server_id = $row.find('.wap-prod-server').val(); + var $msg = $row.find('.wap-prod-msg'); + post('wooaapanel_product_server_save', { product_id: prod_id, server_id: server_id }, function (res) { + $msg.text(res.success ? '✓ Saved' : 'Error'); + setTimeout(function () { $msg.text(''); }, 2000); + }); + }); + }); + } + + /* ── HTML escape helpers ──────────────────────────────────── */ + + function escHtml(s) { + return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); + } + function escAttr(s) { return escHtml(s); } + +})(jQuery); diff --git a/includes/class-wooaapanel-account.php b/includes/class-wooaapanel-account.php new file mode 100644 index 0000000..a7c4358 --- /dev/null +++ b/includes/class-wooaapanel-account.php @@ -0,0 +1,550 @@ + admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'wooaapanel_account' ), + 'pdns_nonce' => class_exists( 'WooDomains_PowerDNS_API' ) ? wp_create_nonce( 'woodomains_nonce' ) : '', + 'pdns_active' => class_exists( 'WooDomains_PowerDNS_API' ) ? 1 : 0, + 'ajax_url_woodomains' => admin_url( 'admin-ajax.php' ), + ] ); + } + + // ── My Account page render ──────────────────────────────────────────────── + + public function render_page(): void { + if ( ! is_user_logged_in() ) { + echo '

' . esc_html__( 'Please log in to manage your hosting.', 'wooaapanel' ) . '

'; + return; + } + + $customer_id = get_current_user_id(); + $site_assigns = $this->get_site_assignments( $customer_id ); + $db_assigns = $this->get_db_assignments( $customer_id ); + $pdns_active = class_exists( 'WooDomains_PowerDNS_API' ); + + if ( empty( $site_assigns ) && empty( $db_assigns ) ) { + echo '
' . esc_html__( 'You have no hosting resources assigned yet. Please contact support.', 'wooaapanel' ) . '
'; + return; + } + ?> + + get_results( $wpdb->prepare( " + SELECT a.id, a.server_id, a.site_name, a.domain, + s.name AS server_name, s.url AS server_url + FROM {$wpdb->prefix}wooaapanel_site_assignments a + JOIN {$wpdb->prefix}wooaapanel_servers s ON s.id = a.server_id + WHERE a.customer_id = %d AND s.active = 1 + ORDER BY a.site_name + ", $customer_id ) ); + } + + private function get_db_assignments( int $customer_id ): array { + global $wpdb; + return $wpdb->get_results( $wpdb->prepare( " + SELECT a.id, a.server_id, a.db_name, + s.name AS server_name + FROM {$wpdb->prefix}wooaapanel_db_assignments a + JOIN {$wpdb->prefix}wooaapanel_servers s ON s.id = a.server_id + WHERE a.customer_id = %d AND s.active = 1 + ORDER BY a.db_name + ", $customer_id ) ); + } + + private function get_server( int $id ): ?object { + global $wpdb; + return $wpdb->get_row( $wpdb->prepare( + "SELECT * FROM {$wpdb->prefix}wooaapanel_servers WHERE id = %d AND active = 1", + $id + ) ); + } + + /** Verify nonce + login. */ + private function account_verify(): void { + check_ajax_referer( 'wooaapanel_account', 'nonce' ); + if ( ! is_user_logged_in() ) { + wp_send_json_error( 'Not logged in.', 401 ); + } + } + + /** Confirm the current user owns this site assignment. */ + private function verify_site_ownership( int $server_id, string $site_name ): bool { + global $wpdb; + return (bool) $wpdb->get_var( $wpdb->prepare( " + SELECT id FROM {$wpdb->prefix}wooaapanel_site_assignments + WHERE customer_id = %d AND server_id = %d AND site_name = %s + ", get_current_user_id(), $server_id, $site_name ) ); + } + + /** Confirm the current user owns this DB assignment. */ + private function verify_db_ownership( int $server_id, string $db_name ): bool { + global $wpdb; + return (bool) $wpdb->get_var( $wpdb->prepare( " + SELECT id FROM {$wpdb->prefix}wooaapanel_db_assignments + WHERE customer_id = %d AND server_id = %d AND db_name = %s + ", get_current_user_id(), $server_id, $db_name ) ); + } + + private function api_for_site( int $server_id, string $site_name ): WooAApanel_API { + if ( ! $this->verify_site_ownership( $server_id, $site_name ) ) { + wp_send_json_error( 'Access denied.', 403 ); + } + $server = $this->get_server( $server_id ); + if ( ! $server ) { + wp_send_json_error( 'Server unavailable.' ); + } + return WooAApanel_API::from_server( $server ); + } + + private function api_for_db( int $server_id, string $db_name ): WooAApanel_API { + if ( ! $this->verify_db_ownership( $server_id, $db_name ) ) { + wp_send_json_error( 'Access denied.', 403 ); + } + $server = $this->get_server( $server_id ); + if ( ! $server ) { + wp_send_json_error( 'Server unavailable.' ); + } + return WooAApanel_API::from_server( $server ); + } + + // ═════════════════════════════════════════════════════════════════════════ + // AJAX: Account – Sites + // ═════════════════════════════════════════════════════════════════════════ + + public function ajax_wooaapanel_acct_sites(): void { + $this->account_verify(); + $sites = $this->get_site_assignments( get_current_user_id() ); + $dbs = $this->get_db_assignments( get_current_user_id() ); + wp_send_json_success( [ 'sites' => $sites, 'databases' => $dbs ] ); + } + + public function ajax_wooaapanel_acct_site_domains(): void { + $this->account_verify(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $api = $this->api_for_site( $server_id, $site_name ); + wp_send_json( $api->get_site_domains( $site_name ) ); + } + + public function ajax_wooaapanel_acct_site_add_domain(): void { + $this->account_verify(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $domain = sanitize_text_field( $_POST['domain'] ?? '' ); + + if ( ! $domain ) { + wp_send_json_error( 'Domain is required.' ); + } + + $api = $this->api_for_site( $server_id, $site_name ); + wp_send_json( $api->add_domain( $site_name, $domain ) ); + } + + public function ajax_wooaapanel_acct_site_del_domain(): void { + $this->account_verify(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $domain = sanitize_text_field( $_POST['domain'] ?? '' ); + $site_id = absint( $_POST['site_id'] ?? 0 ); + $api = $this->api_for_site( $server_id, $site_name ); + wp_send_json( $api->delete_domain( $site_name, $domain, $site_id ) ); + } + + public function ajax_wooaapanel_acct_site_xss_get(): void { + $this->account_verify(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $site_path = sanitize_text_field( $_POST['site_path'] ?? "/www/wwwroot/{$site_name}" ); + $api = $this->api_for_site( $server_id, $site_name ); + wp_send_json( $api->get_xss( $site_name, $site_path ) ); + } + + public function ajax_wooaapanel_acct_site_xss_set(): void { + $this->account_verify(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $site_path = sanitize_text_field( $_POST['site_path'] ?? "/www/wwwroot/{$site_name}" ); + $enable = ! empty( $_POST['enable'] ); + $api = $this->api_for_site( $server_id, $site_name ); + wp_send_json( $api->set_xss( $site_name, $site_path, $enable ) ); + } + + public function ajax_wooaapanel_acct_site_php_get(): void { + $this->account_verify(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $api = $this->api_for_site( $server_id, $site_name ); + wp_send_json( $api->get_site_php_version( $site_name ) ); + } + + public function ajax_wooaapanel_acct_site_php_versions(): void { + $this->account_verify(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $api = $this->api_for_site( $server_id, $site_name ); + wp_send_json( $api->get_php_versions() ); + } + + public function ajax_wooaapanel_acct_site_php_set(): void { + $this->account_verify(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $version = sanitize_text_field( $_POST['version'] ?? '' ); + + if ( ! $version ) { + wp_send_json_error( 'PHP version is required.' ); + } + + $api = $this->api_for_site( $server_id, $site_name ); + wp_send_json( $api->set_site_php_version( $site_name, $version ) ); + } + + public function ajax_wooaapanel_acct_site_rewrite_get(): void { + $this->account_verify(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $template = sanitize_text_field( $_POST['template'] ?? '' ); + $api = $this->api_for_site( $server_id, $site_name ); + wp_send_json( $api->get_rewrite_content( $site_name, $template ) ); + } + + public function ajax_wooaapanel_acct_site_rewrite_set(): void { + $this->account_verify(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $content = wp_unslash( $_POST['content'] ?? '' ); + $api = $this->api_for_site( $server_id, $site_name ); + wp_send_json( $api->set_rewrite( $site_name, $content ) ); + } + + public function ajax_wooaapanel_acct_site_ssl_get(): void { + $this->account_verify(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $api = $this->api_for_site( $server_id, $site_name ); + wp_send_json( $api->get_ssl( $site_name ) ); + } + + public function ajax_wooaapanel_acct_server_info(): void { + $this->account_verify(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + + // Fetch the server row to expose only safe fields (name, IP visible via panel URL host). + if ( ! $this->verify_site_ownership( $server_id, $site_name ) ) { + wp_send_json_error( 'Access denied.', 403 ); + } + + $server = $this->get_server( $server_id ); + if ( ! $server ) { + wp_send_json_error( 'Server unavailable.' ); + } + + // Parse IP from URL. + $host = parse_url( $server->url, PHP_URL_HOST ); + + // External IP via aaPanel status endpoint. + $api = WooAApanel_API::from_server( $server ); + $res = $api->get_server_info(); + + wp_send_json_success( [ + 'server_name' => $server->name, + 'server_host' => $host, + 'panel_url' => $server->url, + 'panel_status' => $res['data'] ?? [], + ] ); + } + + // ═════════════════════════════════════════════════════════════════════════ + // AJAX: Account – Databases + // ═════════════════════════════════════════════════════════════════════════ + + public function ajax_wooaapanel_acct_dbs(): void { + $this->account_verify(); + $dbs = $this->get_db_assignments( get_current_user_id() ); + wp_send_json_success( $dbs ); + } + + public function ajax_wooaapanel_acct_db_backup(): void { + $this->account_verify(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $db_name = sanitize_text_field( $_POST['db_name'] ?? '' ); + $api = $this->api_for_db( $server_id, $db_name ); + wp_send_json( $api->backup_database( $db_name ) ); + } + + public function ajax_wooaapanel_acct_db_backups(): void { + $this->account_verify(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $db_name = sanitize_text_field( $_POST['db_name'] ?? '' ); + $api = $this->api_for_db( $server_id, $db_name ); + wp_send_json( $api->get_db_backups( $db_name ) ); + } + + public function ajax_wooaapanel_acct_db_backup_delete(): void { + $this->account_verify(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $db_name = sanitize_text_field( $_POST['db_name'] ?? '' ); + $backup_id = absint( $_POST['backup_id'] ?? 0 ); + $api = $this->api_for_db( $server_id, $db_name ); + wp_send_json( $api->delete_db_backup( $backup_id, $db_name ) ); + } + + public function ajax_wooaapanel_acct_db_optimize(): void { + $this->account_verify(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $db_name = sanitize_text_field( $_POST['db_name'] ?? '' ); + $api = $this->api_for_db( $server_id, $db_name ); + wp_send_json( $api->optimize_table( $db_name ) ); + } + + public function ajax_wooaapanel_acct_db_repair(): void { + $this->account_verify(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $db_name = sanitize_text_field( $_POST['db_name'] ?? '' ); + $api = $this->api_for_db( $server_id, $db_name ); + wp_send_json( $api->repair_table( $db_name ) ); + } + + public function ajax_wooaapanel_acct_db_password(): void { + $this->account_verify(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $db_name = sanitize_text_field( $_POST['db_name'] ?? '' ); + $db_user = sanitize_text_field( $_POST['db_user'] ?? '' ); + $password = $_POST['password'] ?? ''; + + if ( ! $db_user || ! $password ) { + wp_send_json_error( 'Database user and new password are required.' ); + } + + $api = $this->api_for_db( $server_id, $db_name ); + wp_send_json( $api->reset_db_password( $db_name, $db_user, $password ) ); + } +} diff --git a/includes/class-wooaapanel-admin.php b/includes/class-wooaapanel-admin.php new file mode 100644 index 0000000..0f45d23 --- /dev/null +++ b/includes/class-wooaapanel-admin.php @@ -0,0 +1,745 @@ + admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'wooaapanel_admin' ), + ] ); + } + + // ── Pages ──────────────────────────────────────────────────────────────── + + public function page_dashboard(): void { + echo '

WooAApanel

'; + echo '

' . esc_html__( 'Manage aaPanel servers, site and database assignments for your WooCommerce customers.', 'wooaapanel' ) . '

'; + echo '
    '; + $pages = [ + 'wooaapanel-servers' => 'Servers', + 'wooaapanel-site-assignments' => 'Site Assignments', + 'wooaapanel-db-assignments' => 'DB Assignments', + 'wooaapanel-sites' => 'Sites', + 'wooaapanel-databases' => 'Databases', + 'wooaapanel-products' => 'WC Product Linking', + ]; + foreach ( $pages as $slug => $label ) { + printf( '
  • %s
  • ', esc_url( admin_url( 'admin.php?page=' . $slug ) ), esc_html( $label ) ); + } + echo '
'; + } + + public function page_servers(): void { + echo '

' . esc_html__( 'aaPanel Servers', 'wooaapanel' ) . '

'; + echo '
'; + } + + public function page_site_assignments(): void { + echo '

' . esc_html__( 'Site Assignments', 'wooaapanel' ) . '

'; + echo '
'; + } + + public function page_db_assignments(): void { + echo '

' . esc_html__( 'Database Assignments', 'wooaapanel' ) . '

'; + echo '
'; + } + + public function page_sites(): void { + echo '

' . esc_html__( 'Sites', 'wooaapanel' ) . '

'; + echo '
'; + } + + public function page_databases(): void { + echo '

' . esc_html__( 'Databases', 'wooaapanel' ) . '

'; + echo '
'; + } + + public function page_products(): void { + echo '

' . esc_html__( 'WC Product ↔ Server Linking', 'wooaapanel' ) . '

'; + echo '

' . esc_html__( 'Link a WooCommerce product to an aaPanel server. When a customer purchases the product, a site and database will be automatically provisioned on the linked server.', 'wooaapanel' ) . '

'; + echo '
'; + } + + // ── Helpers ─────────────────────────────────────────────────────────────── + + private function verify_admin(): void { + check_ajax_referer( 'wooaapanel_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}wooaapanel_servers WHERE id = %d", + $id + ) ); + } + + private function api( int $server_id ): WooAApanel_API { + $server = $this->get_server( $server_id ); + if ( ! $server ) { + wp_send_json_error( 'Server not found.', 404 ); + } + return WooAApanel_API::from_server( $server ); + } + + // ═════════════════════════════════════════════════════════════════════════ + // AJAX: Servers + // ═════════════════════════════════════════════════════════════════════════ + + public function ajax_wooaapanel_servers_list(): void { + $this->verify_admin(); + global $wpdb; + $rows = $wpdb->get_results( "SELECT id, name, url, active, created_at FROM {$wpdb->prefix}wooaapanel_servers ORDER BY id DESC" ); + wp_send_json_success( $rows ); + } + + public function ajax_wooaapanel_server_save(): void { + $this->verify_admin(); + + $id = absint( $_POST['id'] ?? 0 ); + $name = sanitize_text_field( $_POST['name'] ?? '' ); + $url = esc_url_raw( $_POST['url'] ?? '' ); + $api_key = sanitize_text_field( $_POST['api_key'] ?? '' ); + $active = absint( $_POST['active'] ?? 1 ); + + if ( ! $name || ! $url || ! $api_key ) { + wp_send_json_error( 'Name, URL and API key are required.' ); + } + + global $wpdb; + $data = compact( 'name', 'url', 'api_key', 'active' ); + + if ( $id ) { + $wpdb->update( "{$wpdb->prefix}wooaapanel_servers", $data, [ 'id' => $id ] ); + } else { + $wpdb->insert( "{$wpdb->prefix}wooaapanel_servers", $data ); + $id = $wpdb->insert_id; + } + + wp_send_json_success( [ 'id' => $id ] ); + } + + public function ajax_wooaapanel_server_delete(): void { + $this->verify_admin(); + $id = absint( $_POST['id'] ?? 0 ); + global $wpdb; + $wpdb->delete( "{$wpdb->prefix}wooaapanel_servers", [ 'id' => $id ] ); + wp_send_json_success(); + } + + public function ajax_wooaapanel_server_test(): void { + $this->verify_admin(); + $id = absint( $_POST['id'] ?? 0 ); + $api = $this->api( $id ); + $res = $api->test_connection(); + wp_send_json( $res ); + } + + // ── Remote site/db lists (used by assignment UI) ────────────────────── + + public function ajax_wooaapanel_remote_sites(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $api = $this->api( $server_id ); + $res = $api->get_sites( 1, 200 ); + wp_send_json( $res ); + } + + public function ajax_wooaapanel_remote_dbs(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $api = $this->api( $server_id ); + $res = $api->get_databases( 1, 200 ); + wp_send_json( $res ); + } + + // ═════════════════════════════════════════════════════════════════════════ + // AJAX: Site Assignments + // ═════════════════════════════════════════════════════════════════════════ + + public function ajax_wooaapanel_site_assignments_list(): void { + $this->verify_admin(); + global $wpdb; + $rows = $wpdb->get_results( " + SELECT a.id, a.customer_id, a.server_id, a.site_name, a.domain, a.created_at, + s.name AS server_name, + u.display_name AS customer_name, u.user_email AS customer_email + FROM {$wpdb->prefix}wooaapanel_site_assignments a + JOIN {$wpdb->prefix}wooaapanel_servers s ON s.id = a.server_id + LEFT JOIN {$wpdb->users} u ON u.ID = a.customer_id + ORDER BY a.id DESC + " ); + wp_send_json_success( $rows ); + } + + public function ajax_wooaapanel_site_assignment_save(): void { + $this->verify_admin(); + + $id = absint( $_POST['id'] ?? 0 ); + $customer_id = absint( $_POST['customer_id'] ?? 0 ); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $domain = sanitize_text_field( $_POST['domain'] ?? '' ); + + if ( ! $customer_id || ! $server_id || ! $site_name ) { + wp_send_json_error( 'Customer, server and site name are required.' ); + } + + global $wpdb; + $data = compact( 'customer_id', 'server_id', 'site_name', 'domain' ); + + if ( $id ) { + $wpdb->update( "{$wpdb->prefix}wooaapanel_site_assignments", $data, [ 'id' => $id ] ); + } else { + $wpdb->insert( "{$wpdb->prefix}wooaapanel_site_assignments", $data ); + $id = $wpdb->insert_id; + } + + wp_send_json_success( [ 'id' => $id ] ); + } + + public function ajax_wooaapanel_site_assignment_delete(): void { + $this->verify_admin(); + $id = absint( $_POST['id'] ?? 0 ); + global $wpdb; + $wpdb->delete( "{$wpdb->prefix}wooaapanel_site_assignments", [ 'id' => $id ] ); + wp_send_json_success(); + } + + // ═════════════════════════════════════════════════════════════════════════ + // AJAX: DB Assignments + // ═════════════════════════════════════════════════════════════════════════ + + public function ajax_wooaapanel_db_assignments_list(): void { + $this->verify_admin(); + global $wpdb; + $rows = $wpdb->get_results( " + SELECT a.id, a.customer_id, a.server_id, a.db_name, a.created_at, + s.name AS server_name, + u.display_name AS customer_name, u.user_email AS customer_email + FROM {$wpdb->prefix}wooaapanel_db_assignments a + JOIN {$wpdb->prefix}wooaapanel_servers s ON s.id = a.server_id + LEFT JOIN {$wpdb->users} u ON u.ID = a.customer_id + ORDER BY a.id DESC + " ); + wp_send_json_success( $rows ); + } + + public function ajax_wooaapanel_db_assignment_save(): void { + $this->verify_admin(); + + $id = absint( $_POST['id'] ?? 0 ); + $customer_id = absint( $_POST['customer_id'] ?? 0 ); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $db_name = sanitize_text_field( $_POST['db_name'] ?? '' ); + + if ( ! $customer_id || ! $server_id || ! $db_name ) { + wp_send_json_error( 'Customer, server and database name are required.' ); + } + + global $wpdb; + $data = compact( 'customer_id', 'server_id', 'db_name' ); + + if ( $id ) { + $wpdb->update( "{$wpdb->prefix}wooaapanel_db_assignments", $data, [ 'id' => $id ] ); + } else { + $wpdb->insert( "{$wpdb->prefix}wooaapanel_db_assignments", $data ); + $id = $wpdb->insert_id; + } + + wp_send_json_success( [ 'id' => $id ] ); + } + + public function ajax_wooaapanel_db_assignment_delete(): void { + $this->verify_admin(); + $id = absint( $_POST['id'] ?? 0 ); + global $wpdb; + $wpdb->delete( "{$wpdb->prefix}wooaapanel_db_assignments", [ 'id' => $id ] ); + wp_send_json_success(); + } + + // ═════════════════════════════════════════════════════════════════════════ + // AJAX: Customer search + // ═════════════════════════════════════════════════════════════════════════ + + public function ajax_wooaapanel_customers_search(): void { + $this->verify_admin(); + + $q = sanitize_text_field( $_POST['q'] ?? '' ); + $users = get_users( [ + 'role__in' => [ 'customer', 'subscriber', 'administrator' ], + 'search' => '*' . $q . '*', + 'number' => 20, + 'fields' => [ 'ID', 'display_name', 'user_email' ], + ] ); + + wp_send_json_success( array_map( fn( $u ) => [ + 'id' => $u->ID, + 'label' => $u->display_name . ' <' . $u->user_email . '>', + ], $users ) ); + } + + // ═════════════════════════════════════════════════════════════════════════ + // AJAX: Admin site actions (ALL operations) + // ═════════════════════════════════════════════════════════════════════════ + + public function ajax_wooaapanel_admin_site_list(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $search = sanitize_text_field( $_POST['search'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->get_sites( 1, 100, $search ) ); + } + + public function ajax_wooaapanel_admin_site_add(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $api = $this->api( $server_id ); + $data = [ + 'webname' => sanitize_text_field( $_POST['webname'] ?? '' ), + 'path' => sanitize_text_field( $_POST['path'] ?? '' ), + 'type_id' => absint( $_POST['type_id'] ?? 0 ), + 'version' => sanitize_text_field( $_POST['version'] ?? '' ), + 'port' => absint( $_POST['port'] ?? 80 ), + 'ps' => sanitize_text_field( $_POST['ps'] ?? '' ), + 'ftp_username' => sanitize_text_field( $_POST['ftp_username'] ?? '' ), + 'ftp_password' => sanitize_text_field( $_POST['ftp_password'] ?? '' ), + 'sql' => sanitize_text_field( $_POST['sql'] ?? '' ), + 'codeing' => sanitize_text_field( $_POST['codeing'] ?? 'utf8mb4' ), + 'datauser' => sanitize_text_field( $_POST['datauser'] ?? '' ), + 'datapassword' => sanitize_text_field( $_POST['datapassword'] ?? '' ), + ]; + wp_send_json( $api->add_site( $data ) ); + } + + public function ajax_wooaapanel_admin_site_delete(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $ftp = ! empty( $_POST['ftp'] ); + $database = ! empty( $_POST['database'] ); + $path = ! empty( $_POST['path'] ); + $api = $this->api( $server_id ); + wp_send_json( $api->delete_site( $site_name, $ftp, $database, $path ) ); + } + + public function ajax_wooaapanel_admin_site_domains(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->get_site_domains( $site_name ) ); + } + + public function ajax_wooaapanel_admin_site_add_domain(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $domain = sanitize_text_field( $_POST['domain'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->add_domain( $site_name, $domain ) ); + } + + public function ajax_wooaapanel_admin_site_del_domain(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $domain = sanitize_text_field( $_POST['domain'] ?? '' ); + $site_id = absint( $_POST['site_id'] ?? 0 ); + $api = $this->api( $server_id ); + wp_send_json( $api->delete_domain( $site_name, $domain, $site_id ) ); + } + + public function ajax_wooaapanel_admin_site_xss_get(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $site_path = sanitize_text_field( $_POST['site_path'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->get_xss( $site_name, $site_path ) ); + } + + public function ajax_wooaapanel_admin_site_xss_set(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $site_path = sanitize_text_field( $_POST['site_path'] ?? '' ); + $enable = ! empty( $_POST['enable'] ); + $api = $this->api( $server_id ); + wp_send_json( $api->set_xss( $site_name, $site_path, $enable ) ); + } + + public function ajax_wooaapanel_admin_site_php_get(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->get_site_php_version( $site_name ) ); + } + + public function ajax_wooaapanel_admin_site_php_versions(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $api = $this->api( $server_id ); + wp_send_json( $api->get_php_versions() ); + } + + public function ajax_wooaapanel_admin_site_php_set(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $version = sanitize_text_field( $_POST['version'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->set_site_php_version( $site_name, $version ) ); + } + + public function ajax_wooaapanel_admin_site_rewrite_list(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->get_rewrite_list( $site_name ) ); + } + + public function ajax_wooaapanel_admin_site_rewrite_get(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $template = sanitize_text_field( $_POST['template'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->get_rewrite_content( $site_name, $template ) ); + } + + public function ajax_wooaapanel_admin_site_rewrite_set(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $content = wp_unslash( $_POST['content'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->set_rewrite( $site_name, $content ) ); + } + + public function ajax_wooaapanel_admin_site_ssl_get(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->get_ssl( $site_name ) ); + } + + public function ajax_wooaapanel_admin_site_ssl_close(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->close_ssl( $site_name ) ); + } + + public function ajax_wooaapanel_admin_site_ssl_list(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $api = $this->api( $server_id ); + wp_send_json( $api->list_ssl_certs() ); + } + + public function ajax_wooaapanel_admin_site_ssl_upload(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $key = wp_unslash( $_POST['key'] ?? '' ); + $cert = wp_unslash( $_POST['cert'] ?? '' ); + $domain = sanitize_text_field( $_POST['domain'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->upload_cert( $key, $cert, $domain ) ); + } + + public function ajax_wooaapanel_admin_site_ssl_deploy(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $cert_id = absint( $_POST['cert_id'] ?? 0 ); + $api = $this->api( $server_id ); + wp_send_json( $api->deploy_cert_to_site( $site_name, $cert_id ) ); + } + + public function ajax_wooaapanel_admin_site_run_path(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $site_name = sanitize_text_field( $_POST['site_name'] ?? '' ); + $run_path = sanitize_text_field( $_POST['run_path'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->set_run_path( $site_name, $run_path ) ); + } + + // ═════════════════════════════════════════════════════════════════════════ + // AJAX: Admin DB actions (ALL operations) + // ═════════════════════════════════════════════════════════════════════════ + + public function ajax_wooaapanel_admin_db_list(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $search = sanitize_text_field( $_POST['search'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->get_databases( 1, 100, $search ) ); + } + + public function ajax_wooaapanel_admin_db_add(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $db_name = sanitize_text_field( $_POST['db_name'] ?? '' ); + $db_user = sanitize_text_field( $_POST['db_user'] ?? '' ); + $password = sanitize_text_field( $_POST['password'] ?? '' ); + $codeing = sanitize_text_field( $_POST['codeing'] ?? 'utf8mb4' ); + $api = $this->api( $server_id ); + wp_send_json( $api->add_database( $db_name, $db_user, $password, $codeing ) ); + } + + public function ajax_wooaapanel_admin_db_delete(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $db_name = sanitize_text_field( $_POST['db_name'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->delete_database( $db_name ) ); + } + + public function ajax_wooaapanel_admin_db_backup(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $db_name = sanitize_text_field( $_POST['db_name'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->backup_database( $db_name ) ); + } + + public function ajax_wooaapanel_admin_db_backups(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $db_name = sanitize_text_field( $_POST['db_name'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->get_db_backups( $db_name ) ); + } + + public function ajax_wooaapanel_admin_db_backup_delete(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $backup_id = absint( $_POST['backup_id'] ?? 0 ); + $db_name = sanitize_text_field( $_POST['db_name'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->delete_db_backup( $backup_id, $db_name ) ); + } + + public function ajax_wooaapanel_admin_db_optimize(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $db_name = sanitize_text_field( $_POST['db_name'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->optimize_table( $db_name ) ); + } + + public function ajax_wooaapanel_admin_db_repair(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $db_name = sanitize_text_field( $_POST['db_name'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->repair_table( $db_name ) ); + } + + public function ajax_wooaapanel_admin_db_access_get(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $db_name = sanitize_text_field( $_POST['db_name'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->get_db_access( $db_name ) ); + } + + public function ajax_wooaapanel_admin_db_access_set(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $db_name = sanitize_text_field( $_POST['db_name'] ?? '' ); + $access = sanitize_text_field( $_POST['access'] ?? '%' ); + $api = $this->api( $server_id ); + wp_send_json( $api->set_db_access( $db_name, $access ) ); + } + + public function ajax_wooaapanel_admin_db_password(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $db_name = sanitize_text_field( $_POST['db_name'] ?? '' ); + $db_user = sanitize_text_field( $_POST['db_user'] ?? '' ); + $password = sanitize_text_field( $_POST['password'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->reset_db_password( $db_name, $db_user, $password ) ); + } + + public function ajax_wooaapanel_admin_db_recycle(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $api = $this->api( $server_id ); + wp_send_json( $api->get_recycle_bin() ); + } + + public function ajax_wooaapanel_admin_db_restore(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $path = sanitize_text_field( $_POST['path'] ?? '' ); + $api = $this->api( $server_id ); + wp_send_json( $api->restore_from_recycle( $path ) ); + } + + public function ajax_wooaapanel_admin_db_sync(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $api = $this->api( $server_id ); + wp_send_json( $api->sync_to_databases() ); + } + + public function ajax_wooaapanel_admin_db_quota(): void { + $this->verify_admin(); + $server_id = absint( $_POST['server_id'] ?? 0 ); + $db_name = sanitize_text_field( $_POST['db_name'] ?? '' ); + $quota_mb = absint( $_POST['quota_mb'] ?? 0 ); + $api = $this->api( $server_id ); + wp_send_json( $api->modify_db_quota( $db_name, $quota_mb ) ); + } + + // ═════════════════════════════════════════════════════════════════════════ + // AJAX: WC Product server linking + // ═════════════════════════════════════════════════════════════════════════ + + public function ajax_wooaapanel_products_list(): void { + $this->verify_admin(); + + $products = wc_get_products( [ 'limit' => 200, 'status' => 'publish' ] ); + $servers = $this->get_all_servers(); + + $rows = []; + foreach ( $products as $product ) { + $rows[] = [ + 'id' => $product->get_id(), + 'name' => $product->get_name(), + 'server_id' => (int) get_post_meta( $product->get_id(), '_wooaapanel_server_id', true ), + ]; + } + + wp_send_json_success( [ + 'products' => $rows, + 'servers' => $servers, + ] ); + } + + public function ajax_wooaapanel_product_server_save(): void { + $this->verify_admin(); + $product_id = absint( $_POST['product_id'] ?? 0 ); + $server_id = absint( $_POST['server_id'] ?? 0 ); + update_post_meta( $product_id, '_wooaapanel_server_id', $server_id ); + wp_send_json_success(); + } + + // ── Internal helpers ────────────────────────────────────────────────── + + private function get_all_servers(): array { + global $wpdb; + return $wpdb->get_results( "SELECT id, name FROM {$wpdb->prefix}wooaapanel_servers WHERE active = 1 ORDER BY name" ); + } +} diff --git a/includes/class-wooaapanel-api.php b/includes/class-wooaapanel-api.php new file mode 100644 index 0000000..fa3e3c8 --- /dev/null +++ b/includes/class-wooaapanel-api.php @@ -0,0 +1,399 @@ +base_url = rtrim( $url, '/' ); + $this->api_key = $api_key; + } + + // ── Auth helpers ───────────────────────────────────────────────────────── + + private function auth_params(): array { + $now = time(); + return [ + 'request_time' => $now, + 'request_token' => md5( $now . md5( $this->api_key ) ), + ]; + } + + // ── Core HTTP ──────────────────────────────────────────────────────────── + + /** + * POST to an aaPanel v2 endpoint. + * + * @param string $path e.g. '/v2/site?action=AddSite' + * @param array $body POST body fields (multipart) + */ + public function post( string $path, array $body = [] ): array { + $auth = $this->auth_params(); + // Auth params go on the query string. + $url = $this->base_url . $path + . ( str_contains( $path, '?' ) ? '&' : '?' ) + . http_build_query( $auth ); + + $response = wp_remote_post( $url, [ + 'timeout' => $this->timeout, + 'sslverify' => apply_filters( 'wooaapanel_sslverify', false ), + 'body' => $body, + ] ); + + if ( is_wp_error( $response ) ) { + return [ 'success' => false, 'error' => $response->get_error_message() ]; + } + + $code = wp_remote_retrieve_response_code( $response ); + $raw = wp_remote_retrieve_body( $response ); + $data = json_decode( $raw, true ); + + return [ + 'success' => ( $code >= 200 && $code < 300 ), + 'data' => $data, + 'code' => $code, + ]; + } + + // ── Connection test ─────────────────────────────────────────────────────── + + public function test_connection(): array { + return $this->post( '/v2/panel/public/get_soft_status', [ 'name' => 'nginx' ] ); + } + + // ═══════════════════════════════════════════════════════════════════════════ + // WEBSITE / PHP PROJECT + // ═══════════════════════════════════════════════════════════════════════════ + + // ── Site listing ───────────────────────────────────────────────────────── + + public function get_sites( int $page = 1, int $limit = 50, string $search = '' ): array { + return $this->post( '/v2/data?action=getData', [ + 'p' => $page, + 'limit' => $limit, + 'table' => 'sites', + 'search' => $search, + 'type' => -1, + ] ); + } + + public function get_site_types(): array { + return $this->post( '/v2/site?action=get_site_types', [] ); + } + + // ── Site CRUD ──────────────────────────────────────────────────────────── + + public function add_site( array $data ): array { + return $this->post( '/v2/site?action=AddSite', $data ); + } + + public function check_delete_site( string $site_name ): array { + return $this->post( '/v2/site?action=check_del_data', [ 'name' => $site_name ] ); + } + + public function delete_site( string $site_name, bool $ftp = false, bool $database = false, bool $path = false ): array { + return $this->post( '/v2/site?action=DeleteSite', [ + 'webname' => $site_name, + 'ftp' => $ftp ? 1 : 0, + 'database' => $database ? 1 : 0, + 'path' => $path ? 1 : 0, + ] ); + } + + // ── Domains ────────────────────────────────────────────────────────────── + + public function get_site_domains( string $site_name ): array { + return $this->post( '/v2/data?action=getData&table=domain', [ + 'search' => $site_name, + 'limit' => 100, + 'p' => 1, + ] ); + } + + public function add_domain( string $site_name, string $domain ): array { + return $this->post( '/v2/site?action=AddDomain', [ + 'id' => 0, // will be resolved by panel + 'webname' => $site_name, + 'domain' => $domain, + ] ); + } + + public function delete_domain( string $site_name, string $domain, int $site_id = 0 ): array { + return $this->post( '/v2/site?action=DelDomain', [ + 'id' => $site_id, + 'webname' => $site_name, + 'domain' => $domain, + 'port' => 80, + ] ); + } + + // ── XSS protection ─────────────────────────────────────────────────────── + + public function get_xss( string $site_name, string $site_path ): array { + return $this->post( '/v2/site?action=GetDirUserINI', [ + 'id' => 0, + 'name' => $site_name, + 'path' => $site_path, + ] ); + } + + public function set_xss( string $site_name, string $site_path, bool $enable ): array { + return $this->post( '/v2/site?action=SetDirUserINI', [ + 'id' => 0, + 'name' => $site_name, + 'path' => $site_path, + 'action' => $enable ? 'set' : 'close', + ] ); + } + + // ── Site root ──────────────────────────────────────────────────────────── + + public function get_site_root( string $site_name ): array { + return $this->post( '/v2/data?action=getKey', [ + 'name' => $site_name, + 'key' => 'path', + ] ); + } + + public function set_run_path( string $site_name, string $run_path ): array { + return $this->post( '/v2/site?action=SetSiteRunPath', [ + 'id' => 0, + 'runPath' => $run_path, + 'siteName' => $site_name, + ] ); + } + + // ── PHP version ────────────────────────────────────────────────────────── + + public function get_site_php_version( string $site_name ): array { + return $this->post( '/v2/site?action=GetSitePHPVersion', [ 'siteName' => $site_name ] ); + } + + public function get_php_versions(): array { + return $this->post( '/v2/site?action=GetPHPVersion', [] ); + } + + public function set_site_php_version( string $site_name, string $version ): array { + return $this->post( '/v2/site?action=SetPHPVersion', [ + 'siteName' => $site_name, + 'version' => $version, + ] ); + } + + // ── URL rewrite ────────────────────────────────────────────────────────── + + public function get_rewrite_list( string $site_name ): array { + return $this->post( '/v2/site?action=GetRewriteList', [ 'siteName' => $site_name ] ); + } + + public function get_rewrite_content( string $site_name, string $template ): array { + return $this->post( '/v2/files?action=GetFileBody', [ + 'path' => "/www/server/panel/vhost/rewrite/{$site_name}.conf", + ] ); + } + + public function set_rewrite( string $site_name, string $content ): array { + return $this->post( '/v2/files?action=SaveFileBody', [ + 'path' => "/www/server/panel/vhost/rewrite/{$site_name}.conf", + 'data' => $content, + 'encoding' => 'utf-8', + ] ); + } + + // ── SSL ────────────────────────────────────────────────────────────────── + + public function get_ssl( string $site_name ): array { + return $this->post( '/v2/site?action=GetSSL', [ 'siteName' => $site_name ] ); + } + + public function close_ssl( string $site_name ): array { + return $this->post( '/v2/site?action=CloseSSLConf', [ 'updateOf' => 1, 'siteName' => $site_name ] ); + } + + public function list_ssl_certs(): array { + return $this->post( '/v2/ssl_domain?action=list_ssl_info', [] ); + } + + public function upload_cert( string $key, string $cert, string $domain ): array { + return $this->post( '/v2/ssl_domain?action=upload_cert', [ + 'privateKey' => $key, + 'certPem' => $cert, + 'bindDomain' => $domain, + ] ); + } + + public function deploy_cert_to_site( string $site_name, int $cert_id ): array { + return $this->post( '/v2/ssl_domain?action=cert_deploy_sites', [ + 'siteName' => $site_name, + 'certId' => $cert_id, + ] ); + } + + // ── Server info ────────────────────────────────────────────────────────── + + public function get_server_info(): array { + return $this->post( '/v2/panel/public/get_soft_status', [ 'name' => 'nginx' ] ); + } + + // ═══════════════════════════════════════════════════════════════════════════ + // DATABASES / MYSQL + // ═══════════════════════════════════════════════════════════════════════════ + + // ── Listing ────────────────────────────────────────────────────────────── + + public function get_databases( int $page = 1, int $limit = 50, string $search = '' ): array { + return $this->post( '/v2/data?action=getData&table=databases&type=MySQL', [ + 'p' => $page, + 'limit' => $limit, + 'search' => $search, + 'type' => 'MySQL', + ] ); + } + + public function get_db_info( string $db_name ): array { + return $this->post( '/v2/database?action=GetInfo', [ 'name' => $db_name ] ); + } + + // ── Add / Delete ───────────────────────────────────────────────────────── + + public function add_database( string $db_name, string $db_user, string $password, string $codeing = 'utf8mb4' ): array { + return $this->post( '/v2/database?action=AddDatabase', [ + 'name' => $db_name, + 'db_user' => $db_user, + 'password' => $password, + 'codeing' => $codeing, + 'address' => '%', + 'accept' => '%', + ] ); + } + + public function check_delete_db( string $db_name ): array { + return $this->post( '/v2/database?action=check_del_data', [ 'name' => $db_name ] ); + } + + public function delete_database( string $db_name ): array { + return $this->post( '/v2/database?action=DeleteDatabase', [ 'name' => $db_name ] ); + } + + // ── Backup ─────────────────────────────────────────────────────────────── + + public function backup_database( string $db_name ): array { + return $this->post( '/v2/database?action=ToBackup', [ 'name' => $db_name ] ); + } + + public function get_db_backups( string $db_name ): array { + return $this->post( '/v2/data?action=getData', [ + 'p' => 1, + 'limit' => 100, + 'table' => 'backup', + 'search' => $db_name, + 'type' => 'database', + ] ); + } + + public function delete_db_backup( int $backup_id, string $db_name ): array { + return $this->post( '/v2/database?action=DelBackup', [ + 'id' => $backup_id, + 'name' => $db_name, + ] ); + } + + public function get_recycle_bin(): array { + return $this->post( '/v2/files?action=Get_Recycle_bin', [ 'type' => 'database' ] ); + } + + public function restore_from_recycle( string $path ): array { + return $this->post( '/v2/files?action=Re_Recycle_bin', [ 'path' => $path ] ); + } + + // ── Optimization ───────────────────────────────────────────────────────── + + public function optimize_table( string $db_name ): array { + return $this->post( '/v2/database?action=OpTable', [ 'name' => $db_name ] ); + } + + public function repair_table( string $db_name ): array { + return $this->post( '/v2/database?action=ReTable', [ 'name' => $db_name ] ); + } + + // ── Credentials / Access ───────────────────────────────────────────────── + + public function get_db_access( string $db_name ): array { + return $this->post( '/v2/database?action=GetDatabaseAccess', [ 'name' => $db_name ] ); + } + + public function set_db_access( string $db_name, string $access ): array { + return $this->post( '/v2/database?action=SetDatabaseAccess', [ + 'name' => $db_name, + 'access' => $access, + ] ); + } + + public function reset_db_password( string $db_name, string $db_user, string $password ): array { + return $this->post( '/v2/database?action=ResDatabasePassword', [ + 'name' => $db_name, + 'db_user' => $db_user, + 'password' => $password, + ] ); + } + + public function get_mysql_root_password(): array { + return $this->post( '/v2/data?action=getKey', [ 'name' => 'mysql' ] ); + } + + public function set_mysql_root_password( string $password ): array { + return $this->post( '/v2/database?action=SetupPassword', [ 'password' => $password ] ); + } + + // ── Sync ───────────────────────────────────────────────────────────────── + + public function sync_to_databases( array $ids = [] ): array { + return $this->post( '/v2/database?action=SyncToDatabases&type=0', [ + 'ids' => wp_json_encode( $ids ), + ] ); + } + + public function sync_get_databases(): array { + return $this->post( '/v2/database?action=SyncGetDatabases', [] ); + } + + // ── Import SQL ─────────────────────────────────────────────────────────── + + public function import_sql( string $db_name, string $sql_path ): array { + return $this->post( '/v2/database?action=InputSql', [ + 'name' => $db_name, + 'file' => $sql_path, + ] ); + } + + // ── Quota ──────────────────────────────────────────────────────────────── + + public function modify_db_quota( string $db_name, int $quota_mb ): array { + return $this->post( '/v2/project/quota/modify_database_quota', [ + 'name' => $db_name, + 'quota' => $quota_mb, + ] ); + } + + // ── Static factory ─────────────────────────────────────────────────────── + + public static function from_server( object $server ): self { + return new self( $server->url, $server->api_key ); + } + + public function get_base_url(): string { + return $this->base_url; + } +} diff --git a/includes/class-wooaapanel-installer.php b/includes/class-wooaapanel-installer.php new file mode 100644 index 0000000..2e942a8 --- /dev/null +++ b/includes/class-wooaapanel-installer.php @@ -0,0 +1,56 @@ +get_charset_collate(); + + // aaPanel server instances. + $servers_sql = "CREATE TABLE {$wpdb->prefix}wooaapanel_servers ( + id INT NOT NULL AUTO_INCREMENT, + name VARCHAR(100) NOT NULL, + url VARCHAR(500) NOT NULL, + api_key VARCHAR(500) NOT NULL, + active TINYINT(1) NOT NULL DEFAULT 1, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) + ) $charset;"; + + // Site (PHP project) assignments per customer. + $sites_sql = "CREATE TABLE {$wpdb->prefix}wooaapanel_site_assignments ( + id INT NOT NULL AUTO_INCREMENT, + customer_id BIGINT NOT NULL, + server_id INT NOT NULL, + site_name VARCHAR(255) NOT NULL, + domain VARCHAR(255) NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + KEY idx_customer (customer_id), + KEY idx_server (server_id), + UNIQUE KEY uniq_site (server_id, site_name) + ) $charset;"; + + // Database assignments per customer. + $dbs_sql = "CREATE TABLE {$wpdb->prefix}wooaapanel_db_assignments ( + id INT NOT NULL AUTO_INCREMENT, + customer_id BIGINT NOT NULL, + server_id INT NOT NULL, + db_name VARCHAR(255) NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + KEY idx_customer (customer_id), + KEY idx_server (server_id), + UNIQUE KEY uniq_db (server_id, db_name) + ) $charset;"; + + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + dbDelta( $servers_sql ); + dbDelta( $sites_sql ); + dbDelta( $dbs_sql ); + + update_option( 'wooaapanel_db_version', WOOAAPANEL_VERSION ); + } +} diff --git a/includes/class-wooaapanel-orders.php b/includes/class-wooaapanel-orders.php new file mode 100644 index 0000000..0919d28 --- /dev/null +++ b/includes/class-wooaapanel-orders.php @@ -0,0 +1,124 @@ +get_customer_id(); + if ( ! $customer_id ) { + return; // guest orders – skip + } + + foreach ( $order->get_items() as $item ) { + $product_id = $item->get_product_id(); + $server_id = (int) get_post_meta( $product_id, '_wooaapanel_server_id', true ); + + if ( ! $server_id ) { + continue; + } + + // Avoid double-provisioning if order is re-completed. + $provisioned_key = "_wooaapanel_provisioned_{$product_id}"; + if ( $order->get_meta( $provisioned_key ) ) { + continue; + } + + global $wpdb; + $server = $wpdb->get_row( $wpdb->prepare( + "SELECT * FROM {$wpdb->prefix}wooaapanel_servers WHERE id = %d AND active = 1", + $server_id + ) ); + + if ( ! $server ) { + $order->add_order_note( sprintf( + __( 'WooAApanel: server #%d not found or inactive – skipping provisioning for product #%d.', 'wooaapanel' ), + $server_id, + $product_id + ) ); + continue; + } + + $user = get_user_by( 'id', $customer_id ); + $username = $user ? sanitize_user( $user->user_login, true ) : 'user' . $customer_id; + // Sanitise to aaPanel-safe names (alphanumeric + underscore, max 16 chars). + $safe_name = preg_replace( '/[^a-z0-9_]/', '', strtolower( $username ) ); + $safe_name = substr( $safe_name ?: 'user', 0, 12 ); + $suffix = substr( md5( $order_id . $product_id ), 0, 4 ); + $site_name = $safe_name . '_' . $suffix; + $db_name = $safe_name . '_' . $suffix; + $db_user = $safe_name . '_' . $suffix; + $db_pass = wp_generate_password( 16, false ); + + $api = WooAApanel_API::from_server( $server ); + + // Create site. + $site_res = $api->add_site( [ + 'webname' => $site_name, + 'path' => "/www/wwwroot/{$site_name}", + 'type_id' => 0, + 'version' => '', + 'port' => 80, + 'ps' => "WooCommerce order #{$order_id}", + 'ftp_username' => '', + 'ftp_password' => '', + 'sql' => 'MySQL', + 'codeing' => 'utf8mb4', + 'datauser' => $db_user, + 'datapassword' => $db_pass, + ] ); + + $site_ok = ! empty( $site_res['data']['siteStatus'] ) || ( $site_res['success'] ?? false ); + + if ( $site_ok ) { + // Record site assignment. + $wpdb->insert( "{$wpdb->prefix}wooaapanel_site_assignments", [ + 'customer_id' => $customer_id, + 'server_id' => $server_id, + 'site_name' => $site_name, + 'domain' => '', + ] ); + + // Record DB assignment. + $wpdb->insert( "{$wpdb->prefix}wooaapanel_db_assignments", [ + 'customer_id' => $customer_id, + 'server_id' => $server_id, + 'db_name' => $db_name, + ] ); + + $order->update_meta_data( $provisioned_key, '1' ); + $order->add_order_note( sprintf( + __( 'WooAApanel: provisioned site "%s" and database "%s" on server "%s".', 'wooaapanel' ), + $site_name, + $db_name, + $server->name + ) ); + } else { + $error = $site_res['error'] ?? wp_json_encode( $site_res['data'] ?? [] ); + $order->add_order_note( sprintf( + __( 'WooAApanel: provisioning failed for product #%d on server "%s": %s', 'wooaapanel' ), + $product_id, + $server->name, + $error + ) ); + } + + $order->save(); + } + } +} diff --git a/wooaapanel.php b/wooaapanel.php new file mode 100644 index 0000000..b10ccfd --- /dev/null +++ b/wooaapanel.php @@ -0,0 +1,45 @@ +

%s

', + esc_html__( 'WooAApanel requires WooCommerce to be installed and active.', 'wooaapanel' ) + ); + } ); + return; + } + new WooAApanel_Admin(); + new WooAApanel_Account(); + new WooAApanel_Orders(); +} );