feat: initial WooAApanel plugin

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 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 12:48:06 +01:00
commit 8bb96b9048
10 changed files with 3458 additions and 0 deletions

View File

@@ -0,0 +1,550 @@
<?php
/**
* WooAApanel Account WooCommerce My Account integration.
*
* Adds a "Web Hosting" tab where customers can manage their assigned sites
* and databases. DNS management is provided via WooDomains PowerDNS if
* that plugin is active.
*/
defined( 'ABSPATH' ) || exit;
class WooAApanel_Account {
const ENDPOINT = 'web-hosting';
public function __construct() {
add_action( 'init', [ $this, 'register_endpoint' ] );
add_filter( 'woocommerce_account_menu_items', [ $this, 'add_menu_item' ] );
add_action( 'woocommerce_account_' . self::ENDPOINT . '_endpoint', [ $this, 'render_page' ] );
add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_assets' ] );
$actions = [
// Sites
'wooaapanel_acct_sites',
'wooaapanel_acct_site_domains',
'wooaapanel_acct_site_add_domain',
'wooaapanel_acct_site_del_domain',
'wooaapanel_acct_site_xss_get',
'wooaapanel_acct_site_xss_set',
'wooaapanel_acct_site_php_get',
'wooaapanel_acct_site_php_versions',
'wooaapanel_acct_site_php_set',
'wooaapanel_acct_site_rewrite_get',
'wooaapanel_acct_site_rewrite_set',
'wooaapanel_acct_site_ssl_get',
'wooaapanel_acct_server_info',
// Databases
'wooaapanel_acct_dbs',
'wooaapanel_acct_db_backup',
'wooaapanel_acct_db_backups',
'wooaapanel_acct_db_backup_delete',
'wooaapanel_acct_db_optimize',
'wooaapanel_acct_db_repair',
'wooaapanel_acct_db_password',
];
foreach ( $actions as $action ) {
add_action( 'wp_ajax_' . $action, [ $this, 'ajax_' . $action ] );
}
}
public function register_endpoint(): void {
add_rewrite_endpoint( self::ENDPOINT, EP_ROOT | EP_PAGES );
}
public function add_menu_item( array $items ): array {
$logout = $items['customer-logout'] ?? null;
unset( $items['customer-logout'] );
$items[ self::ENDPOINT ] = __( 'Web Hosting', 'wooaapanel' );
if ( $logout ) {
$items['customer-logout'] = $logout;
}
return $items;
}
public function enqueue_assets(): void {
if ( ! is_account_page() ) {
return;
}
wp_enqueue_style( 'wooaapanel-account', WOOAAPANEL_PLUGIN_URL . 'assets/css/wooaapanel-account.css', [], WOOAAPANEL_VERSION );
wp_enqueue_script( 'wooaapanel-account', WOOAAPANEL_PLUGIN_URL . 'assets/js/wooaapanel-account.js', [ 'jquery' ], WOOAAPANEL_VERSION, true );
wp_localize_script( 'wooaapanel-account', 'wooaapanelAcct', [
'ajax_url' => 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 '<p>' . esc_html__( 'Please log in to manage your hosting.', 'wooaapanel' ) . '</p>';
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 '<div class="woocommerce-info">' . esc_html__( 'You have no hosting resources assigned yet. Please contact support.', 'wooaapanel' ) . '</div>';
return;
}
?>
<div class="wooaapanel-account" id="wooaapanel-account">
<div id="wap-acct-notices"></div>
<?php if ( ! empty( $site_assigns ) ) : ?>
<h2><?php esc_html_e( 'Websites', 'wooaapanel' ); ?></h2>
<?php foreach ( $site_assigns as $assign ) : ?>
<div class="wap-site-panel"
data-assignment-id="<?php echo esc_attr( $assign->id ); ?>"
data-server-id="<?php echo esc_attr( $assign->server_id ); ?>"
data-site-name="<?php echo esc_attr( $assign->site_name ); ?>"
data-domain="<?php echo esc_attr( $assign->domain ); ?>">
<div class="wap-panel-header">
<span class="wap-site-name"><?php echo esc_html( $assign->site_name ); ?></span>
<span class="wap-domain"><?php echo esc_html( $assign->domain ); ?></span>
<span class="wap-server-label"><?php echo esc_html( $assign->server_name ); ?></span>
<div class="wap-panel-actions">
<button class="button wap-btn-sm wap-load-domains"><?php esc_html_e( 'Domains', 'wooaapanel' ); ?></button>
<button class="button wap-btn-sm wap-load-php"><?php esc_html_e( 'PHP', 'wooaapanel' ); ?></button>
<button class="button wap-btn-sm wap-load-rewrite"><?php esc_html_e( 'URL Rewrite', 'wooaapanel' ); ?></button>
<button class="button wap-btn-sm wap-load-ssl"><?php esc_html_e( 'SSL', 'wooaapanel' ); ?></button>
<button class="button wap-btn-sm wap-load-server-info"><?php esc_html_e( 'Server Info', 'wooaapanel' ); ?></button>
<?php if ( $pdns_active && $assign->domain ) : ?>
<button class="button wap-btn-sm wap-load-dns"
data-zone="<?php echo esc_attr( rtrim( $assign->domain, '.' ) . '.' ); ?>">
<?php esc_html_e( 'DNS Records', 'wooaapanel' ); ?>
</button>
<?php endif; ?>
</div>
</div><!-- .wap-panel-header -->
<!-- Domains -->
<div class="wap-section wap-domains-section" style="display:none">
<h4><?php esc_html_e( 'Domain Names', 'wooaapanel' ); ?></h4>
<div class="wap-domains-list"></div>
<div class="wap-add-domain-form">
<input type="text" class="wap-new-domain input-text" placeholder="newdomain.com">
<button class="button wap-add-domain-btn"><?php esc_html_e( 'Add Domain', 'wooaapanel' ); ?></button>
<span class="wap-domain-notice"></span>
</div>
</div>
<!-- PHP Version -->
<div class="wap-section wap-php-section" style="display:none">
<h4><?php esc_html_e( 'PHP Version', 'wooaapanel' ); ?></h4>
<div class="wap-php-current"></div>
<select class="wap-php-select"></select>
<button class="button wap-php-set-btn"><?php esc_html_e( 'Set PHP Version', 'wooaapanel' ); ?></button>
<span class="wap-php-notice"></span>
</div>
<!-- URL Rewrite -->
<div class="wap-section wap-rewrite-section" style="display:none">
<h4><?php esc_html_e( 'URL Rewrite', 'wooaapanel' ); ?></h4>
<textarea class="wap-rewrite-content" rows="10" style="width:100%;font-family:monospace"></textarea>
<button class="button wap-rewrite-save-btn"><?php esc_html_e( 'Save Rewrite', 'wooaapanel' ); ?></button>
<span class="wap-rewrite-notice"></span>
</div>
<!-- SSL Info -->
<div class="wap-section wap-ssl-section" style="display:none">
<h4><?php esc_html_e( 'SSL Certificate', 'wooaapanel' ); ?></h4>
<div class="wap-ssl-info"></div>
</div>
<!-- XSS -->
<div class="wap-section wap-xss-section" style="display:none">
<h4><?php esc_html_e( 'Anti-XSS Protection', 'wooaapanel' ); ?></h4>
<div class="wap-xss-status"></div>
<button class="button wap-xss-toggle-btn"><?php esc_html_e( 'Toggle XSS Protection', 'wooaapanel' ); ?></button>
<span class="wap-xss-notice"></span>
</div>
<!-- Server Info -->
<div class="wap-section wap-server-section" style="display:none">
<h4><?php esc_html_e( 'Server Information', 'wooaapanel' ); ?></h4>
<div class="wap-server-info"></div>
</div>
<!-- DNS (WooDomains PowerDNS integration) -->
<?php if ( $pdns_active && $assign->domain ) : ?>
<div class="wap-section wap-dns-section" style="display:none"
data-zone="<?php echo esc_attr( rtrim( $assign->domain, '.' ) . '.' ); ?>">
<h4><?php esc_html_e( 'DNS Records', 'wooaapanel' ); ?></h4>
<div class="wap-dns-records-list"></div>
<hr>
<h5><?php esc_html_e( 'Add / Update Record', 'wooaapanel' ); ?></h5>
<div class="wap-dns-add-form">
<label><?php esc_html_e( 'Name', 'wooaapanel' ); ?> <input type="text" class="wap-dns-name input-text" placeholder="<?php echo esc_attr( $assign->domain . '.' ); ?>"></label>
<label><?php esc_html_e( 'Type', 'wooaapanel' ); ?>
<select class="wap-dns-type">
<?php foreach ( [ 'A', 'AAAA', 'CNAME', 'MX', 'TXT', 'SRV', 'CAA' ] as $t ) : ?>
<option><?php echo esc_html( $t ); ?></option>
<?php endforeach; ?>
</select>
</label>
<label><?php esc_html_e( 'TTL', 'wooaapanel' ); ?> <input type="number" class="wap-dns-ttl input-text" value="3600" min="60" style="width:80px"></label>
<label><?php esc_html_e( 'Content (one per line)', 'wooaapanel' ); ?><br>
<textarea class="wap-dns-content input-text" rows="3"></textarea>
</label>
<button class="button wap-dns-save-btn"><?php esc_html_e( 'Save Record', 'wooaapanel' ); ?></button>
<span class="wap-dns-notice"></span>
</div>
</div>
<?php endif; ?>
</div><!-- .wap-site-panel -->
<?php endforeach; ?>
<?php endif; ?>
<?php if ( ! empty( $db_assigns ) ) : ?>
<h2><?php esc_html_e( 'Databases', 'wooaapanel' ); ?></h2>
<?php foreach ( $db_assigns as $assign ) : ?>
<div class="wap-db-panel"
data-assignment-id="<?php echo esc_attr( $assign->id ); ?>"
data-server-id="<?php echo esc_attr( $assign->server_id ); ?>"
data-db-name="<?php echo esc_attr( $assign->db_name ); ?>">
<div class="wap-panel-header">
<span class="wap-db-name"><strong><?php echo esc_html( $assign->db_name ); ?></strong></span>
<span class="wap-server-label"><?php echo esc_html( $assign->server_name ); ?></span>
<div class="wap-panel-actions">
<button class="button wap-btn-sm wap-db-backup-btn"><?php esc_html_e( 'Backup', 'wooaapanel' ); ?></button>
<button class="button wap-btn-sm wap-load-db-backups"><?php esc_html_e( 'Backups', 'wooaapanel' ); ?></button>
<button class="button wap-btn-sm wap-db-optimize-btn"><?php esc_html_e( 'Optimize', 'wooaapanel' ); ?></button>
<button class="button wap-btn-sm wap-db-repair-btn"><?php esc_html_e( 'Repair', 'wooaapanel' ); ?></button>
<button class="button wap-btn-sm wap-db-password-btn"><?php esc_html_e( 'Change Password', 'wooaapanel' ); ?></button>
</div>
</div>
<div class="wap-db-action-notice"></div>
<!-- Backup list -->
<div class="wap-section wap-db-backups-section" style="display:none">
<h4><?php esc_html_e( 'Database Backups', 'wooaapanel' ); ?></h4>
<div class="wap-db-backups-list"></div>
</div>
<!-- Change password form -->
<div class="wap-section wap-db-password-section" style="display:none">
<h4><?php esc_html_e( 'Change Database Password', 'wooaapanel' ); ?></h4>
<input type="text" class="wap-db-user input-text" placeholder="<?php esc_attr_e( 'Database user', 'wooaapanel' ); ?>">
<input type="password" class="wap-db-new-pass input-text" placeholder="<?php esc_attr_e( 'New password', 'wooaapanel' ); ?>">
<button class="button wap-db-pass-save-btn"><?php esc_html_e( 'Save Password', 'wooaapanel' ); ?></button>
<span class="wap-db-pass-notice"></span>
</div>
</div><!-- .wap-db-panel -->
<?php endforeach; ?>
<?php endif; ?>
</div><!-- #wooaapanel-account -->
<?php
}
// ── Helpers ───────────────────────────────────────────────────────────────
private function get_site_assignments( int $customer_id ): array {
global $wpdb;
return $wpdb->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 ) );
}
}

View File

@@ -0,0 +1,745 @@
<?php
/**
* WooAApanel Admin menu pages and AJAX handlers.
*/
defined( 'ABSPATH' ) || exit;
class WooAApanel_Admin {
public function __construct() {
add_action( 'admin_menu', [ $this, 'register_menu' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
$actions = [
// Servers
'wooaapanel_servers_list',
'wooaapanel_server_save',
'wooaapanel_server_delete',
'wooaapanel_server_test',
// Site assignments
'wooaapanel_site_assignments_list',
'wooaapanel_site_assignment_save',
'wooaapanel_site_assignment_delete',
// DB assignments
'wooaapanel_db_assignments_list',
'wooaapanel_db_assignment_save',
'wooaapanel_db_assignment_delete',
// Customer search
'wooaapanel_customers_search',
// Remote site list
'wooaapanel_remote_sites',
'wooaapanel_remote_dbs',
// === Admin site actions ===
'wooaapanel_admin_site_list',
'wooaapanel_admin_site_add',
'wooaapanel_admin_site_delete',
'wooaapanel_admin_site_domains',
'wooaapanel_admin_site_add_domain',
'wooaapanel_admin_site_del_domain',
'wooaapanel_admin_site_xss_get',
'wooaapanel_admin_site_xss_set',
'wooaapanel_admin_site_php_get',
'wooaapanel_admin_site_php_versions',
'wooaapanel_admin_site_php_set',
'wooaapanel_admin_site_rewrite_list',
'wooaapanel_admin_site_rewrite_get',
'wooaapanel_admin_site_rewrite_set',
'wooaapanel_admin_site_ssl_get',
'wooaapanel_admin_site_ssl_close',
'wooaapanel_admin_site_ssl_upload',
'wooaapanel_admin_site_ssl_deploy',
'wooaapanel_admin_site_ssl_list',
'wooaapanel_admin_site_run_path',
// === Admin DB actions ===
'wooaapanel_admin_db_list',
'wooaapanel_admin_db_add',
'wooaapanel_admin_db_delete',
'wooaapanel_admin_db_backup',
'wooaapanel_admin_db_backups',
'wooaapanel_admin_db_backup_delete',
'wooaapanel_admin_db_optimize',
'wooaapanel_admin_db_repair',
'wooaapanel_admin_db_access_get',
'wooaapanel_admin_db_access_set',
'wooaapanel_admin_db_password',
'wooaapanel_admin_db_recycle',
'wooaapanel_admin_db_restore',
'wooaapanel_admin_db_sync',
'wooaapanel_admin_db_quota',
// WC product settings
'wooaapanel_products_list',
'wooaapanel_product_server_save',
];
foreach ( $actions as $action ) {
add_action( 'wp_ajax_' . $action, [ $this, 'ajax_' . $action ] );
}
}
// ── Menu ─────────────────────────────────────────────────────────────────
public function register_menu(): void {
add_menu_page(
'WooAApanel',
'WooAApanel',
'manage_woocommerce',
'wooaapanel',
[ $this, 'page_dashboard' ],
'dashicons-admin-site-alt3',
57
);
add_submenu_page( 'wooaapanel', 'Dashboard', 'Dashboard', 'manage_woocommerce', 'wooaapanel', [ $this, 'page_dashboard' ] );
add_submenu_page( 'wooaapanel', 'Servers', 'Servers', 'manage_woocommerce', 'wooaapanel-servers', [ $this, 'page_servers' ] );
add_submenu_page( 'wooaapanel', 'Site Assignments', 'Site Assignments', 'manage_woocommerce', 'wooaapanel-site-assignments',[ $this, 'page_site_assignments' ] );
add_submenu_page( 'wooaapanel', 'DB Assignments', 'DB Assignments', 'manage_woocommerce', 'wooaapanel-db-assignments', [ $this, 'page_db_assignments' ] );
add_submenu_page( 'wooaapanel', 'Sites', 'Sites', 'manage_woocommerce', 'wooaapanel-sites', [ $this, 'page_sites' ] );
add_submenu_page( 'wooaapanel', 'Databases', 'Databases', 'manage_woocommerce', 'wooaapanel-databases', [ $this, 'page_databases' ] );
add_submenu_page( 'wooaapanel', 'WC Products', 'WC Products', 'manage_woocommerce', 'wooaapanel-products', [ $this, 'page_products' ] );
}
public function enqueue_assets( string $hook ): void {
if ( strpos( $hook, 'wooaapanel' ) === false ) {
return;
}
wp_enqueue_style( 'wooaapanel-admin', WOOAAPANEL_PLUGIN_URL . 'assets/css/wooaapanel-admin.css', [], WOOAAPANEL_VERSION );
wp_enqueue_script( 'wooaapanel-admin', WOOAAPANEL_PLUGIN_URL . 'assets/js/wooaapanel-admin.js', [ 'jquery' ], WOOAAPANEL_VERSION, true );
wp_localize_script( 'wooaapanel-admin', 'wooaapanel', [
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'wooaapanel_admin' ),
] );
}
// ── Pages ────────────────────────────────────────────────────────────────
public function page_dashboard(): void {
echo '<div class="wrap"><h1>WooAApanel</h1>';
echo '<p>' . esc_html__( 'Manage aaPanel servers, site and database assignments for your WooCommerce customers.', 'wooaapanel' ) . '</p>';
echo '<ul>';
$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( '<li><a href="%s">%s</a></li>', esc_url( admin_url( 'admin.php?page=' . $slug ) ), esc_html( $label ) );
}
echo '</ul></div>';
}
public function page_servers(): void {
echo '<div class="wrap wooaapanel-page" id="wooaapanel-servers-page"><h1>' . esc_html__( 'aaPanel Servers', 'wooaapanel' ) . '</h1>';
echo '<div id="wap-servers-wrap"></div></div>';
}
public function page_site_assignments(): void {
echo '<div class="wrap wooaapanel-page" id="wooaapanel-site-assignments-page"><h1>' . esc_html__( 'Site Assignments', 'wooaapanel' ) . '</h1>';
echo '<div id="wap-site-assignments-wrap"></div></div>';
}
public function page_db_assignments(): void {
echo '<div class="wrap wooaapanel-page" id="wooaapanel-db-assignments-page"><h1>' . esc_html__( 'Database Assignments', 'wooaapanel' ) . '</h1>';
echo '<div id="wap-db-assignments-wrap"></div></div>';
}
public function page_sites(): void {
echo '<div class="wrap wooaapanel-page" id="wooaapanel-sites-page"><h1>' . esc_html__( 'Sites', 'wooaapanel' ) . '</h1>';
echo '<div id="wap-sites-wrap"></div></div>';
}
public function page_databases(): void {
echo '<div class="wrap wooaapanel-page" id="wooaapanel-databases-page"><h1>' . esc_html__( 'Databases', 'wooaapanel' ) . '</h1>';
echo '<div id="wap-databases-wrap"></div></div>';
}
public function page_products(): void {
echo '<div class="wrap wooaapanel-page" id="wooaapanel-products-page"><h1>' . esc_html__( 'WC Product ↔ Server Linking', 'wooaapanel' ) . '</h1>';
echo '<p>' . 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' ) . '</p>';
echo '<div id="wap-products-wrap"></div></div>';
}
// ── 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" );
}
}

View File

@@ -0,0 +1,399 @@
<?php
/**
* aaPanel API client.
*
* Authentication: every request appends request_time and request_token
* as query-string parameters.
* request_token = md5( time + md5( api_key ) )
*
* All endpoints use POST with multipart/form-data body.
*/
defined( 'ABSPATH' ) || exit;
class WooAApanel_API {
private string $base_url;
private string $api_key;
private int $timeout = 30;
public function __construct( string $url, string $api_key ) {
$this->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;
}
}

View File

@@ -0,0 +1,56 @@
<?php
defined( 'ABSPATH' ) || exit;
class WooAApanel_Installer {
public static function install(): void {
global $wpdb;
$charset = $wpdb->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 );
}
}

View File

@@ -0,0 +1,124 @@
<?php
/**
* WooAApanel Orders auto-provision sites and databases when a WooCommerce
* order is completed for a product linked to an aaPanel server.
*
* Product meta:
* _wooaapanel_server_id (int) which server to provision on
*/
defined( 'ABSPATH' ) || exit;
class WooAApanel_Orders {
public function __construct() {
add_action( 'woocommerce_order_status_completed', [ $this, 'provision_on_complete' ], 10, 1 );
}
public function provision_on_complete( int $order_id ): void {
$order = wc_get_order( $order_id );
if ( ! $order ) {
return;
}
$customer_id = $order->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();
}
}
}