feat: Add licensing module backend
- LicensingSettings.php with key format, activation limits, expiry settings - LicenseManager.php with key generation, activation/deactivation, validation - LicensingModule.php with WooCommerce product meta integration - LicensesController.php with admin, customer, and public API endpoints - Database tables: woonoow_licenses, woonoow_license_activations - has_settings enabled in ModuleRegistry
This commit is contained in:
291
includes/Api/LicensesController.php
Normal file
291
includes/Api/LicensesController.php
Normal file
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
/**
|
||||
* Licenses API Controller
|
||||
*
|
||||
* REST API endpoints for license management.
|
||||
*
|
||||
* @package WooNooW\Api
|
||||
*/
|
||||
|
||||
namespace WooNooW\Api;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use WP_Error;
|
||||
use WooNooW\Core\ModuleRegistry;
|
||||
use WooNooW\Modules\Licensing\LicenseManager;
|
||||
|
||||
class LicensesController {
|
||||
|
||||
/**
|
||||
* Register REST routes
|
||||
*/
|
||||
public static function register_routes() {
|
||||
// Check if module is enabled
|
||||
if (!ModuleRegistry::is_enabled('licensing')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Admin routes
|
||||
register_rest_route('woonoow/v1', '/licenses', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_licenses'],
|
||||
'permission_callback' => function() {
|
||||
return current_user_can('manage_woocommerce');
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('woonoow/v1', '/licenses/(?P<id>\d+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_license'],
|
||||
'permission_callback' => function() {
|
||||
return current_user_can('manage_woocommerce');
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('woonoow/v1', '/licenses/(?P<id>\d+)', [
|
||||
'methods' => 'DELETE',
|
||||
'callback' => [__CLASS__, 'revoke_license'],
|
||||
'permission_callback' => function() {
|
||||
return current_user_can('manage_woocommerce');
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('woonoow/v1', '/licenses/(?P<id>\d+)/activations', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_activations'],
|
||||
'permission_callback' => function() {
|
||||
return current_user_can('manage_woocommerce');
|
||||
},
|
||||
]);
|
||||
|
||||
// Customer routes
|
||||
register_rest_route('woonoow/v1', '/account/licenses', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_customer_licenses'],
|
||||
'permission_callback' => function() {
|
||||
return is_user_logged_in();
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('woonoow/v1', '/account/licenses/(?P<id>\d+)/deactivate', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'customer_deactivate'],
|
||||
'permission_callback' => function() {
|
||||
return is_user_logged_in();
|
||||
},
|
||||
]);
|
||||
|
||||
// Public API routes (for software validation)
|
||||
register_rest_route('woonoow/v1', '/licenses/validate', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'validate_license'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
register_rest_route('woonoow/v1', '/licenses/activate', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'activate_license'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
register_rest_route('woonoow/v1', '/licenses/deactivate', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'deactivate_license'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all licenses (admin)
|
||||
*/
|
||||
public static function get_licenses(WP_REST_Request $request) {
|
||||
$args = [
|
||||
'search' => $request->get_param('search'),
|
||||
'status' => $request->get_param('status'),
|
||||
'product_id' => $request->get_param('product_id'),
|
||||
'user_id' => $request->get_param('user_id'),
|
||||
'limit' => $request->get_param('per_page') ?: 50,
|
||||
'offset' => (($request->get_param('page') ?: 1) - 1) * ($request->get_param('per_page') ?: 50),
|
||||
];
|
||||
|
||||
$result = LicenseManager::get_all_licenses($args);
|
||||
|
||||
// Enrich with product and user info
|
||||
foreach ($result['licenses'] as &$license) {
|
||||
$license = self::enrich_license($license);
|
||||
}
|
||||
|
||||
return new WP_REST_Response([
|
||||
'licenses' => $result['licenses'],
|
||||
'total' => $result['total'],
|
||||
'page' => $request->get_param('page') ?: 1,
|
||||
'per_page' => $args['limit'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get single license (admin)
|
||||
*/
|
||||
public static function get_license(WP_REST_Request $request) {
|
||||
$license = LicenseManager::get_license($request->get_param('id'));
|
||||
|
||||
if (!$license) {
|
||||
return new WP_Error('not_found', __('License not found', 'woonoow'), ['status' => 404]);
|
||||
}
|
||||
|
||||
$license = self::enrich_license($license);
|
||||
$license['activations'] = LicenseManager::get_activations($license['id']);
|
||||
|
||||
return new WP_REST_Response($license);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke license (admin)
|
||||
*/
|
||||
public static function revoke_license(WP_REST_Request $request) {
|
||||
$result = LicenseManager::revoke($request->get_param('id'));
|
||||
|
||||
if (!$result) {
|
||||
return new WP_Error('revoke_failed', __('Failed to revoke license', 'woonoow'), ['status' => 500]);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get activations for license (admin)
|
||||
*/
|
||||
public static function get_activations(WP_REST_Request $request) {
|
||||
$activations = LicenseManager::get_activations($request->get_param('id'));
|
||||
return new WP_REST_Response($activations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get customer's licenses
|
||||
*/
|
||||
public static function get_customer_licenses(WP_REST_Request $request) {
|
||||
$user_id = get_current_user_id();
|
||||
$licenses = LicenseManager::get_user_licenses($user_id);
|
||||
|
||||
// Enrich each license
|
||||
foreach ($licenses as &$license) {
|
||||
$license = self::enrich_license($license);
|
||||
$license['activations'] = LicenseManager::get_activations($license['id']);
|
||||
}
|
||||
|
||||
return new WP_REST_Response($licenses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customer deactivate their own activation
|
||||
*/
|
||||
public static function customer_deactivate(WP_REST_Request $request) {
|
||||
$user_id = get_current_user_id();
|
||||
$license = LicenseManager::get_license($request->get_param('id'));
|
||||
|
||||
if (!$license || $license['user_id'] != $user_id) {
|
||||
return new WP_Error('not_found', __('License not found', 'woonoow'), ['status' => 404]);
|
||||
}
|
||||
|
||||
$data = $request->get_json_params();
|
||||
$result = LicenseManager::deactivate(
|
||||
$license['license_key'],
|
||||
$data['activation_id'] ?? null,
|
||||
$data['machine_id'] ?? null
|
||||
);
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return new WP_REST_Response($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate license (public API)
|
||||
*/
|
||||
public static function validate_license(WP_REST_Request $request) {
|
||||
$data = $request->get_json_params();
|
||||
|
||||
if (empty($data['license_key'])) {
|
||||
return new WP_Error('missing_key', __('License key is required', 'woonoow'), ['status' => 400]);
|
||||
}
|
||||
|
||||
$result = LicenseManager::validate($data['license_key']);
|
||||
return new WP_REST_Response($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate license (public API)
|
||||
*/
|
||||
public static function activate_license(WP_REST_Request $request) {
|
||||
$data = $request->get_json_params();
|
||||
|
||||
if (empty($data['license_key'])) {
|
||||
return new WP_Error('missing_key', __('License key is required', 'woonoow'), ['status' => 400]);
|
||||
}
|
||||
|
||||
$activation_data = [
|
||||
'domain' => $data['domain'] ?? null,
|
||||
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? null,
|
||||
'machine_id' => $data['machine_id'] ?? null,
|
||||
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null,
|
||||
];
|
||||
|
||||
$result = LicenseManager::activate($data['license_key'], $activation_data);
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return new WP_REST_Response($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate license (public API)
|
||||
*/
|
||||
public static function deactivate_license(WP_REST_Request $request) {
|
||||
$data = $request->get_json_params();
|
||||
|
||||
if (empty($data['license_key'])) {
|
||||
return new WP_Error('missing_key', __('License key is required', 'woonoow'), ['status' => 400]);
|
||||
}
|
||||
|
||||
$result = LicenseManager::deactivate(
|
||||
$data['license_key'],
|
||||
$data['activation_id'] ?? null,
|
||||
$data['machine_id'] ?? null
|
||||
);
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return new WP_REST_Response($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enrich license with product and user info
|
||||
*/
|
||||
private static function enrich_license($license) {
|
||||
// Add product info
|
||||
$product = wc_get_product($license['product_id']);
|
||||
$license['product_name'] = $product ? $product->get_name() : __('Unknown Product', 'woonoow');
|
||||
|
||||
// Add user info
|
||||
$user = get_userdata($license['user_id']);
|
||||
$license['user_email'] = $user ? $user->user_email : '';
|
||||
$license['user_name'] = $user ? $user->display_name : __('Unknown User', 'woonoow');
|
||||
|
||||
// Add computed fields
|
||||
$license['is_expired'] = $license['expires_at'] && strtotime($license['expires_at']) < time();
|
||||
$license['activations_remaining'] = $license['activation_limit'] > 0
|
||||
? max(0, $license['activation_limit'] - $license['activation_count'])
|
||||
: -1;
|
||||
|
||||
return $license;
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ use WooNooW\Api\ModulesController;
|
||||
use WooNooW\Api\ModuleSettingsController;
|
||||
use WooNooW\Api\CampaignsController;
|
||||
use WooNooW\Api\DocsController;
|
||||
use WooNooW\Api\LicensesController;
|
||||
use WooNooW\Frontend\ShopController;
|
||||
use WooNooW\Frontend\CartController as FrontendCartController;
|
||||
use WooNooW\Frontend\AccountController;
|
||||
@@ -158,6 +159,9 @@ class Routes {
|
||||
// Campaigns controller
|
||||
CampaignsController::register_routes();
|
||||
|
||||
// Licenses controller (licensing module)
|
||||
LicensesController::register_routes();
|
||||
|
||||
// Modules controller
|
||||
$modules_controller = new ModulesController();
|
||||
$modules_controller->register_routes();
|
||||
|
||||
@@ -82,6 +82,7 @@ class ModuleRegistry {
|
||||
'category' => 'products',
|
||||
'icon' => 'key',
|
||||
'default_enabled' => false,
|
||||
'has_settings' => true,
|
||||
'features' => [
|
||||
__('License key generation', 'woonoow'),
|
||||
__('Activation management', 'woonoow'),
|
||||
|
||||
502
includes/Modules/Licensing/LicenseManager.php
Normal file
502
includes/Modules/Licensing/LicenseManager.php
Normal file
@@ -0,0 +1,502 @@
|
||||
<?php
|
||||
/**
|
||||
* License Manager
|
||||
*
|
||||
* Handles license key generation, activation, deactivation, and validation.
|
||||
*
|
||||
* @package WooNooW\Modules\Licensing
|
||||
*/
|
||||
|
||||
namespace WooNooW\Modules\Licensing;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
use WooNooW\Core\ModuleRegistry;
|
||||
|
||||
class LicenseManager {
|
||||
|
||||
private static $table_name = 'woonoow_licenses';
|
||||
private static $activations_table = 'woonoow_license_activations';
|
||||
|
||||
/**
|
||||
* Initialize
|
||||
*/
|
||||
public static function init() {
|
||||
// Only initialize if module is enabled
|
||||
if (!ModuleRegistry::is_enabled('licensing')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hook into order completion
|
||||
add_action('woocommerce_order_status_completed', [__CLASS__, 'generate_licenses_for_order']);
|
||||
add_action('woocommerce_order_status_processing', [__CLASS__, 'generate_licenses_for_order']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create database tables
|
||||
*/
|
||||
public static function create_tables() {
|
||||
global $wpdb;
|
||||
$charset_collate = $wpdb->get_charset_collate();
|
||||
|
||||
$licenses_table = $wpdb->prefix . self::$table_name;
|
||||
$activations_table = $wpdb->prefix . self::$activations_table;
|
||||
|
||||
$sql = "CREATE TABLE IF NOT EXISTS $licenses_table (
|
||||
id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
license_key varchar(255) NOT NULL,
|
||||
product_id bigint(20) UNSIGNED NOT NULL,
|
||||
order_id bigint(20) UNSIGNED NOT NULL,
|
||||
order_item_id bigint(20) UNSIGNED NOT NULL,
|
||||
user_id bigint(20) UNSIGNED NOT NULL,
|
||||
status varchar(20) NOT NULL DEFAULT 'active',
|
||||
activation_limit int(11) NOT NULL DEFAULT 1,
|
||||
activation_count int(11) NOT NULL DEFAULT 0,
|
||||
expires_at datetime DEFAULT NULL,
|
||||
created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY license_key (license_key),
|
||||
KEY product_id (product_id),
|
||||
KEY order_id (order_id),
|
||||
KEY user_id (user_id),
|
||||
KEY status (status)
|
||||
) $charset_collate;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS $activations_table (
|
||||
id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
license_id bigint(20) UNSIGNED NOT NULL,
|
||||
domain varchar(255) DEFAULT NULL,
|
||||
ip_address varchar(45) DEFAULT NULL,
|
||||
machine_id varchar(255) DEFAULT NULL,
|
||||
user_agent text DEFAULT NULL,
|
||||
status varchar(20) NOT NULL DEFAULT 'active',
|
||||
activated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deactivated_at datetime DEFAULT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY license_id (license_id),
|
||||
KEY status (status)
|
||||
) $charset_collate;";
|
||||
|
||||
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
||||
dbDelta($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate licenses for completed order
|
||||
*/
|
||||
public static function generate_licenses_for_order($order_id) {
|
||||
$order = wc_get_order($order_id);
|
||||
if (!$order) return;
|
||||
|
||||
foreach ($order->get_items() as $item_id => $item) {
|
||||
$product_id = $item->get_product_id();
|
||||
$product = wc_get_product($product_id);
|
||||
|
||||
if (!$product) continue;
|
||||
|
||||
// Check if product has licensing enabled
|
||||
$licensing_enabled = get_post_meta($product_id, '_woonoow_licensing_enabled', true);
|
||||
if ($licensing_enabled !== 'yes') continue;
|
||||
|
||||
// Check if license already exists for this order item
|
||||
if (self::license_exists_for_order_item($item_id)) continue;
|
||||
|
||||
// Get activation limit from product or default
|
||||
$activation_limit = (int) get_post_meta($product_id, '_woonoow_license_activation_limit', true);
|
||||
if ($activation_limit <= 0) {
|
||||
$activation_limit = (int) get_option('woonoow_licensing_default_activation_limit', 1);
|
||||
}
|
||||
|
||||
// Get expiry from product or default
|
||||
$expiry_days = (int) get_post_meta($product_id, '_woonoow_license_expiry_days', true);
|
||||
if ($expiry_days <= 0 && get_option('woonoow_licensing_license_expiry_enabled', false)) {
|
||||
$expiry_days = (int) get_option('woonoow_licensing_default_expiry_days', 365);
|
||||
}
|
||||
|
||||
$expires_at = $expiry_days > 0 ? gmdate('Y-m-d H:i:s', strtotime("+$expiry_days days")) : null;
|
||||
|
||||
// Generate license for each quantity
|
||||
$quantity = $item->get_quantity();
|
||||
for ($i = 0; $i < $quantity; $i++) {
|
||||
self::create_license([
|
||||
'product_id' => $product_id,
|
||||
'order_id' => $order_id,
|
||||
'order_item_id' => $item_id,
|
||||
'user_id' => $order->get_user_id(),
|
||||
'activation_limit' => $activation_limit,
|
||||
'expires_at' => $expires_at,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if license already exists for order item
|
||||
*/
|
||||
public static function license_exists_for_order_item($order_item_id) {
|
||||
global $wpdb;
|
||||
$table = $wpdb->prefix . self::$table_name;
|
||||
|
||||
return (bool) $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM $table WHERE order_item_id = %d",
|
||||
$order_item_id
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new license
|
||||
*/
|
||||
public static function create_license($data) {
|
||||
global $wpdb;
|
||||
$table = $wpdb->prefix . self::$table_name;
|
||||
|
||||
$license_key = self::generate_license_key();
|
||||
|
||||
$wpdb->insert($table, [
|
||||
'license_key' => $license_key,
|
||||
'product_id' => $data['product_id'],
|
||||
'order_id' => $data['order_id'],
|
||||
'order_item_id' => $data['order_item_id'],
|
||||
'user_id' => $data['user_id'],
|
||||
'activation_limit' => $data['activation_limit'] ?? 1,
|
||||
'expires_at' => $data['expires_at'] ?? null,
|
||||
'status' => 'active',
|
||||
]);
|
||||
|
||||
$license_id = $wpdb->insert_id;
|
||||
|
||||
do_action('woonoow/license/created', $license_id, $license_key, $data);
|
||||
|
||||
return [
|
||||
'id' => $license_id,
|
||||
'license_key' => $license_key,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate license key
|
||||
*/
|
||||
public static function generate_license_key() {
|
||||
$format = get_option('woonoow_licensing_license_key_format', 'serial');
|
||||
$prefix = get_option('woonoow_licensing_license_key_prefix', '');
|
||||
|
||||
switch ($format) {
|
||||
case 'uuid':
|
||||
$key = wp_generate_uuid4();
|
||||
break;
|
||||
case 'alphanumeric':
|
||||
$key = strtoupper(wp_generate_password(16, false));
|
||||
break;
|
||||
case 'serial':
|
||||
default:
|
||||
$key = strtoupper(sprintf(
|
||||
'%s-%s-%s-%s',
|
||||
wp_generate_password(4, false),
|
||||
wp_generate_password(4, false),
|
||||
wp_generate_password(4, false),
|
||||
wp_generate_password(4, false)
|
||||
));
|
||||
break;
|
||||
}
|
||||
|
||||
return $prefix . $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get license by key
|
||||
*/
|
||||
public static function get_license_by_key($license_key) {
|
||||
global $wpdb;
|
||||
$table = $wpdb->prefix . self::$table_name;
|
||||
|
||||
return $wpdb->get_row($wpdb->prepare(
|
||||
"SELECT * FROM $table WHERE license_key = %s",
|
||||
$license_key
|
||||
), ARRAY_A);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get license by ID
|
||||
*/
|
||||
public static function get_license($license_id) {
|
||||
global $wpdb;
|
||||
$table = $wpdb->prefix . self::$table_name;
|
||||
|
||||
return $wpdb->get_row($wpdb->prepare(
|
||||
"SELECT * FROM $table WHERE id = %d",
|
||||
$license_id
|
||||
), ARRAY_A);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get licenses for user
|
||||
*/
|
||||
public static function get_user_licenses($user_id, $args = []) {
|
||||
global $wpdb;
|
||||
$table = $wpdb->prefix . self::$table_name;
|
||||
|
||||
$defaults = [
|
||||
'status' => null,
|
||||
'limit' => 50,
|
||||
'offset' => 0,
|
||||
];
|
||||
$args = wp_parse_args($args, $defaults);
|
||||
|
||||
$where = "user_id = %d";
|
||||
$params = [$user_id];
|
||||
|
||||
if ($args['status']) {
|
||||
$where .= " AND status = %s";
|
||||
$params[] = $args['status'];
|
||||
}
|
||||
|
||||
$sql = "SELECT * FROM $table WHERE $where ORDER BY created_at DESC LIMIT %d OFFSET %d";
|
||||
$params[] = $args['limit'];
|
||||
$params[] = $args['offset'];
|
||||
|
||||
return $wpdb->get_results($wpdb->prepare($sql, $params), ARRAY_A);
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate license
|
||||
*/
|
||||
public static function activate($license_key, $activation_data = []) {
|
||||
global $wpdb;
|
||||
$license = self::get_license_by_key($license_key);
|
||||
|
||||
if (!$license) {
|
||||
return new \WP_Error('invalid_license', __('Invalid license key', 'woonoow'));
|
||||
}
|
||||
|
||||
if ($license['status'] !== 'active') {
|
||||
return new \WP_Error('license_inactive', __('License is not active', 'woonoow'));
|
||||
}
|
||||
|
||||
// Check expiry
|
||||
if ($license['expires_at'] && strtotime($license['expires_at']) < time()) {
|
||||
$block_expired = get_option('woonoow_licensing_block_expired_activations', true);
|
||||
if ($block_expired) {
|
||||
return new \WP_Error('license_expired', __('License has expired', 'woonoow'));
|
||||
}
|
||||
}
|
||||
|
||||
// Check activation limit
|
||||
if ($license['activation_limit'] > 0 && $license['activation_count'] >= $license['activation_limit']) {
|
||||
return new \WP_Error('activation_limit_reached', __('Activation limit reached', 'woonoow'));
|
||||
}
|
||||
|
||||
// Create activation record
|
||||
$activations_table = $wpdb->prefix . self::$activations_table;
|
||||
$licenses_table = $wpdb->prefix . self::$table_name;
|
||||
|
||||
$wpdb->insert($activations_table, [
|
||||
'license_id' => $license['id'],
|
||||
'domain' => $activation_data['domain'] ?? null,
|
||||
'ip_address' => $activation_data['ip_address'] ?? null,
|
||||
'machine_id' => $activation_data['machine_id'] ?? null,
|
||||
'user_agent' => $activation_data['user_agent'] ?? null,
|
||||
'status' => 'active',
|
||||
]);
|
||||
|
||||
// Increment activation count
|
||||
$wpdb->query($wpdb->prepare(
|
||||
"UPDATE $licenses_table SET activation_count = activation_count + 1 WHERE id = %d",
|
||||
$license['id']
|
||||
));
|
||||
|
||||
do_action('woonoow/license/activated', $license['id'], $activation_data);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'activation_id' => $wpdb->insert_id,
|
||||
'activations_remaining' => $license['activation_limit'] > 0
|
||||
? max(0, $license['activation_limit'] - $license['activation_count'] - 1)
|
||||
: -1,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate license
|
||||
*/
|
||||
public static function deactivate($license_key, $activation_id = null, $machine_id = null) {
|
||||
global $wpdb;
|
||||
$license = self::get_license_by_key($license_key);
|
||||
|
||||
if (!$license) {
|
||||
return new \WP_Error('invalid_license', __('Invalid license key', 'woonoow'));
|
||||
}
|
||||
|
||||
// Check if deactivation is allowed
|
||||
$allow_deactivation = get_option('woonoow_licensing_allow_deactivation', true);
|
||||
if (!$allow_deactivation) {
|
||||
return new \WP_Error('deactivation_disabled', __('License deactivation is disabled', 'woonoow'));
|
||||
}
|
||||
|
||||
$activations_table = $wpdb->prefix . self::$activations_table;
|
||||
$licenses_table = $wpdb->prefix . self::$table_name;
|
||||
|
||||
// Find activation to deactivate
|
||||
$where = "license_id = %d AND status = 'active'";
|
||||
$params = [$license['id']];
|
||||
|
||||
if ($activation_id) {
|
||||
$where .= " AND id = %d";
|
||||
$params[] = $activation_id;
|
||||
} elseif ($machine_id) {
|
||||
$where .= " AND machine_id = %s";
|
||||
$params[] = $machine_id;
|
||||
}
|
||||
|
||||
$activation = $wpdb->get_row($wpdb->prepare(
|
||||
"SELECT * FROM $activations_table WHERE $where LIMIT 1",
|
||||
$params
|
||||
), ARRAY_A);
|
||||
|
||||
if (!$activation) {
|
||||
return new \WP_Error('no_activation', __('No active activation found', 'woonoow'));
|
||||
}
|
||||
|
||||
// Deactivate
|
||||
$wpdb->update(
|
||||
$activations_table,
|
||||
['status' => 'deactivated', 'deactivated_at' => current_time('mysql')],
|
||||
['id' => $activation['id']]
|
||||
);
|
||||
|
||||
// Decrement activation count
|
||||
$wpdb->query($wpdb->prepare(
|
||||
"UPDATE $licenses_table SET activation_count = GREATEST(0, activation_count - 1) WHERE id = %d",
|
||||
$license['id']
|
||||
));
|
||||
|
||||
do_action('woonoow/license/deactivated', $license['id'], $activation['id']);
|
||||
|
||||
return ['success' => true];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate license (check if valid without activating)
|
||||
*/
|
||||
public static function validate($license_key) {
|
||||
$license = self::get_license_by_key($license_key);
|
||||
|
||||
if (!$license) {
|
||||
return [
|
||||
'valid' => false,
|
||||
'error' => 'invalid_license',
|
||||
'message' => __('Invalid license key', 'woonoow'),
|
||||
];
|
||||
}
|
||||
|
||||
$is_expired = $license['expires_at'] && strtotime($license['expires_at']) < time();
|
||||
|
||||
return [
|
||||
'valid' => $license['status'] === 'active' && !$is_expired,
|
||||
'license_key' => $license['license_key'],
|
||||
'status' => $license['status'],
|
||||
'activation_limit' => (int) $license['activation_limit'],
|
||||
'activation_count' => (int) $license['activation_count'],
|
||||
'activations_remaining' => $license['activation_limit'] > 0
|
||||
? max(0, $license['activation_limit'] - $license['activation_count'])
|
||||
: -1,
|
||||
'expires_at' => $license['expires_at'],
|
||||
'is_expired' => $is_expired,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke license
|
||||
*/
|
||||
public static function revoke($license_id) {
|
||||
global $wpdb;
|
||||
$table = $wpdb->prefix . self::$table_name;
|
||||
|
||||
$result = $wpdb->update(
|
||||
$table,
|
||||
['status' => 'revoked'],
|
||||
['id' => $license_id]
|
||||
);
|
||||
|
||||
if ($result !== false) {
|
||||
do_action('woonoow/license/revoked', $license_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all licenses (admin)
|
||||
*/
|
||||
public static function get_all_licenses($args = []) {
|
||||
global $wpdb;
|
||||
$table = $wpdb->prefix . self::$table_name;
|
||||
|
||||
$defaults = [
|
||||
'search' => '',
|
||||
'status' => null,
|
||||
'product_id' => null,
|
||||
'user_id' => null,
|
||||
'limit' => 50,
|
||||
'offset' => 0,
|
||||
'orderby' => 'created_at',
|
||||
'order' => 'DESC',
|
||||
];
|
||||
$args = wp_parse_args($args, $defaults);
|
||||
|
||||
$where_clauses = ['1=1'];
|
||||
$params = [];
|
||||
|
||||
if ($args['search']) {
|
||||
$where_clauses[] = "license_key LIKE %s";
|
||||
$params[] = '%' . $wpdb->esc_like($args['search']) . '%';
|
||||
}
|
||||
|
||||
if ($args['status']) {
|
||||
$where_clauses[] = "status = %s";
|
||||
$params[] = $args['status'];
|
||||
}
|
||||
|
||||
if ($args['product_id']) {
|
||||
$where_clauses[] = "product_id = %d";
|
||||
$params[] = $args['product_id'];
|
||||
}
|
||||
|
||||
if ($args['user_id']) {
|
||||
$where_clauses[] = "user_id = %d";
|
||||
$params[] = $args['user_id'];
|
||||
}
|
||||
|
||||
$where = implode(' AND ', $where_clauses);
|
||||
$orderby = sanitize_sql_orderby($args['orderby'] . ' ' . $args['order']) ?: 'created_at DESC';
|
||||
|
||||
$sql = "SELECT * FROM $table WHERE $where ORDER BY $orderby LIMIT %d OFFSET %d";
|
||||
$params[] = $args['limit'];
|
||||
$params[] = $args['offset'];
|
||||
|
||||
$licenses = $wpdb->get_results($wpdb->prepare($sql, $params), ARRAY_A);
|
||||
|
||||
// Get total count
|
||||
$count_sql = "SELECT COUNT(*) FROM $table WHERE $where";
|
||||
$total = $wpdb->get_var($wpdb->prepare($count_sql, array_slice($params, 0, -2)));
|
||||
|
||||
return [
|
||||
'licenses' => $licenses,
|
||||
'total' => (int) $total,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get activations for a license
|
||||
*/
|
||||
public static function get_activations($license_id) {
|
||||
global $wpdb;
|
||||
$table = $wpdb->prefix . self::$activations_table;
|
||||
|
||||
return $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT * FROM $table WHERE license_id = %d ORDER BY activated_at DESC",
|
||||
$license_id
|
||||
), ARRAY_A);
|
||||
}
|
||||
}
|
||||
111
includes/Modules/Licensing/LicensingModule.php
Normal file
111
includes/Modules/Licensing/LicensingModule.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
/**
|
||||
* Licensing Module Bootstrap
|
||||
*
|
||||
* @package WooNooW\Modules\Licensing
|
||||
*/
|
||||
|
||||
namespace WooNooW\Modules\Licensing;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
use WooNooW\Core\ModuleRegistry;
|
||||
use WooNooW\Modules\LicensingSettings;
|
||||
|
||||
class LicensingModule {
|
||||
|
||||
/**
|
||||
* Initialize the licensing module
|
||||
*/
|
||||
public static function init() {
|
||||
// Register settings schema
|
||||
LicensingSettings::init();
|
||||
|
||||
// Initialize license manager when module is enabled
|
||||
add_action('plugins_loaded', [__CLASS__, 'maybe_init_manager'], 20);
|
||||
|
||||
// Install tables on module enable
|
||||
add_action('woonoow/module/enabled', [__CLASS__, 'on_module_enabled']);
|
||||
|
||||
// Add product meta fields
|
||||
add_action('woocommerce_product_options_general_product_data', [__CLASS__, 'add_product_licensing_fields']);
|
||||
add_action('woocommerce_process_product_meta', [__CLASS__, 'save_product_licensing_fields']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize manager if module is enabled
|
||||
*/
|
||||
public static function maybe_init_manager() {
|
||||
if (ModuleRegistry::is_enabled('licensing')) {
|
||||
LicenseManager::init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle module enable
|
||||
*/
|
||||
public static function on_module_enabled($module_id) {
|
||||
if ($module_id === 'licensing') {
|
||||
LicenseManager::create_tables();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add licensing fields to product edit page
|
||||
*/
|
||||
public static function add_product_licensing_fields() {
|
||||
global $post;
|
||||
|
||||
if (!ModuleRegistry::is_enabled('licensing')) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<div class="options_group show_if_simple show_if_downloadable">';
|
||||
|
||||
woocommerce_wp_checkbox([
|
||||
'id' => '_woonoow_licensing_enabled',
|
||||
'label' => __('Enable Licensing', 'woonoow'),
|
||||
'description' => __('Generate license keys for this product on purchase', 'woonoow'),
|
||||
]);
|
||||
|
||||
woocommerce_wp_text_input([
|
||||
'id' => '_woonoow_license_activation_limit',
|
||||
'label' => __('Activation Limit', 'woonoow'),
|
||||
'description' => __('Max activations per license (0 = use default, leave empty for unlimited)', 'woonoow'),
|
||||
'type' => 'number',
|
||||
'custom_attributes' => [
|
||||
'min' => '0',
|
||||
'step' => '1',
|
||||
],
|
||||
]);
|
||||
|
||||
woocommerce_wp_text_input([
|
||||
'id' => '_woonoow_license_expiry_days',
|
||||
'label' => __('License Expiry (Days)', 'woonoow'),
|
||||
'description' => __('Days until license expires (0 = never expires)', 'woonoow'),
|
||||
'type' => 'number',
|
||||
'custom_attributes' => [
|
||||
'min' => '0',
|
||||
'step' => '1',
|
||||
],
|
||||
]);
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Save licensing fields
|
||||
*/
|
||||
public static function save_product_licensing_fields($post_id) {
|
||||
$licensing_enabled = isset($_POST['_woonoow_licensing_enabled']) ? 'yes' : 'no';
|
||||
update_post_meta($post_id, '_woonoow_licensing_enabled', $licensing_enabled);
|
||||
|
||||
if (isset($_POST['_woonoow_license_activation_limit'])) {
|
||||
update_post_meta($post_id, '_woonoow_license_activation_limit', absint($_POST['_woonoow_license_activation_limit']));
|
||||
}
|
||||
|
||||
if (isset($_POST['_woonoow_license_expiry_days'])) {
|
||||
update_post_meta($post_id, '_woonoow_license_expiry_days', absint($_POST['_woonoow_license_expiry_days']));
|
||||
}
|
||||
}
|
||||
}
|
||||
95
includes/Modules/LicensingSettings.php
Normal file
95
includes/Modules/LicensingSettings.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/**
|
||||
* Licensing Module Settings
|
||||
*
|
||||
* @package WooNooW\Modules
|
||||
*/
|
||||
|
||||
namespace WooNooW\Modules;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
class LicensingSettings {
|
||||
|
||||
/**
|
||||
* Initialize the settings
|
||||
*/
|
||||
public static function init() {
|
||||
add_filter('woonoow/module_settings_schema', [__CLASS__, 'register_schema']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register licensing settings schema
|
||||
*/
|
||||
public static function register_schema($schemas) {
|
||||
$schemas['licensing'] = [
|
||||
'license_key_format' => [
|
||||
'type' => 'select',
|
||||
'label' => __('License Key Format', 'woonoow'),
|
||||
'description' => __('Format for generated license keys', 'woonoow'),
|
||||
'options' => [
|
||||
'uuid' => 'UUID (e.g., a1b2c3d4-e5f6-7890-abcd-ef1234567890)',
|
||||
'serial' => 'Serial (e.g., XXXX-XXXX-XXXX-XXXX)',
|
||||
'alphanumeric' => 'Alphanumeric (e.g., ABC123DEF456)',
|
||||
],
|
||||
'default' => 'serial',
|
||||
],
|
||||
'license_key_prefix' => [
|
||||
'type' => 'text',
|
||||
'label' => __('License Key Prefix', 'woonoow'),
|
||||
'description' => __('Optional prefix for license keys (e.g., PRO-, ENT-)', 'woonoow'),
|
||||
'placeholder' => 'e.g., PRO-',
|
||||
'default' => '',
|
||||
],
|
||||
'default_activation_limit' => [
|
||||
'type' => 'number',
|
||||
'label' => __('Default Activation Limit', 'woonoow'),
|
||||
'description' => __('Default max activations per license (0 = unlimited). Can be overridden per product.', 'woonoow'),
|
||||
'default' => 1,
|
||||
'min' => 0,
|
||||
'max' => 100,
|
||||
],
|
||||
'allow_deactivation' => [
|
||||
'type' => 'toggle',
|
||||
'label' => __('Allow Deactivation', 'woonoow'),
|
||||
'description' => __('Allow customers to deactivate their licenses to free up activation slots', 'woonoow'),
|
||||
'default' => true,
|
||||
],
|
||||
'license_expiry_enabled' => [
|
||||
'type' => 'toggle',
|
||||
'label' => __('Enable License Expiry', 'woonoow'),
|
||||
'description' => __('Licenses expire after a set period (for subscription-based products)', 'woonoow'),
|
||||
'default' => false,
|
||||
],
|
||||
'default_expiry_days' => [
|
||||
'type' => 'number',
|
||||
'label' => __('Default Expiry (Days)', 'woonoow'),
|
||||
'description' => __('Default license validity period in days (0 = never expires)', 'woonoow'),
|
||||
'default' => 365,
|
||||
'min' => 0,
|
||||
],
|
||||
'block_expired_activations' => [
|
||||
'type' => 'toggle',
|
||||
'label' => __('Block Expired Activations', 'woonoow'),
|
||||
'description' => __('Prevent new activations for expired licenses (deactivations still allowed)', 'woonoow'),
|
||||
'default' => true,
|
||||
],
|
||||
'send_expiry_reminder' => [
|
||||
'type' => 'toggle',
|
||||
'label' => __('Send Expiry Reminders', 'woonoow'),
|
||||
'description' => __('Send email reminders before license expires', 'woonoow'),
|
||||
'default' => true,
|
||||
],
|
||||
'expiry_reminder_days' => [
|
||||
'type' => 'number',
|
||||
'label' => __('Reminder Days Before Expiry', 'woonoow'),
|
||||
'description' => __('Send reminder this many days before expiry', 'woonoow'),
|
||||
'default' => 7,
|
||||
'min' => 1,
|
||||
'max' => 30,
|
||||
],
|
||||
];
|
||||
|
||||
return $schemas;
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ add_action('plugins_loaded', function () {
|
||||
// Initialize module settings
|
||||
WooNooW\Modules\NewsletterSettings::init();
|
||||
WooNooW\Modules\WishlistSettings::init();
|
||||
WooNooW\Modules\Licensing\LicensingModule::init();
|
||||
});
|
||||
|
||||
// Activation/Deactivation hooks
|
||||
|
||||
Reference in New Issue
Block a user