diff --git a/index.php b/index.php index a41ceab..33afa5d 100644 --- a/index.php +++ b/index.php @@ -82,6 +82,37 @@ if ($page === 'activate') { $error = $res['error']; } } + + if (isset($_POST['update_license'])) { + $id = (int)$_POST['id']; + $status = $_POST['status'] ?? null; + $owner = $_POST['owner'] ?? null; + $address = $_POST['address'] ?? null; + + $updateData = []; + if ($status !== null) $updateData['status'] = $status; + if ($owner !== null) $updateData['owner'] = $owner; + if ($address !== null) $updateData['address'] = $address; + + $res = LicenseService::updateLicense($id, $updateData); + if ($res['success']) { + $message = "License updated successfully!"; + } else { + $message = "Error: " . ($res['error'] ?? 'Unknown error'); + } + } + + if (isset($_POST['issue_license'])) { + $max = (int)($_POST['max_activations'] ?? 1); + $owner = $_POST['owner'] ?? null; + $address = $_POST['address'] ?? null; + $res = LicenseService::issueLicense($max, 'FLAT', $owner, $address); + if ($res['success']) { + $message = "New License Issued: " . $res['license_key']; + } else { + $message = "Error: " . ($res['error'] ?? 'Unknown error'); + } + } ?> @@ -1856,9 +1887,18 @@ if (isset($_POST['add_hr_department'])) { if (isset($_POST['add_cash_register'])) { $name = $_POST['name'] ?? ''; if ($name) { - $stmt = db()->prepare("INSERT INTO cash_registers (name) VALUES (?)"); - $stmt->execute([$name]); - $message = "Cash Register added successfully!"; + // Check license limit + $allowed = LicenseService::getAllowedActivations(); + $stmt = db()->query("SELECT COUNT(*) FROM cash_registers"); + $current_count = (int)$stmt->fetchColumn(); + + if ($current_count >= $allowed) { + $message = "Error: Activation Limit Reached. Your license only allows $allowed register(s)."; + } else { + $stmt = db()->prepare("INSERT INTO cash_registers (name) VALUES (?)"); + $stmt->execute([$name]); + $message = "Cash Register added successfully!"; + } } } if (isset($_POST['edit_cash_register'])) { @@ -1969,6 +2009,7 @@ $page_permissions = [ 'logs' => 'logs_view', 'cash_registers' => 'cash_registers_view', 'register_sessions' => 'register_sessions_view', + 'licenses' => 'licenses_view', ]; if (isset($page_permissions[$page]) && !can($page_permissions[$page])) { @@ -2060,13 +2101,12 @@ $permission_groups = [ 'users' => 'Users', 'cash_registers' => 'Cash Registers', 'register_sessions' => 'Register Sessions', - 'scale_devices' => 'Scale Devices', - 'customer_display_settings' => 'Customer Display', - 'backups' => 'Backups', + 'licenses' => 'Licenses', 'logs' => 'System Logs' ] ]; + if ($page === 'export') { $type = $_GET['type'] ?? 'sales'; $filename = $type . "_export_" . date('Y-m-d') . ".csv"; @@ -2742,6 +2782,11 @@ switch ($page) { $data['cash_registers'] = db()->query("SELECT * FROM cash_registers WHERE status = 'active'")->fetchAll(); $data['users'] = db()->query("SELECT id, username FROM users ORDER BY username ASC")->fetchAll(); break; + case 'licenses': + $res = LicenseService::listLicenses(); + $data['licenses'] = $res['success'] ? $res['data'] : []; + $data['license_error'] = $res['success'] ? '' : ($res['error'] ?? 'Failed to fetch licenses.'); + break; default: $data['customers'] = db()->query("SELECT * FROM customers ORDER BY id DESC LIMIT 5")->fetchAll(); $data['stats'] = [ @@ -3074,6 +3119,9 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; + + + @@ -8139,6 +8187,17 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
Cash Registers Management

Define your shop counters and registers.

+
+ + + + License Limit: / + Registers + +
+ + +
+
Server Response:
+

+                
+ + +
+ Upload this file to the same directory as index.php +
+ + + + + + + + diff --git a/license_manager/config.php b/license_manager/config.php new file mode 100644 index 0000000..e606274 --- /dev/null +++ b/license_manager/config.php @@ -0,0 +1,22 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + ]); + } + return $pdo; +} diff --git a/license_manager/database.sql b/license_manager/database.sql new file mode 100644 index 0000000..37a8578 --- /dev/null +++ b/license_manager/database.sql @@ -0,0 +1,26 @@ +-- SQL for the License Manager Database +-- Create a new database called 'license_manager_db' and run this script. + +CREATE TABLE IF NOT EXISTS licenses ( + id INT AUTO_INCREMENT PRIMARY KEY, + license_key VARCHAR(255) UNIQUE NOT NULL, + max_activations INT DEFAULT 1, + status ENUM('active', 'suspended', 'expired') DEFAULT 'active', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS activations ( + id INT AUTO_INCREMENT PRIMARY KEY, + license_id INT NOT NULL, + fingerprint VARCHAR(255) NOT NULL, + domain VARCHAR(255), + product VARCHAR(255), + activated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (license_id) REFERENCES licenses(id) ON DELETE CASCADE, + UNIQUE KEY (license_id, fingerprint) +) ENGINE=InnoDB; + +-- Seed some test data +INSERT INTO licenses (license_key, max_activations) VALUES ('FLAT-8822-1192-3301', 1); +INSERT INTO licenses (license_key, max_activations) VALUES ('FLAT-TEST-KEY-0001', 5); +INSERT INTO licenses (license_key, max_activations) VALUES ('FLAT-DEV-UNLIMITED', 999); diff --git a/license_manager/index.php b/license_manager/index.php new file mode 100644 index 0000000..f201f20 --- /dev/null +++ b/license_manager/index.php @@ -0,0 +1,247 @@ + false, 'error' => 'Database connection failed.']); + exit; +} + +if ($endpoint === 'activate') { + $key = strtoupper(trim($input['license_key'] ?? '')); + $fingerprint = $input['fingerprint'] ?? ''; + $domain = $input['domain'] ?? ''; + $product = $input['product'] ?? ''; + + if (empty($key) || empty($fingerprint)) { + echo json_encode(['success' => false, 'error' => 'Missing required parameters.']); + exit; + } + + // 1. Find License + $stmt = $pdo->prepare("SELECT * FROM licenses WHERE license_key = ? LIMIT 1"); + $stmt->execute([$key]); + $license = $stmt->fetch(); + + if (!$license) { + echo json_encode(['success' => false, 'error' => 'Invalid license key.']); + exit; + } + + if ($license['status'] !== 'active') { + echo json_encode(['success' => false, 'error' => 'License is ' . $license['status'] . '.']); + exit; + } + + // 2. Check current activations + $stmt = $pdo->prepare("SELECT COUNT(*) FROM activations WHERE license_id = ?"); + $stmt->execute([$license['id']]); + $current_activations = $stmt->fetchColumn(); + + // 3. Check if this machine is already activated + $stmt = $pdo->prepare("SELECT * FROM activations WHERE license_id = ? AND fingerprint = ?"); + $stmt->execute([$license['id'], $fingerprint]); + $existing = $stmt->fetch(); + + if (!$existing) { + if ($current_activations >= $license['max_activations']) { + echo json_encode(['success' => false, 'error' => 'Maximum activation limit reached.']); + exit; + } + + // Record new activation + $stmt = $pdo->prepare("INSERT INTO activations (license_id, fingerprint, domain, product) VALUES (?, ?, ?, ?)"); + $stmt->execute([$license['id'], $fingerprint, $domain, $product]); + } + + // Success: Return signed token + $token = hash_hmac('sha256', $key . $fingerprint, SERVER_SECRET); + echo json_encode([ + 'success' => true, + 'activation_token' => $token + ]); + exit; +} + +if ($endpoint === 'verify') { + $key = strtoupper(trim($input['license_key'] ?? '')); + $fingerprint = $input['fingerprint'] ?? ''; + $token = $input['token'] ?? ''; + + // Simple validation: re-calculate token and check DB status + $expected_token = hash_hmac('sha256', $key . $fingerprint, SERVER_SECRET); + + if ($token !== $expected_token) { + echo json_encode(['success' => false, 'error' => 'Invalid activation token.']); + exit; + } + + $stmt = $pdo->prepare("SELECT status FROM licenses WHERE license_key = ?"); + $stmt->execute([$key]); + $status = $stmt->fetchColumn(); + + if ($status === 'active') { + echo json_encode(['success' => true]); + } else { + echo json_encode(['success' => false, 'error' => 'License is no longer active.']); + } + exit; +} + +if ($endpoint === 'deactivate') { + $key = strtoupper(trim($input['license_key'] ?? '')); + $fingerprint = $input['fingerprint'] ?? ''; + + // Deactivation should ideally require a token or signature, but for simplicity: + // We check if the license exists and the activation matches + + // Find License ID + $stmt = $pdo->prepare("SELECT id FROM licenses WHERE license_key = ?"); + $stmt->execute([$key]); + $licenseId = $stmt->fetchColumn(); + + if (!$licenseId) { + echo json_encode(['success' => false, 'error' => 'Invalid license key.']); + exit; + } + + // Delete Activation + $stmt = $pdo->prepare("DELETE FROM activations WHERE license_id = ? AND fingerprint = ?"); + $stmt->execute([$licenseId, $fingerprint]); + + if ($stmt->rowCount() > 0) { + echo json_encode(['success' => true]); + } else { + echo json_encode(['success' => false, 'error' => 'Activation not found.']); + } + exit; +} + +if ($endpoint === 'issue') { + $secret = $input['secret'] ?? ''; + + // Basic security check using the config constant + if ($secret !== SERVER_SECRET) { + echo json_encode(['success' => false, 'error' => 'Unauthorized. Invalid secret.']); + exit; + } + + $max_activations = (int)($input['max_activations'] ?? 1); + $prefix = strtoupper(trim($input['prefix'] ?? 'FLAT')); + $owner = $input['owner'] ?? null; + $address = $input['address'] ?? null; + + // Generate a formatted key: PREFIX-XXXX-XXXX + $key = $prefix . '-' . bin2hex(random_bytes(2)) . '-' . bin2hex(random_bytes(2)); + $key = strtoupper($key); + + try { + $stmt = $pdo->prepare("INSERT INTO licenses (license_key, max_activations, owner, address) VALUES (?, ?, ?, ?)"); + $stmt->execute([$key, $max_activations, $owner, $address]); + + echo json_encode([ + 'success' => true, + 'license_key' => $key, + 'max_activations' => $max_activations, + 'owner' => $owner, + 'address' => $address + ]); + } catch (Exception $e) { + echo json_encode(['success' => false, 'error' => 'Failed to generate license.']); + } + exit; +} + +if ($endpoint === 'list') { + // Basic security check (Optional: You can use the secret here too) + // For now, it fetches all licenses with their activation counts + try { + $stmt = $pdo->prepare(" + SELECT l.*, + (SELECT COUNT(*) FROM activations a WHERE a.license_id = l.id) as activations_count, + (l.status = 'active') as is_active + FROM licenses l + ORDER BY l.created_at DESC + "); + $stmt->execute(); + $licenses = $stmt->fetchAll(); + + echo json_encode([ + 'success' => true, + 'data' => $licenses + ]); + } catch (Exception $e) { + echo json_encode(['success' => false, 'error' => 'Failed to fetch licenses: ' . $e->getMessage()]); + } + exit; +} + +if ($endpoint === 'update') { + $secret = $input['secret'] ?? ''; + if ($secret !== SERVER_SECRET) { + echo json_encode(['success' => false, 'error' => 'Unauthorized.']); + exit; + } + + $id = (int)($input['id'] ?? 0); + $status = $input['status'] ?? null; + $owner = $input['owner'] ?? null; + $address = $input['address'] ?? null; + + if (!$id) { + echo json_encode(['success' => false, 'error' => 'ID is required.']); + exit; + } + + try { + $fields = []; + $params = []; + if ($status !== null) { $fields[] = "status = ?"; $params[] = $status; } + if ($owner !== null) { $fields[] = "owner = ?"; $params[] = $owner; } + if ($address !== null) { $fields[] = "address = ?"; $params[] = $address; } + + if (empty($fields)) { + echo json_encode(['success' => false, 'error' => 'No fields to update.']); + exit; + } + + $params[] = $id; + $sql = "UPDATE licenses SET " . implode(', ', $fields) . " WHERE id = ?"; + $stmt = $pdo->prepare($sql); + $stmt->execute($params); + + echo json_encode(['success' => true]); + } catch (Exception $e) { + echo json_encode(['success' => false, 'error' => 'Update failed: ' . $e->getMessage()]); + } + exit; +} + +echo json_encode(['success' => false, 'error' => 'Invalid endpoint.']); diff --git a/post_debug.log b/post_debug.log index 30a873a..02ae818 100644 --- a/post_debug.log +++ b/post_debug.log @@ -165,3 +165,6 @@ 2026-02-20 12:39:30 - POST: {"supplier_id":"6","lpo_date":"2026-02-20","delivery_date":"","terms_conditions":"","item_ids":["2"],"quantities":["1"],"prices":["0.150"],"add_lpo":""} 2026-02-20 12:40:43 - POST: {"supplier_id":"7","lpo_date":"2026-02-20","delivery_date":"","terms_conditions":"","item_ids":["2"],"quantities":["1"],"prices":["0.150"],"add_lpo":""} 2026-02-20 12:40:54 - POST: {"supplier_id":"7","lpo_date":"2026-02-20","delivery_date":"","terms_conditions":"","item_ids":["2"],"quantities":["1"],"prices":["0.150"],"add_lpo":""} +2026-02-20 13:31:46 - POST: {"action":"save_pos_transaction","customer_id":"7","payments":"[{\"method\":\"credit\",\"amount\":2.3}]","total_amount":"2.3","tax_amount":"0","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":1,\"qty\":4,\"price\":0.45,\"vat_rate\":0,\"vat_amount\":0},{\"id\":3,\"qty\":2,\"price\":0.25,\"vat_rate\":0,\"vat_amount\":0}]"} +2026-02-20 13:33:04 - POST: {"supplier_id":"5","lpo_date":"2026-02-20","delivery_date":"","terms_conditions":"","item_ids":["2"],"quantities":["12"],"prices":["0.150"],"add_lpo":""} +2026-02-20 13:33:26 - POST: {"convert_lpo_to_purchase":"","lpo_id":"22"} diff --git a/search_debug.log b/search_debug.log index 24e5eab..cee7fec 100644 --- a/search_debug.log +++ b/search_debug.log @@ -12,3 +12,4 @@ 2026-02-20 11:34:38 - search_items call: q=on 2026-02-20 12:40:40 - search_items call: q=on 2026-02-20 12:40:40 - search_items call: q=oni +2026-02-20 13:32:55 - search_items call: q=on