diff --git a/includes/Api/LicensesController.php b/includes/Api/LicensesController.php new file mode 100644 index 0000000..523052c --- /dev/null +++ b/includes/Api/LicensesController.php @@ -0,0 +1,291 @@ + 'GET', + 'callback' => [__CLASS__, 'get_licenses'], + 'permission_callback' => function() { + return current_user_can('manage_woocommerce'); + }, + ]); + + register_rest_route('woonoow/v1', '/licenses/(?P\d+)', [ + 'methods' => 'GET', + 'callback' => [__CLASS__, 'get_license'], + 'permission_callback' => function() { + return current_user_can('manage_woocommerce'); + }, + ]); + + register_rest_route('woonoow/v1', '/licenses/(?P\d+)', [ + 'methods' => 'DELETE', + 'callback' => [__CLASS__, 'revoke_license'], + 'permission_callback' => function() { + return current_user_can('manage_woocommerce'); + }, + ]); + + register_rest_route('woonoow/v1', '/licenses/(?P\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\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; + } +} diff --git a/includes/Api/Routes.php b/includes/Api/Routes.php index 7321a73..b19f879 100644 --- a/includes/Api/Routes.php +++ b/includes/Api/Routes.php @@ -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(); diff --git a/includes/Core/ModuleRegistry.php b/includes/Core/ModuleRegistry.php index 02dcb73..ad2e226 100644 --- a/includes/Core/ModuleRegistry.php +++ b/includes/Core/ModuleRegistry.php @@ -82,6 +82,7 @@ class ModuleRegistry { 'category' => 'products', 'icon' => 'key', 'default_enabled' => false, + 'has_settings' => true, 'features' => [ __('License key generation', 'woonoow'), __('Activation management', 'woonoow'), diff --git a/includes/Modules/Licensing/LicenseManager.php b/includes/Modules/Licensing/LicenseManager.php new file mode 100644 index 0000000..cb9d238 --- /dev/null +++ b/includes/Modules/Licensing/LicenseManager.php @@ -0,0 +1,502 @@ +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); + } +} diff --git a/includes/Modules/Licensing/LicensingModule.php b/includes/Modules/Licensing/LicensingModule.php new file mode 100644 index 0000000..b7e32c1 --- /dev/null +++ b/includes/Modules/Licensing/LicensingModule.php @@ -0,0 +1,111 @@ +'; + + 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 ''; + } + + /** + * 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'])); + } + } +} diff --git a/includes/Modules/LicensingSettings.php b/includes/Modules/LicensingSettings.php new file mode 100644 index 0000000..46b2b68 --- /dev/null +++ b/includes/Modules/LicensingSettings.php @@ -0,0 +1,95 @@ + [ + '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; + } +} diff --git a/woonoow.php b/woonoow.php index 4471c31..b89e1b0 100644 --- a/woonoow.php +++ b/woonoow.php @@ -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