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,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();
}
}
}