get_charset_collate(); $licenses_table = $wpdb->prefix . self::$table_name; $activations_table = $wpdb->prefix . self::$activations_table; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); // Create licenses table - dbDelta requires each CREATE TABLE to be called separately $sql_licenses = "CREATE TABLE $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;"; dbDelta($sql_licenses); // Create activations table $sql_activations = "CREATE TABLE $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;"; dbDelta($sql_activations); } /** * 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); } }