Files
WooNooW/includes/Api/CustomersController.php
dwindown 829d9d0d8f feat(api): Add CustomersController with full CRUD operations
Backend implementation for Customer module

Created CustomersController.php:
 GET /customers - List with pagination, search, role filter
 GET /customers/{id} - Get single customer with full details
 POST /customers - Create new customer with validation
 PUT /customers/{id} - Update customer data
 DELETE /customers/{id} - Delete customer (with safety checks)
 GET /customers/search - Autocomplete search

Features:
- Full WooCommerce integration (WC_Customer)
- Billing and shipping address management
- Order stats (total_orders, total_spent)
- Email uniqueness validation
- Username auto-generation from email
- Password generation if not provided
- Role-based permissions (list_users, create_users, etc.)
- Cannot delete current user (safety)
- Optional new account email notification

Data format:
- List: Basic customer info (id, name, email, registered)
- Detail: Full data including billing, shipping, stats
- Search: Minimal data for autocomplete (id, name, email)

Registered routes in Routes.php:
- Added CustomersController import
- Registered all customer endpoints

Next: Frontend API client and CRUD pages
2025-11-20 22:40:59 +07:00

437 lines
15 KiB
PHP

<?php
namespace WooNooW\Api;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
use WP_User;
use WC_Customer;
/**
* CustomersController
*
* Handles customer CRUD operations via REST API
*
* @package WooNooW\Api
*/
class CustomersController {
/**
* Register REST API routes
*/
public static function register_routes() {
// List customers
register_rest_route('woonoow/v1', '/customers', [
'methods' => 'GET',
'callback' => [__CLASS__, 'list_customers'],
'permission_callback' => function () { return current_user_can('list_users'); },
]);
// Get single customer
register_rest_route('woonoow/v1', '/customers/(?P<id>\d+)', [
'methods' => 'GET',
'callback' => [__CLASS__, 'get_customer'],
'permission_callback' => function () { return current_user_can('list_users'); },
]);
// Create customer
register_rest_route('woonoow/v1', '/customers', [
'methods' => 'POST',
'callback' => [__CLASS__, 'create_customer'],
'permission_callback' => function () { return current_user_can('create_users'); },
]);
// Update customer
register_rest_route('woonoow/v1', '/customers/(?P<id>\d+)', [
'methods' => 'PUT',
'callback' => [__CLASS__, 'update_customer'],
'permission_callback' => function () { return current_user_can('edit_users'); },
]);
// Delete customer
register_rest_route('woonoow/v1', '/customers/(?P<id>\d+)', [
'methods' => 'DELETE',
'callback' => [__CLASS__, 'delete_customer'],
'permission_callback' => function () { return current_user_can('delete_users'); },
]);
// Search customers (for autocomplete)
register_rest_route('woonoow/v1', '/customers/search', [
'methods' => 'GET',
'callback' => [__CLASS__, 'search_customers'],
'permission_callback' => function () { return current_user_can('list_users'); },
]);
}
/**
* GET /woonoow/v1/customers
* List all customers with pagination and filtering
*/
public static function list_customers(WP_REST_Request $request): WP_REST_Response {
$page = max(1, (int) $request->get_param('page') ?: 1);
$per_page = max(1, min(100, (int) $request->get_param('per_page') ?: 20));
$search = sanitize_text_field($request->get_param('search') ?: '');
$role = sanitize_text_field($request->get_param('role') ?: 'customer');
$args = [
'role' => $role,
'number' => $per_page,
'paged' => $page,
'orderby' => 'registered',
'order' => 'DESC',
];
// Search by name or email
if ($search) {
$args['search'] = '*' . $search . '*';
$args['search_columns'] = ['user_login', 'user_email', 'display_name'];
}
$user_query = new \WP_User_Query($args);
$users = $user_query->get_results();
$total = $user_query->get_total();
$customers = [];
foreach ($users as $user) {
$customers[] = self::format_customer($user);
}
return new WP_REST_Response([
'data' => $customers,
'pagination' => [
'total' => $total,
'total_pages' => ceil($total / $per_page),
'current' => $page,
'per_page' => $per_page,
],
]);
}
/**
* GET /woonoow/v1/customers/{id}
* Get single customer by ID
*/
public static function get_customer(WP_REST_Request $request): WP_REST_Response|WP_Error {
$id = (int) $request->get_param('id');
$user = get_user_by('ID', $id);
if (!$user) {
return new WP_Error('customer_not_found', __('Customer not found', 'woonoow'), ['status' => 404]);
}
return new WP_REST_Response(self::format_customer($user, true));
}
/**
* POST /woonoow/v1/customers
* Create new customer
*/
public static function create_customer(WP_REST_Request $request): WP_REST_Response|WP_Error {
$data = $request->get_json_params();
// Validate required fields
if (empty($data['email'])) {
return new WP_Error('missing_email', __('Email is required', 'woonoow'), ['status' => 400]);
}
if (empty($data['first_name'])) {
return new WP_Error('missing_first_name', __('First name is required', 'woonoow'), ['status' => 400]);
}
if (empty($data['last_name'])) {
return new WP_Error('missing_last_name', __('Last name is required', 'woonoow'), ['status' => 400]);
}
// Check if email already exists
if (email_exists($data['email'])) {
return new WP_Error('email_exists', __('Email already exists', 'woonoow'), ['status' => 400]);
}
// Generate username from email if not provided
$username = !empty($data['username']) ? sanitize_user($data['username']) : sanitize_user($data['email']);
// Check if username exists
if (username_exists($username)) {
// Append number to make it unique
$base_username = $username;
$counter = 1;
while (username_exists($username)) {
$username = $base_username . $counter;
$counter++;
}
}
// Generate password if not provided
$password = !empty($data['password']) ? $data['password'] : wp_generate_password(12, true, true);
// Create user
$user_id = wp_create_user($username, $password, sanitize_email($data['email']));
if (is_wp_error($user_id)) {
return new WP_Error('create_failed', $user_id->get_error_message(), ['status' => 500]);
}
// Set role to customer
$user = new WP_User($user_id);
$user->set_role('customer');
// Update user meta
wp_update_user([
'ID' => $user_id,
'first_name' => sanitize_text_field($data['first_name']),
'last_name' => sanitize_text_field($data['last_name']),
'display_name' => sanitize_text_field($data['first_name'] . ' ' . $data['last_name']),
]);
// Update WooCommerce customer data
$customer = new WC_Customer($user_id);
// Billing address
if (!empty($data['billing'])) {
$billing = $data['billing'];
$customer->set_billing_first_name(sanitize_text_field($billing['first_name'] ?? $data['first_name']));
$customer->set_billing_last_name(sanitize_text_field($billing['last_name'] ?? $data['last_name']));
$customer->set_billing_company(sanitize_text_field($billing['company'] ?? ''));
$customer->set_billing_address_1(sanitize_text_field($billing['address_1'] ?? ''));
$customer->set_billing_address_2(sanitize_text_field($billing['address_2'] ?? ''));
$customer->set_billing_city(sanitize_text_field($billing['city'] ?? ''));
$customer->set_billing_state(sanitize_text_field($billing['state'] ?? ''));
$customer->set_billing_postcode(sanitize_text_field($billing['postcode'] ?? ''));
$customer->set_billing_country(sanitize_text_field($billing['country'] ?? ''));
$customer->set_billing_phone(sanitize_text_field($billing['phone'] ?? ''));
}
// Shipping address
if (!empty($data['shipping'])) {
$shipping = $data['shipping'];
$customer->set_shipping_first_name(sanitize_text_field($shipping['first_name'] ?? $data['first_name']));
$customer->set_shipping_last_name(sanitize_text_field($shipping['last_name'] ?? $data['last_name']));
$customer->set_shipping_company(sanitize_text_field($shipping['company'] ?? ''));
$customer->set_shipping_address_1(sanitize_text_field($shipping['address_1'] ?? ''));
$customer->set_shipping_address_2(sanitize_text_field($shipping['address_2'] ?? ''));
$customer->set_shipping_city(sanitize_text_field($shipping['city'] ?? ''));
$customer->set_shipping_state(sanitize_text_field($shipping['state'] ?? ''));
$customer->set_shipping_postcode(sanitize_text_field($shipping['postcode'] ?? ''));
$customer->set_shipping_country(sanitize_text_field($shipping['country'] ?? ''));
}
$customer->save();
// Send new account email if requested
if (!empty($data['send_email'])) {
wp_new_user_notification($user_id, null, 'both');
}
return new WP_REST_Response(self::format_customer(get_user_by('ID', $user_id), true), 201);
}
/**
* PUT /woonoow/v1/customers/{id}
* Update existing customer
*/
public static function update_customer(WP_REST_Request $request): WP_REST_Response|WP_Error {
$id = (int) $request->get_param('id');
$data = $request->get_json_params();
$user = get_user_by('ID', $id);
if (!$user) {
return new WP_Error('customer_not_found', __('Customer not found', 'woonoow'), ['status' => 404]);
}
// Update user data
$user_data = ['ID' => $id];
if (!empty($data['email'])) {
// Check if email is changing and if new email exists
if ($data['email'] !== $user->user_email && email_exists($data['email'])) {
return new WP_Error('email_exists', __('Email already exists', 'woonoow'), ['status' => 400]);
}
$user_data['user_email'] = sanitize_email($data['email']);
}
if (!empty($data['first_name'])) {
$user_data['first_name'] = sanitize_text_field($data['first_name']);
}
if (!empty($data['last_name'])) {
$user_data['last_name'] = sanitize_text_field($data['last_name']);
}
if (isset($data['first_name']) || isset($data['last_name'])) {
$first = $data['first_name'] ?? get_user_meta($id, 'first_name', true);
$last = $data['last_name'] ?? get_user_meta($id, 'last_name', true);
$user_data['display_name'] = trim($first . ' ' . $last);
}
if (!empty($data['password'])) {
$user_data['user_pass'] = $data['password'];
}
$result = wp_update_user($user_data);
if (is_wp_error($result)) {
return new WP_Error('update_failed', $result->get_error_message(), ['status' => 500]);
}
// Update WooCommerce customer data
$customer = new WC_Customer($id);
// Billing address
if (!empty($data['billing'])) {
$billing = $data['billing'];
if (isset($billing['first_name'])) $customer->set_billing_first_name(sanitize_text_field($billing['first_name']));
if (isset($billing['last_name'])) $customer->set_billing_last_name(sanitize_text_field($billing['last_name']));
if (isset($billing['company'])) $customer->set_billing_company(sanitize_text_field($billing['company']));
if (isset($billing['address_1'])) $customer->set_billing_address_1(sanitize_text_field($billing['address_1']));
if (isset($billing['address_2'])) $customer->set_billing_address_2(sanitize_text_field($billing['address_2']));
if (isset($billing['city'])) $customer->set_billing_city(sanitize_text_field($billing['city']));
if (isset($billing['state'])) $customer->set_billing_state(sanitize_text_field($billing['state']));
if (isset($billing['postcode'])) $customer->set_billing_postcode(sanitize_text_field($billing['postcode']));
if (isset($billing['country'])) $customer->set_billing_country(sanitize_text_field($billing['country']));
if (isset($billing['phone'])) $customer->set_billing_phone(sanitize_text_field($billing['phone']));
}
// Shipping address
if (!empty($data['shipping'])) {
$shipping = $data['shipping'];
if (isset($shipping['first_name'])) $customer->set_shipping_first_name(sanitize_text_field($shipping['first_name']));
if (isset($shipping['last_name'])) $customer->set_shipping_last_name(sanitize_text_field($shipping['last_name']));
if (isset($shipping['company'])) $customer->set_shipping_company(sanitize_text_field($shipping['company']));
if (isset($shipping['address_1'])) $customer->set_shipping_address_1(sanitize_text_field($shipping['address_1']));
if (isset($shipping['address_2'])) $customer->set_shipping_address_2(sanitize_text_field($shipping['address_2']));
if (isset($shipping['city'])) $customer->set_shipping_city(sanitize_text_field($shipping['city']));
if (isset($shipping['state'])) $customer->set_shipping_state(sanitize_text_field($shipping['state']));
if (isset($shipping['postcode'])) $customer->set_shipping_postcode(sanitize_text_field($shipping['postcode']));
if (isset($shipping['country'])) $customer->set_shipping_country(sanitize_text_field($shipping['country']));
}
$customer->save();
return new WP_REST_Response(self::format_customer(get_user_by('ID', $id), true));
}
/**
* DELETE /woonoow/v1/customers/{id}
* Delete customer
*/
public static function delete_customer(WP_REST_Request $request): WP_REST_Response|WP_Error {
$id = (int) $request->get_param('id');
$user = get_user_by('ID', $id);
if (!$user) {
return new WP_Error('customer_not_found', __('Customer not found', 'woonoow'), ['status' => 404]);
}
// Don't allow deleting current user
if ($id === get_current_user_id()) {
return new WP_Error('cannot_delete_self', __('You cannot delete your own account', 'woonoow'), ['status' => 403]);
}
require_once(ABSPATH . 'wp-admin/includes/user.php');
$result = wp_delete_user($id);
if (!$result) {
return new WP_Error('delete_failed', __('Failed to delete customer', 'woonoow'), ['status' => 500]);
}
return new WP_REST_Response(['success' => true]);
}
/**
* GET /woonoow/v1/customers/search
* Search customers for autocomplete
*/
public static function search_customers(WP_REST_Request $request): WP_REST_Response {
$search = sanitize_text_field($request->get_param('q') ?: '');
$limit = max(1, min(50, (int) $request->get_param('limit') ?: 10));
if (empty($search)) {
return new WP_REST_Response([]);
}
$args = [
'role' => 'customer',
'number' => $limit,
'search' => '*' . $search . '*',
'search_columns' => ['user_login', 'user_email', 'display_name'],
];
$user_query = new \WP_User_Query($args);
$users = $user_query->get_results();
$results = [];
foreach ($users as $user) {
$results[] = [
'id' => $user->ID,
'name' => $user->display_name,
'email' => $user->user_email,
];
}
return new WP_REST_Response($results);
}
/**
* Format customer data for API response
*/
private static function format_customer(WP_User $user, bool $detailed = false): array {
$customer = new WC_Customer($user->ID);
$data = [
'id' => $user->ID,
'username' => $user->user_login,
'email' => $user->user_email,
'first_name' => $user->first_name,
'last_name' => $user->last_name,
'display_name' => $user->display_name,
'registered' => $user->user_registered,
'role' => !empty($user->roles) ? $user->roles[0] : '',
];
if ($detailed) {
$data['billing'] = [
'first_name' => $customer->get_billing_first_name(),
'last_name' => $customer->get_billing_last_name(),
'company' => $customer->get_billing_company(),
'address_1' => $customer->get_billing_address_1(),
'address_2' => $customer->get_billing_address_2(),
'city' => $customer->get_billing_city(),
'state' => $customer->get_billing_state(),
'postcode' => $customer->get_billing_postcode(),
'country' => $customer->get_billing_country(),
'phone' => $customer->get_billing_phone(),
];
$data['shipping'] = [
'first_name' => $customer->get_shipping_first_name(),
'last_name' => $customer->get_shipping_last_name(),
'company' => $customer->get_shipping_company(),
'address_1' => $customer->get_shipping_address_1(),
'address_2' => $customer->get_shipping_address_2(),
'city' => $customer->get_shipping_city(),
'state' => $customer->get_shipping_state(),
'postcode' => $customer->get_shipping_postcode(),
'country' => $customer->get_shipping_country(),
];
// Get order stats
$orders = wc_get_orders([
'customer_id' => $user->ID,
'limit' => -1,
'status' => ['wc-completed', 'wc-processing'],
]);
$total_spent = 0;
foreach ($orders as $order) {
$total_spent += $order->get_total();
}
$data['stats'] = [
'total_orders' => count($orders),
'total_spent' => $total_spent,
];
}
return $data;
}
}