Port
@@ -392,8 +393,16 @@
+
+ Advanced (optional) — Unix socket path
+
+
Socket Path
+
+
If set, host/port are ignored. Common aaPanel paths: /tmp/mysql.sock or /www/server/mysql/mysql.sock.
+
+
← Back
@@ -653,12 +662,14 @@ function renderChecks(containerId, checks) {
btn.disabled = true;
btn.innerHTML = '
Testing...';
+ const socketEl = document.getElementById('dbSocket');
dbCredentials = {
- db_host: document.getElementById('dbHost').value,
+ db_host: document.getElementById('dbHost').value.trim() || '127.0.0.1',
db_port: document.getElementById('dbPort').value,
db_user: document.getElementById('dbUser').value,
db_pass: document.getElementById('dbPass').value,
db_name: document.getElementById('dbName').value,
+ db_socket: socketEl ? socketEl.value.trim() : '',
};
const data = await post('test_db', dbCredentials);
From 6903cc073eb65cba5b5bdd9b8c626ad5433ed02e Mon Sep 17 00:00:00 2001
From: Ayoub Mohamed Samir <7agtyadmin@gmail.com>
Date: Thu, 7 May 2026 07:00:52 +0300
Subject: [PATCH 07/15] P0: Joomla-grade installer hardening for arbitrary
Linux panels (#19)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Phase 0 of the multi-panel compatibility plan. No table-prefix work yet
(that's P1). All changes local to install/ajax.php + install/index.php.
Hardening:
1. Async per-migration runner. handleInstallDb() split into:
- install_db_init → returns ordered file list with applied flags
- install_db_step → applies ONE migration file (browser drives loop)
- install_db_all → legacy single-shot fast path
Browser pumps the step loop, bypassing max_execution_time caps that
panels like aaPanel/Plesk/cPanel enforce (often 30-60s).
2. Per-statement SQL splitter (installerSplitSql) respects backticks,
single/double-quoted strings, line comments (-- and #), and block
comments. Strips DELIMITER + outer BEGIN/COMMIT wrappers. Lets the
runner survive PDO buffer caps and report progress accurately.
3. set_time_limit(0) + ignore_user_abort(true) at top — best-effort.
4. Preflight expansions:
- open_basedir detection — flags if app root outside allowed paths
- disable_functions audit — fails if mkdir/chmod/file_put_contents/
unlink/rmdir/fopen blocked
- Live mkdir+write+read+unlink probe under uploads/
- parent_writable flag returned
- php_version_full echoed for support tickets
5. Charset auto-fallback: SELECT VERSION() on first connect. MariaDB
<5.5.3 or MySQL <5.7 → utf8mb3 (legacy 'utf8'). Persisted in session
and surfaced to UI via dbCharset selector.
6. CREATE DATABASE skip-toggle: new step-2 checkbox + 1044/1142 error
handler returns suggest_skip_create:true so JS can show "Tick & retry"
button. Plesk/CyberPanel/ISPConfig users no longer hit a dead end.
7. Reverse-proxy IP hardening (getClientIp): only honor X-Forwarded-For
/ X-Real-IP / Client-IP when REMOTE_ADDR is in private/loopback
range. Closes the spoofable trusted-network 2FA-bypass surface.
8. Auto-unlock recovery: install.lock + admin_users empty/missing →
silent unlock. Inlined in install/index.php (avoids dragging ajax.php's
JSON header into HTML) and mirrored as installerCheckIncompleteState()
in ajax.php for runtime symmetry. Logged to install/install.log.
9. Unix-socket auto-detect: handleDetectSocket probes /tmp/mysql.sock,
/var/run/mysqld/mysqld.sock, /var/lib/mysql/mysql.sock,
/www/server/mysql/mysql.sock, /var/run/mariadb/mariadb.sock,
/usr/local/mysql/mysql.sock + 2 more. UI Detect button auto-fills.
10. handleHealth post-install probe: SELECT 1 + SHOW TABLES for the
five canonical KeyGate tables + admin_users count. Useful for step
6 link and external monitoring during shared-host installs.
11. installerBuildDsn() now accepts $charset; getInstallerPdo() pulls
it from session. installerFriendlyDbError() adds 1044/1142 hints
and 1045/no-password specific messaging.
12. installerLog() helper: append-only audit trail at install/install.log.
UI changes (install/index.php):
- Step 2: skip-create-DB checkbox, charset selector, prefix input
(P1-ready, hidden behind Advanced section), Detect socket button.
- Step 3: replaced single-shot runMigrations with init+step loop;
per-row spinner; per-row pass/skip/error with file name + message;
stops on first hard error so user reads it.
- Failed test_db with suggest_skip_create renders inline retry button.
Co-authored-by: ChesnoTech <263363000+ChesnoTech@users.noreply.github.com>
---
FINAL_PRODUCTION_SYSTEM/install/ajax.php | 770 ++++++++++++++++++----
FINAL_PRODUCTION_SYSTEM/install/index.php | 208 ++++--
2 files changed, 825 insertions(+), 153 deletions(-)
diff --git a/FINAL_PRODUCTION_SYSTEM/install/ajax.php b/FINAL_PRODUCTION_SYSTEM/install/ajax.php
index c5930bb..fedc2a5 100644
--- a/FINAL_PRODUCTION_SYSTEM/install/ajax.php
+++ b/FINAL_PRODUCTION_SYSTEM/install/ajax.php
@@ -9,14 +9,22 @@
header('Content-Type: application/json');
header('X-Content-Type-Options: nosniff');
-// Block if already installed
+// Best-effort: defang shared-host timeouts. Some panels ignore these silently.
+@set_time_limit(0);
+@ignore_user_abort(true);
+
+session_start();
+
+// Auto-unlock recovery: if install.lock exists but the install never finished
+// (no admin_users yet, or table missing) — clear it and continue. Logged.
+installerCheckIncompleteState();
+
+// Block if (still) installed after auto-unlock check
if (file_exists(__DIR__ . '/../install.lock')) {
die(json_encode(['success' => false, 'message' => 'System is already installed.']));
}
-session_start();
-
-$action = $_POST['action'] ?? '';
+$action = $_POST['action'] ?? $_GET['action'] ?? '';
switch ($action) {
case 'preflight':
@@ -25,8 +33,15 @@
case 'test_db':
handleTestDb();
break;
- case 'install_db':
- handleInstallDb();
+ case 'install_db': // legacy single-shot; falls through to fast-path
+ case 'install_db_all':
+ handleInstallDbAll();
+ break;
+ case 'install_db_init':
+ handleInstallDbInit();
+ break;
+ case 'install_db_step':
+ handleInstallDbStep();
break;
case 'create_admin':
handleCreateAdmin();
@@ -34,6 +49,12 @@
case 'finalize':
handleFinalize();
break;
+ case 'detect_socket':
+ handleDetectSocket();
+ break;
+ case 'health':
+ handleHealth();
+ break;
default:
echo json_encode(['success' => false, 'message' => 'Unknown action']);
}
@@ -197,6 +218,64 @@ function handlePreflight() {
];
}
+ // ── PHP Sandbox / Restrictions (panel-specific blockers) ──
+ $openBasedir = ini_get('open_basedir');
+ if (!empty($openBasedir)) {
+ $allowedPaths = preg_split('/[:;]/', $openBasedir);
+ // Verify the install root is within allowed paths
+ $insideBasedir = false;
+ foreach ($allowedPaths as $p) {
+ if ($p !== '' && strpos($baseDir, rtrim($p, '/')) === 0) {
+ $insideBasedir = true;
+ break;
+ }
+ }
+ $result['settings'][] = [
+ 'label' => 'open_basedir',
+ 'value' => $insideBasedir ? 'OK (' . count($allowedPaths) . ' path(s))' : 'Restricted',
+ 'status' => $insideBasedir ? 'pass' : 'fail',
+ 'hint' => $insideBasedir ? '' : "Add app root '{$baseDir}' to open_basedir in php.ini.",
+ ];
+ } else {
+ $result['settings'][] = [
+ 'label' => 'open_basedir',
+ 'value' => 'Not set (unrestricted)',
+ 'status' => 'pass',
+ ];
+ }
+
+ $disabled = array_filter(array_map('trim', explode(',', (string) ini_get('disable_functions'))));
+ $criticalDisabled = array_intersect($disabled, ['mkdir', 'chmod', 'file_put_contents', 'rmdir', 'unlink', 'fopen']);
+ $result['settings'][] = [
+ 'label' => 'disable_functions',
+ 'value' => empty($disabled) ? 'None' : count($disabled) . ' function(s) blocked',
+ 'status' => empty($criticalDisabled) ? 'pass' : 'fail',
+ 'hint' => empty($criticalDisabled) ? '' : 'Critical functions blocked: ' . implode(', ', $criticalDisabled) . '. Backups/upgrades will degrade.',
+ ];
+
+ // ── Live mkdir / write / unlink probe ──
+ $probeDir = $baseDir . '/uploads/_keygate_probe_' . uniqid();
+ $probeFile = $probeDir . '/probe.txt';
+ $probeOk = @mkdir($probeDir, 0755, true);
+ if ($probeOk) {
+ $writeOk = @file_put_contents($probeFile, 'ok') !== false;
+ $readOk = $writeOk && @file_get_contents($probeFile) === 'ok';
+ @unlink($probeFile);
+ @rmdir($probeDir);
+ } else {
+ $writeOk = $readOk = false;
+ }
+ $result['directories'][] = [
+ 'label' => 'Filesystem write probe',
+ 'value' => ($probeOk && $writeOk && $readOk) ? 'OK' : 'Failed',
+ 'status' => ($probeOk && $writeOk && $readOk) ? 'pass' : 'fail',
+ 'hint' => ($probeOk && $writeOk && $readOk) ? '' : 'Cannot create+write+read in uploads/. Check chmod 755 and disable_functions.',
+ ];
+
+ // ── Parent-dir writable flag (used by step 6 to surface manual workflow) ──
+ $result['parent_writable'] = is_writable($baseDir);
+ $result['php_version_full'] = PHP_VERSION;
+
echo json_encode($result);
}
@@ -204,18 +283,34 @@ function handlePreflight() {
// Step 2: Test Database Connection
// ═══════════════════════════════════════════════════════════════
function handleTestDb() {
- $host = trim($_POST['db_host'] ?? '127.0.0.1');
- $port = (int)($_POST['db_port'] ?? 3306);
- $user = $_POST['db_user'] ?? '';
- $pass = $_POST['db_pass'] ?? '';
- $name = $_POST['db_name'] ?? 'oem_activation';
- $socket = trim($_POST['db_socket'] ?? '');
+ $host = trim($_POST['db_host'] ?? '127.0.0.1');
+ $port = (int)($_POST['db_port'] ?? 3306);
+ $user = $_POST['db_user'] ?? '';
+ $pass = $_POST['db_pass'] ?? '';
+ $name = $_POST['db_name'] ?? 'oem_activation';
+ $socket = trim($_POST['db_socket'] ?? '');
+ $skipCreate = !empty($_POST['skip_create_db']);
+ $rawPrefix = trim((string)($_POST['db_prefix'] ?? ''));
+ $charset = 'utf8mb4'; // Default; may downgrade to utf8mb3 below.
if (empty($user)) {
echo json_encode(['success' => false, 'message' => 'Username is required']);
return;
}
+ // Validate prefix: empty OR `^[a-z][a-z0-9_]{0,9}$`. Deny-list reserved.
+ if ($rawPrefix !== '' && !preg_match('/^[a-z][a-z0-9_]{0,9}$/', $rawPrefix)) {
+ echo json_encode(['success' => false, 'message' => 'Prefix must be 1–10 chars, lowercase letters/digits/_, start with a letter.']);
+ return;
+ }
+ $denyList = ['mysql_', 'sys_', 'information_', 'performance_'];
+ foreach ($denyList as $denied) {
+ if ($rawPrefix !== '' && strpos($rawPrefix, $denied) === 0) {
+ echo json_encode(['success' => false, 'message' => "Prefix '{$rawPrefix}' starts with reserved name. Pick another."]);
+ return;
+ }
+ }
+
// aaPanel / cPanel hint: many panels bind MariaDB to TCP only.
// Coerce 'localhost' → '127.0.0.1' to avoid PDO Unix-socket lookup
// unless an explicit socket path is supplied.
@@ -225,7 +320,7 @@ function handleTestDb() {
try {
// First: connect without database to check server
- $dsn = installerBuildDsn($host, $port, '', $socket);
+ $dsn = installerBuildDsn($host, $port, '', $socket, $charset);
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_TIMEOUT => 10,
@@ -236,21 +331,50 @@ function handleTestDb() {
$isMariaDB = stripos($version, 'MariaDB') !== false;
$serverType = $isMariaDB ? 'MariaDB' : 'MySQL';
+ // Charset auto-fallback: MySQL < 5.7 or MariaDB < 5.5.3 → utf8mb3 (legacy 'utf8')
+ $numericVer = preg_replace('/[^0-9.].*/', '', $version);
+ if ($isMariaDB) {
+ if (version_compare($numericVer, '5.5.3', '<')) $charset = 'utf8';
+ } else {
+ if (version_compare($numericVer, '5.7.0', '<')) $charset = 'utf8';
+ }
+
// Check if database exists
$stmt = $pdo->prepare("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?");
$stmt->execute([$name]);
$dbExists = (bool)$stmt->fetch();
+ $collation = $charset . '_unicode_ci';
if (!$dbExists) {
+ if ($skipCreate) {
+ echo json_encode([
+ 'success' => false,
+ 'message' => "Database '{$name}' does not exist on the server. Create it in your control panel (aaPanel: Databases → Add database) and uncheck 'skip CREATE' OR pre-create it then retry.",
+ ]);
+ return;
+ }
// Try to create it
- $pdo->exec("CREATE DATABASE `{$name}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
- $msg = "{$serverType} {$version} — Database '{$name}' created successfully.";
+ try {
+ $pdo->exec("CREATE DATABASE `{$name}` CHARACTER SET {$charset} COLLATE {$collation}");
+ $msg = "{$serverType} {$version} — Database '{$name}' created successfully (charset={$charset}).";
+ } catch (PDOException $createErr) {
+ $code = (int) $createErr->getCode();
+ if (in_array($code, [1044, 1142]) || stripos($createErr->getMessage(), 'denied') !== false) {
+ echo json_encode([
+ 'success' => false,
+ 'message' => "Your DB user lacks CREATE DATABASE privilege (common on Plesk/CyberPanel). Pre-create the DB '{$name}' in your control panel, then retick 'Database already exists — skip CREATE' and retry.",
+ 'suggest_skip_create' => true,
+ ]);
+ return;
+ }
+ throw $createErr;
+ }
} else {
- $msg = "{$serverType} {$version} — Database '{$name}' exists.";
+ $msg = "{$serverType} {$version} — Database '{$name}' exists (charset will be {$charset}).";
}
// Verify we can connect to the database
- $dsn2 = installerBuildDsn($host, $port, $name, $socket);
+ $dsn2 = installerBuildDsn($host, $port, $name, $socket, $charset);
$pdo2 = new PDO($dsn2, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_TIMEOUT => 10,
@@ -272,9 +396,25 @@ function handleTestDb() {
}
// Store in session for later steps
- $_SESSION['install_db'] = compact('host', 'port', 'user', 'pass', 'name', 'socket');
+ $_SESSION['install_db'] = [
+ 'host' => $host,
+ 'port' => $port,
+ 'user' => $user,
+ 'pass' => $pass,
+ 'name' => $name,
+ 'socket' => $socket,
+ 'prefix' => $rawPrefix,
+ 'charset' => $charset,
+ ];
- echo json_encode(['success' => true, 'message' => $msg]);
+ echo json_encode([
+ 'success' => true,
+ 'message' => $msg,
+ 'charset' => $charset,
+ 'version' => $version,
+ 'serverType'=> $serverType,
+ 'prefix' => $rawPrefix,
+ ]);
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => installerFriendlyDbError($e, $host, $port, $socket)]);
@@ -282,20 +422,15 @@ function handleTestDb() {
}
// ═══════════════════════════════════════════════════════════════
-// Step 3: Install Database (Run Migrations)
+// Step 3: Install Database — Async (init/step) + Fast-path (all)
// ═══════════════════════════════════════════════════════════════
-function handleInstallDb() {
- $pdo = getInstallerPdo();
- if (!$pdo) return;
-
- $sqlDir = realpath(__DIR__ . '/../database');
- if (!$sqlDir) {
- echo json_encode(['success' => false, 'message' => 'Database SQL directory not found']);
- return;
- }
- // Migration order — matches 00-init.sh
- $migrations = [
+/**
+ * Canonical migration order. Mirrors 00-init.sh exactly.
+ * Returns [filename, version] tuples.
+ */
+function installerMigrationList(): array {
+ return [
['schema_versions_migration.sql', 0],
['install.sql', 1],
['database_concurrency_indexes.sql', 2],
@@ -317,80 +452,206 @@ function handleInstallDb() {
['missing_drivers_migration.sql', 18],
['unallocated_space_migration.sql', 19],
];
+}
- $results = [];
+/**
+ * Step-3 INIT: ensure schema_versions exists, return ordered file list with
+ * applied-status flags. Browser drives the per-file loop from here.
+ */
+function handleInstallDbInit() {
+ $pdo = getInstallerPdo();
+ if (!$pdo) return;
+
+ $sqlDir = realpath(__DIR__ . '/../database');
+ if (!$sqlDir) {
+ echo json_encode(['success' => false, 'message' => 'Database SQL directory not found']);
+ return;
+ }
- // Step 0: ensure schema_versions exists (run unconditionally)
+ // Bootstrap schema_versions first (the tracking table for all later migrations).
$schemaFile = $sqlDir . '/schema_versions_migration.sql';
if (file_exists($schemaFile)) {
try {
- $sql = file_get_contents($schemaFile);
- $pdo->exec($sql);
- $results[] = ['file' => 'schema_versions_migration.sql', 'status' => 'ok', 'message' => 'Tracking table ready'];
- } catch (PDOException $e) {
- // Table probably exists already
- $results[] = ['file' => 'schema_versions_migration.sql', 'status' => 'ok', 'message' => 'Tracking table exists'];
- }
+ $pdo->exec(file_get_contents($schemaFile));
+ } catch (PDOException $e) { /* probably already exists */ }
}
- // Run remaining migrations
- for ($i = 1; $i < count($migrations); $i++) {
- [$file, $version] = $migrations[$i];
+ $list = [];
+ foreach (installerMigrationList() as [$file, $version]) {
$filePath = $sqlDir . '/' . $file;
+ $exists = file_exists($filePath);
+ $applied = false;
+
+ if ($exists) {
+ try {
+ $stmt = $pdo->prepare("SELECT COUNT(*) AS c FROM schema_versions WHERE filename = ?");
+ $stmt->execute([$file]);
+ $applied = ((int)$stmt->fetchColumn()) > 0;
+ } catch (PDOException $e) { /* table missing → not applied */ }
+ }
+
+ $list[] = [
+ 'file' => $file,
+ 'version' => $version,
+ 'exists' => $exists,
+ 'applied' => $applied,
+ 'sha256' => $exists ? substr(hash_file('sha256', $filePath), 0, 16) : '',
+ ];
+ }
+
+ echo json_encode([
+ 'success' => true,
+ 'migrations' => $list,
+ 'total' => count($list),
+ ]);
+}
+
+/**
+ * Step-3 STEP: apply ONE migration file.
+ * Body: { file: 'install.sql', version: 1 }
+ */
+function handleInstallDbStep() {
+ @set_time_limit(0);
+ $pdo = getInstallerPdo();
+ if (!$pdo) return;
+
+ $file = $_POST['file'] ?? '';
+ $version = (int)($_POST['version'] ?? 0);
+
+ // Whitelist against the canonical list — no arbitrary file reads.
+ $allowed = array_column(installerMigrationList(), 0);
+ if (!in_array($file, $allowed, true)) {
+ echo json_encode(['success' => false, 'message' => "Migration '{$file}' not on the canonical list."]);
+ return;
+ }
+
+ $sqlDir = realpath(__DIR__ . '/../database');
+ $filePath = $sqlDir . '/' . $file;
+ if (!file_exists($filePath)) {
+ echo json_encode(['file' => $file, 'success' => true, 'status' => 'skipped', 'message' => 'File not found (skipped)']);
+ return;
+ }
+
+ // Skip if already applied
+ try {
+ $stmt = $pdo->prepare("SELECT COUNT(*) AS c FROM schema_versions WHERE filename = ?");
+ $stmt->execute([$file]);
+ if ((int)$stmt->fetchColumn() > 0) {
+ echo json_encode(['file' => $file, 'success' => true, 'status' => 'skipped', 'message' => 'Already applied']);
+ return;
+ }
+ } catch (PDOException $e) { /* table missing — proceed */ }
+
+ $sql = file_get_contents($filePath);
+ $result = installerRunSqlFile($pdo, $sql);
+
+ if ($result['ok']) {
+ try {
+ $checksum = hash('sha256', $sql);
+ $stmt = $pdo->prepare("INSERT IGNORE INTO schema_versions (version, filename, checksum) VALUES (?, ?, ?)");
+ $stmt->execute([$version, $file, $checksum]);
+ } catch (PDOException $e) { /* ignore */ }
+
+ echo json_encode([
+ 'file' => $file,
+ 'success' => true,
+ 'status' => 'ok',
+ 'message' => 'Applied (' . $result['stmts_run'] . ' statements)',
+ 'stmts_run' => $result['stmts_run'],
+ ]);
+ return;
+ }
+
+ // Tolerate "already exists" / "duplicate" — record as applied.
+ if (preg_match('/Duplicate|already exists|1060|1061|1050|1062/i', $result['error'])) {
+ try {
+ $checksum = hash('sha256', $sql);
+ $stmt = $pdo->prepare("INSERT IGNORE INTO schema_versions (version, filename, checksum) VALUES (?, ?, ?)");
+ $stmt->execute([$version, $file, $checksum]);
+ } catch (PDOException $e2) { /* ignore */ }
+
+ echo json_encode([
+ 'file' => $file,
+ 'success' => true,
+ 'status' => 'ok',
+ 'message' => 'Applied (some objects already existed)',
+ ]);
+ return;
+ }
+
+ echo json_encode([
+ 'file' => $file,
+ 'success' => false,
+ 'status' => 'error',
+ 'message' => $result['error'],
+ ]);
+}
+
+/**
+ * Step-3 ALL: legacy single-shot path (used by fast-path when host has
+ * generous max_execution_time AND no other risk flags).
+ */
+function handleInstallDbAll() {
+ @set_time_limit(0);
+ $pdo = getInstallerPdo();
+ if (!$pdo) return;
+
+ $sqlDir = realpath(__DIR__ . '/../database');
+ if (!$sqlDir) {
+ echo json_encode(['success' => false, 'message' => 'Database SQL directory not found']);
+ return;
+ }
+
+ // Bootstrap schema_versions
+ $schemaFile = $sqlDir . '/schema_versions_migration.sql';
+ if (file_exists($schemaFile)) {
+ try { $pdo->exec(file_get_contents($schemaFile)); } catch (PDOException $e) { /* ok */ }
+ }
+ $results = [];
+ foreach (installerMigrationList() as $i => [$file, $version]) {
+ if ($i === 0) {
+ $results[] = ['file' => $file, 'status' => 'ok', 'message' => 'Tracking table ready'];
+ continue;
+ }
+
+ $filePath = $sqlDir . '/' . $file;
if (!file_exists($filePath)) {
$results[] = ['file' => $file, 'status' => 'skipped', 'message' => 'File not found'];
continue;
}
- // Check if already applied
try {
- $stmt = $pdo->prepare("SELECT COUNT(*) AS cnt FROM schema_versions WHERE filename = ?");
+ $stmt = $pdo->prepare("SELECT COUNT(*) AS c FROM schema_versions WHERE filename = ?");
$stmt->execute([$file]);
- $row = $stmt->fetch(PDO::FETCH_ASSOC);
- if ($row && (int)$row['cnt'] > 0) {
+ if ((int)$stmt->fetchColumn() > 0) {
$results[] = ['file' => $file, 'status' => 'skipped', 'message' => 'Already applied'];
continue;
}
- } catch (PDOException $e) {
- // schema_versions might not exist yet
- }
-
- // Execute migration
- try {
- $sql = file_get_contents($filePath);
-
- // For multi-statement SQL, we need to use exec
- // Some files use DELIMITER which doesn't work with PDO — strip them
- $sql = preg_replace('/DELIMITER\s+[^\n]+/i', '', $sql);
+ } catch (PDOException $e) { /* ignore */ }
- $pdo->exec($sql);
-
- // Record in schema_versions
- $checksum = hash('sha256', $sql);
- $stmt = $pdo->prepare("INSERT IGNORE INTO schema_versions (version, filename, checksum) VALUES (?, ?, ?)");
- $stmt->execute([$version, $file, $checksum]);
+ $sql = file_get_contents($filePath);
+ $r = installerRunSqlFile($pdo, $sql);
- $results[] = ['file' => $file, 'status' => 'ok', 'message' => 'Applied successfully'];
- } catch (PDOException $e) {
- $errMsg = $e->getMessage();
- // Some errors are non-fatal (duplicate index, column already exists, etc.)
- if (preg_match('/Duplicate|already exists|1060|1061/i', $errMsg)) {
- // Record as applied anyway
- try {
- $checksum = hash('sha256', $sql);
- $stmt = $pdo->prepare("INSERT IGNORE INTO schema_versions (version, filename, checksum) VALUES (?, ?, ?)");
- $stmt->execute([$version, $file, $checksum]);
- } catch (PDOException $e2) { /* ignore */ }
- $results[] = ['file' => $file, 'status' => 'ok', 'message' => 'Applied (some objects already existed)'];
- } else {
- $results[] = ['file' => $file, 'status' => 'error', 'message' => $errMsg];
- // Don't stop — try remaining migrations
- }
+ if ($r['ok']) {
+ try {
+ $checksum = hash('sha256', $sql);
+ $stmt = $pdo->prepare("INSERT IGNORE INTO schema_versions (version, filename, checksum) VALUES (?, ?, ?)");
+ $stmt->execute([$version, $file, $checksum]);
+ } catch (PDOException $e) { /* ignore */ }
+ $results[] = ['file' => $file, 'status' => 'ok', 'message' => 'Applied (' . $r['stmts_run'] . ' statements)'];
+ } elseif (preg_match('/Duplicate|already exists|1060|1061|1050|1062/i', $r['error'])) {
+ try {
+ $checksum = hash('sha256', $sql);
+ $stmt = $pdo->prepare("INSERT IGNORE INTO schema_versions (version, filename, checksum) VALUES (?, ?, ?)");
+ $stmt->execute([$version, $file, $checksum]);
+ } catch (PDOException $e) { /* ignore */ }
+ $results[] = ['file' => $file, 'status' => 'ok', 'message' => 'Applied (some objects already existed)'];
+ } else {
+ $results[] = ['file' => $file, 'status' => 'error', 'message' => $r['error']];
}
}
- // Check if any critical errors
$errors = array_filter($results, fn($r) => $r['status'] === 'error');
$success = count($errors) === 0;
@@ -401,6 +662,120 @@ function handleInstallDb() {
]);
}
+/**
+ * Run a multi-statement SQL string statement-by-statement.
+ * Strips DELIMITER + outer BEGIN/COMMIT wrappers.
+ * Returns ['ok' => bool, 'stmts_run' => int, 'error' => string].
+ */
+function installerRunSqlFile(PDO $pdo, string $sql): array {
+ // Strip DELIMITER directives — PDO doesn't honor them.
+ $sql = preg_replace('/DELIMITER\s+[^\n]+/i', '', $sql);
+ // Strip outer BEGIN/COMMIT wrappers — DDL implicit-commits anyway and
+ // wrapper breaks per-statement progress reporting on some panels.
+ $sql = preg_replace('/^\s*(START\s+TRANSACTION|BEGIN)\s*;\s*$/im', '', $sql);
+ $sql = preg_replace('/^\s*COMMIT\s*;\s*$/im', '', $sql);
+
+ $stmts = installerSplitSql($sql);
+ $count = 0;
+ foreach ($stmts as $stmt) {
+ $stmt = trim($stmt);
+ if ($stmt === '') continue;
+ try {
+ $pdo->exec($stmt);
+ $count++;
+ } catch (PDOException $e) {
+ return ['ok' => false, 'stmts_run' => $count, 'error' => $e->getMessage()];
+ }
+ }
+ return ['ok' => true, 'stmts_run' => $count, 'error' => ''];
+}
+
+/**
+ * Split a multi-statement SQL string into individual statements.
+ * Respects backticks, single/double-quoted strings, line comments (-- ...),
+ * and block comments (/* ... *\/).
+ *
+ * Returns array of statements (semicolons stripped).
+ */
+function installerSplitSql(string $sql): array {
+ $stmts = [];
+ $buf = '';
+ $len = strlen($sql);
+ $i = 0;
+ $inSingle = $inDouble = $inBacktick = false;
+ $inLineComment = $inBlockComment = false;
+
+ while ($i < $len) {
+ $c = $sql[$i];
+ $next = $i + 1 < $len ? $sql[$i + 1] : '';
+
+ // Comments
+ if (!$inSingle && !$inDouble && !$inBacktick) {
+ if (!$inBlockComment && !$inLineComment && $c === '-' && $next === '-') {
+ $inLineComment = true;
+ $buf .= $c . $next;
+ $i += 2;
+ continue;
+ }
+ if (!$inBlockComment && !$inLineComment && $c === '#') {
+ $inLineComment = true;
+ $buf .= $c;
+ $i++;
+ continue;
+ }
+ if (!$inBlockComment && !$inLineComment && $c === '/' && $next === '*') {
+ $inBlockComment = true;
+ $buf .= $c . $next;
+ $i += 2;
+ continue;
+ }
+ if ($inLineComment && ($c === "\n" || $c === "\r")) {
+ $inLineComment = false;
+ $buf .= $c;
+ $i++;
+ continue;
+ }
+ if ($inBlockComment && $c === '*' && $next === '/') {
+ $inBlockComment = false;
+ $buf .= $c . $next;
+ $i += 2;
+ continue;
+ }
+ }
+
+ if ($inLineComment || $inBlockComment) {
+ $buf .= $c;
+ $i++;
+ continue;
+ }
+
+ // Quote tracking
+ if ($c === "'" && !$inDouble && !$inBacktick) {
+ $inSingle = !$inSingle;
+ } elseif ($c === '"' && !$inSingle && !$inBacktick) {
+ $inDouble = !$inDouble;
+ } elseif ($c === '`' && !$inSingle && !$inDouble) {
+ $inBacktick = !$inBacktick;
+ }
+
+ // Statement terminator
+ if ($c === ';' && !$inSingle && !$inDouble && !$inBacktick) {
+ $stmts[] = $buf;
+ $buf = '';
+ $i++;
+ continue;
+ }
+
+ $buf .= $c;
+ $i++;
+ }
+
+ if (trim($buf) !== '') {
+ $stmts[] = $buf;
+ }
+ return $stmts;
+}
+
// ═══════════════════════════════════════════════════════════════
// Step 4: Create Admin Account
// ═══════════════════════════════════════════════════════════════
@@ -615,12 +990,13 @@ function handleFinalize() {
* Create PDO connection from POST params or session
*/
function getInstallerPdo(): ?PDO {
- $host = trim($_POST['db_host'] ?? $_SESSION['install_db']['host'] ?? '127.0.0.1');
- $port = (int)($_POST['db_port'] ?? $_SESSION['install_db']['port'] ?? 3306);
- $user = $_POST['db_user'] ?? $_SESSION['install_db']['user'] ?? '';
- $pass = $_POST['db_pass'] ?? $_SESSION['install_db']['pass'] ?? '';
- $name = $_POST['db_name'] ?? $_SESSION['install_db']['name'] ?? '';
- $socket = trim($_POST['db_socket'] ?? $_SESSION['install_db']['socket'] ?? '');
+ $host = trim($_POST['db_host'] ?? $_SESSION['install_db']['host'] ?? '127.0.0.1');
+ $port = (int)($_POST['db_port'] ?? $_SESSION['install_db']['port'] ?? 3306);
+ $user = $_POST['db_user'] ?? $_SESSION['install_db']['user'] ?? '';
+ $pass = $_POST['db_pass'] ?? $_SESSION['install_db']['pass'] ?? '';
+ $name = $_POST['db_name'] ?? $_SESSION['install_db']['name'] ?? '';
+ $socket = trim($_POST['db_socket'] ?? $_SESSION['install_db']['socket'] ?? '');
+ $charset = $_SESSION['install_db']['charset'] ?? 'utf8mb4';
if (empty($user) || empty($name)) {
echo json_encode(['success' => false, 'message' => 'Database credentials missing. Go back to step 2.']);
@@ -633,7 +1009,7 @@ function getInstallerPdo(): ?PDO {
}
try {
- $dsn = installerBuildDsn($host, $port, $name, $socket);
+ $dsn = installerBuildDsn($host, $port, $name, $socket, $charset);
return new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
@@ -650,15 +1026,15 @@ function getInstallerPdo(): ?PDO {
* Build a PDO DSN that supports either TCP host:port or Unix socket path.
* When $socket is non-empty, host/port are ignored.
*/
-function installerBuildDsn(string $host, int $port, string $name, string $socket = ''): string {
+function installerBuildDsn(string $host, int $port, string $name, string $socket = '', string $charset = 'utf8mb4'): string {
if ($socket !== '') {
- $dsn = "mysql:unix_socket={$socket};charset=utf8mb4";
+ $dsn = "mysql:unix_socket={$socket};charset={$charset}";
} else {
- $dsn = "mysql:host={$host};port={$port};charset=utf8mb4";
+ $dsn = "mysql:host={$host};port={$port};charset={$charset}";
}
if ($name !== '') {
// Insert dbname between host/socket and charset to keep DSN ordered cleanly
- $dsn = str_replace(';charset=utf8mb4', ";dbname={$name};charset=utf8mb4", $dsn);
+ $dsn = str_replace(";charset={$charset}", ";dbname={$name};charset={$charset}", $dsn);
}
return $dsn;
}
@@ -669,28 +1045,36 @@ function installerBuildDsn(string $host, int $port, string $name, string $socket
*/
function installerFriendlyDbError(PDOException $e, string $host, int $port, string $socket): string {
$raw = $e->getMessage();
- $code = $e->getCode();
+ $code = (int)$e->getCode();
// Strip any DSN fragments that could leak host/port/user
$raw = preg_replace('/\bmysql:[^\s]+/', '[DSN]', $raw);
+ // ── 1044/1142: Lacks privilege (Plesk, CyberPanel, ISPConfig) ─
+ if ($code === 1044 || $code === 1142 || preg_match('/\b(1044|1142)\b/', $raw)) {
+ return "Your DB user lacks the required privilege. On Plesk/CyberPanel, the per-user account often cannot CREATE DATABASE or CREATE TABLE. Pre-create the database in your control panel UI and tick 'Database already exists — skip CREATE'.";
+ }
+ // ── 1045 with no password supplied ─
if (stripos($raw, 'Access denied') !== false) {
+ if (stripos($raw, 'using password: NO') !== false) {
+ return "Access denied. Server says no password was supplied. If your panel set a password (most do), enter it. If not, ensure user is allowed from 127.0.0.1.";
+ }
return 'Access denied. Check username and password. On aaPanel, ensure the user is allowed from this host (set Host = % or 127.0.0.1 in phpMyAdmin → User accounts).';
}
if (stripos($raw, 'Unknown database') !== false) {
- return "Database does not exist and could not be auto-created. Create it manually in aaPanel → Databases, then retry.";
+ return "Database does not exist. Pre-create it in your control panel (aaPanel/cPanel/Plesk → Databases) and tick 'Database already exists — skip CREATE'.";
}
if (stripos($raw, 'Unknown MySQL server host') !== false || stripos($raw, 'getaddrinfo') !== false) {
return "Cannot resolve host '{$host}'. Use 127.0.0.1 instead of localhost on most aaPanel installs.";
}
if (stripos($raw, 'Connection refused') !== false) {
- return "Cannot connect to {$host}:{$port}. MariaDB/MySQL service may not be running, or it's bound to a different port. In aaPanel: App Store → MySQL → check Service is started.";
+ return "Cannot connect to {$host}:{$port}. MariaDB/MySQL service may not be running, or it's bound to a different port. Check the service is started in your panel.";
}
if (stripos($raw, 'No such file or directory') !== false) {
// Classic Unix-socket failure
$hint = $socket !== ''
- ? "Socket path '{$socket}' does not exist."
- : "PDO tried the default Unix socket but it does not exist. Use 127.0.0.1 instead of localhost, or specify the socket path. Common aaPanel paths: /tmp/mysql.sock, /www/server/mysql/mysql.sock";
+ ? "Socket path '{$socket}' does not exist or is not readable by the web user."
+ : "PDO tried the default Unix socket but it does not exist. Use 127.0.0.1 instead of localhost, or click 'Detect socket' in advanced settings. Common paths: /tmp/mysql.sock, /var/run/mysqld/mysqld.sock, /www/server/mysql/mysql.sock";
return $hint;
}
if (stripos($raw, 'timed out') !== false || stripos($raw, 'timeout') !== false) {
@@ -717,25 +1101,31 @@ function returnBytes(string $val): int {
}
/**
- * Get the real client IP, accounting for proxies
+ * Get the real client IP, accounting for proxies.
+ *
+ * Security: only honor X-Forwarded-For / X-Real-IP / Client-IP headers when
+ * the immediate REMOTE_ADDR is in a private/loopback range — that's the
+ * only situation where a forward header is trustworthy (real proxy in front).
+ * Otherwise the client could spoof any IP.
*/
function getClientIp(): string {
- // Check common proxy headers (trusted only in installer context)
- $headers = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP'];
- foreach ($headers as $header) {
- if (!empty($_SERVER[$header])) {
- // X-Forwarded-For may contain multiple IPs — take the first (client)
- $ip = trim(explode(',', $_SERVER[$header])[0]);
- if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
- return $ip;
- }
- // Accept private range IPs too (common in LAN setups)
- if (filter_var($ip, FILTER_VALIDATE_IP)) {
- return $ip;
+ $remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '';
+ $remoteIsPrivate = $remoteAddr !== ''
+ && filter_var($remoteAddr, FILTER_VALIDATE_IP)
+ && !filter_var($remoteAddr, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
+
+ if ($remoteIsPrivate) {
+ $headers = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP'];
+ foreach ($headers as $header) {
+ if (!empty($_SERVER[$header])) {
+ $ip = trim(explode(',', $_SERVER[$header])[0]);
+ if (filter_var($ip, FILTER_VALIDATE_IP)) {
+ return $ip;
+ }
}
}
}
- return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
+ return $remoteAddr !== '' ? $remoteAddr : 'unknown';
}
/**
@@ -931,3 +1321,161 @@ function buildOrderNumberPattern(array $config): string {
?>
PHP_TAIL;
}
+
+// ═══════════════════════════════════════════════════════════════
+// Auto-unlock recovery + socket detection + health probe
+// ═══════════════════════════════════════════════════════════════
+
+/**
+ * Auto-unlock recovery: if install.lock exists but the install never
+ * finished (no admin_users yet, or table missing), delete the lock so
+ * the user can resume. Triple-gated: lock present + DB connectable +
+ * admin_users empty/absent. Logs the auto-action to install.log.
+ *
+ * Idempotent — safe to call on every request.
+ */
+function installerCheckIncompleteState(): void {
+ $lockPath = __DIR__ . '/../install.lock';
+ $configPath = __DIR__ . '/../config.php';
+
+ if (!file_exists($lockPath)) return;
+ if (!file_exists($configPath)) return; // Without config we can't probe DB
+
+ // Best-effort include of config to get $db_config — but we don't trust
+ // its globals in this script's scope, so we re-parse manually.
+ $configSrc = @file_get_contents($configPath);
+ if ($configSrc === false) return;
+
+ if (!preg_match("/'host'\s*=>\s*'([^']+)'/", $configSrc, $hM)) return;
+ if (!preg_match("/'dbname'\s*=>\s*'([^']+)'/", $configSrc, $nM)) return;
+ if (!preg_match("/'username'\s*=>\s*'([^']+)'/", $configSrc, $uM)) return;
+ if (!preg_match("/'password'\s*=>\s*'([^']*)'/", $configSrc, $pM)) return;
+ if (!preg_match("/'port'\s*=>\s*(\d+)/", $configSrc, $portM)) return;
+
+ $host = $hM[1]; $name = $nM[1]; $user = $uM[1]; $pass = $pM[1]; $port = (int)$portM[1];
+ if (strtolower($host) === 'localhost') $host = '127.0.0.1';
+
+ try {
+ $dsn = "mysql:host={$host};port={$port};dbname={$name};charset=utf8mb4";
+ $pdo = new PDO($dsn, $user, $pass, [
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+ PDO::ATTR_TIMEOUT => 5,
+ ]);
+
+ // admin_users table missing → install was never completed.
+ $stmt = $pdo->query("SHOW TABLES LIKE 'admin_users'");
+ if (!$stmt->fetch()) {
+ installerLog("auto-unlock: admin_users table missing — clearing install.lock");
+ @unlink($lockPath);
+ return;
+ }
+
+ // admin_users empty → install was never completed.
+ $cnt = (int)$pdo->query("SELECT COUNT(*) FROM admin_users")->fetchColumn();
+ if ($cnt === 0) {
+ installerLog("auto-unlock: admin_users empty — clearing install.lock");
+ @unlink($lockPath);
+ return;
+ }
+ } catch (PDOException $e) {
+ // Can't connect — might mean DB isn't even available; don't unlock,
+ // user must resolve DB issue first. Log it.
+ installerLog("auto-unlock: skipped (DB connect failed: " . $e->getMessage() . ")");
+ return;
+ }
+
+ // Install was completed properly — keep the lock.
+}
+
+/**
+ * Probe a list of common Unix socket paths and return the first that exists.
+ */
+function installerProbeSockets(): array {
+ $candidates = [
+ '/tmp/mysql.sock',
+ '/var/run/mysqld/mysqld.sock',
+ '/var/lib/mysql/mysql.sock',
+ '/www/server/mysql/mysql.sock',
+ '/var/run/mariadb/mariadb.sock',
+ '/usr/local/mysql/mysql.sock',
+ '/usr/local/var/mysql/mysql.sock',
+ '/opt/lampp/var/mysql/mysql.sock',
+ ];
+ $found = [];
+ foreach ($candidates as $p) {
+ if (@file_exists($p)) {
+ $found[] = $p;
+ }
+ }
+ return $found;
+}
+
+/**
+ * Action: detect_socket — returns list of socket paths discovered on disk.
+ */
+function handleDetectSocket(): void {
+ $found = installerProbeSockets();
+ echo json_encode([
+ 'success' => true,
+ 'sockets' => $found,
+ 'suggested' => $found[0] ?? '',
+ ]);
+}
+
+/**
+ * Action: health — quick post-install probe (no auth required because
+ * install.lock blocks re-entry once installed). Returns DB connect status,
+ * tables present, admin row count.
+ */
+function handleHealth(): void {
+ $configPath = __DIR__ . '/../config.php';
+ if (!file_exists($configPath)) {
+ echo json_encode(['success' => false, 'message' => 'config.php not found']);
+ return;
+ }
+
+ $pdo = getInstallerPdo();
+ if (!$pdo) return; // getInstallerPdo already echoed error
+
+ $checks = [];
+ try {
+ $pdo->query('SELECT 1');
+ $checks[] = ['label' => 'Database connect', 'status' => 'pass'];
+ } catch (PDOException $e) {
+ $checks[] = ['label' => 'Database connect', 'status' => 'fail', 'message' => $e->getMessage()];
+ }
+
+ $expectTables = ['admin_users', 'oem_keys', 'technicians', 'system_config', 'schema_versions'];
+ foreach ($expectTables as $t) {
+ try {
+ $stmt = $pdo->query("SHOW TABLES LIKE '" . str_replace("'", '', $t) . "'");
+ $found = (bool)$stmt->fetch();
+ $checks[] = ['label' => "Table {$t}", 'status' => $found ? 'pass' : 'fail'];
+ } catch (PDOException $e) {
+ $checks[] = ['label' => "Table {$t}", 'status' => 'fail'];
+ }
+ }
+
+ try {
+ $admins = (int)$pdo->query("SELECT COUNT(*) FROM admin_users")->fetchColumn();
+ $checks[] = ['label' => 'Admin accounts', 'status' => $admins > 0 ? 'pass' : 'fail', 'value' => $admins];
+ } catch (PDOException $e) {
+ $checks[] = ['label' => 'Admin accounts', 'status' => 'fail'];
+ }
+
+ $allPass = !in_array(false, array_map(fn($c) => $c['status'] === 'pass', $checks), true);
+ echo json_encode(['success' => $allPass, 'checks' => $checks]);
+}
+
+/**
+ * Append a single line to install/install.log. Best-effort: silently
+ * skipped if the file isn't writable.
+ */
+function installerLog(string $line): void {
+ $logPath = __DIR__ . '/install.log';
+ @file_put_contents(
+ $logPath,
+ '[' . date('Y-m-d H:i:s') . '] ' . $line . PHP_EOL,
+ FILE_APPEND
+ );
+}
diff --git a/FINAL_PRODUCTION_SYSTEM/install/index.php b/FINAL_PRODUCTION_SYSTEM/install/index.php
index dedcb1a..f55d2e1 100644
--- a/FINAL_PRODUCTION_SYSTEM/install/index.php
+++ b/FINAL_PRODUCTION_SYSTEM/install/index.php
@@ -12,8 +12,47 @@
* 6. Complete — write config, show success
*/
-// Prevent running if already installed
+// Auto-unlock recovery (P0): if install.lock exists but install never
+// completed (admin_users empty/missing), clear the lock so user can resume.
+// Inlined to avoid dragging ajax.php's JSON header + dispatch into HTML output.
$lockFile = __DIR__ . '/../install.lock';
+$configFile = __DIR__ . '/../config.php';
+if (file_exists($lockFile) && file_exists($configFile)) {
+ $configSrc = @file_get_contents($configFile);
+ if ($configSrc !== false
+ && preg_match("/'host'\s*=>\s*'([^']+)'/", $configSrc, $hM)
+ && preg_match("/'dbname'\s*=>\s*'([^']+)'/", $configSrc, $nM)
+ && preg_match("/'username'\s*=>\s*'([^']+)'/", $configSrc, $uM)
+ && preg_match("/'password'\s*=>\s*'([^']*)'/", $configSrc, $pM)
+ && preg_match("/'port'\s*=>\s*(\d+)/", $configSrc, $portM)) {
+ $autoHost = strtolower($hM[1]) === 'localhost' ? '127.0.0.1' : $hM[1];
+ try {
+ $autoPdo = new PDO(
+ "mysql:host={$autoHost};port={$portM[1]};dbname={$nM[1]};charset=utf8mb4",
+ $uM[1], $pM[1],
+ [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_TIMEOUT => 5]
+ );
+ $hasAdminTable = (bool) $autoPdo->query("SHOW TABLES LIKE 'admin_users'")->fetch();
+ $adminCount = $hasAdminTable ? (int) $autoPdo->query("SELECT COUNT(*) FROM admin_users")->fetchColumn() : 0;
+ if (!$hasAdminTable || $adminCount === 0) {
+ @unlink($lockFile);
+ @file_put_contents(
+ __DIR__ . '/install.log',
+ '[' . date('Y-m-d H:i:s') . "] auto-unlock from index.php — admin_users empty/missing\n",
+ FILE_APPEND
+ );
+ }
+ } catch (PDOException $autoE) {
+ @file_put_contents(
+ __DIR__ . '/install.log',
+ '[' . date('Y-m-d H:i:s') . '] auto-unlock skipped: ' . $autoE->getMessage() . "\n",
+ FILE_APPEND
+ );
+ }
+ }
+}
+
+// Prevent running if already installed
if (file_exists($lockFile)) {
$installed = json_decode(file_get_contents($lockFile), true);
?>
@@ -395,12 +434,36 @@
Will be created if it doesn't exist (requires CREATE privilege).
+
- Advanced (optional) — Unix socket path
+ Advanced (optional)
Socket Path
-
-
If set, host/port are ignored. Common aaPanel paths: /tmp/mysql.sock or /www/server/mysql/mysql.sock.
+
+
+ Detect
+
+
If set, host/port are ignored. Click Detect to auto-find. Common paths: /tmp/mysql.sock, /var/run/mysqld/mysqld.sock, /www/server/mysql/mysql.sock.
+
+
+
Charset
+
+ Auto-detect (recommended)
+ utf8mb4 (MySQL ≥5.7 / MariaDB ≥10.2)
+ utf8 / utf8mb3 (legacy)
+
+
Auto-detect downgrades to utf8 for very old servers (MySQL < 5.7).
+
+
+
Table Prefix (optional)
+
+
Lowercase letters, digits, underscore. Lets you run KeyGate alongside other apps in the same database. Leave empty for default.
@@ -657,77 +720,138 @@ function renderChecks(containerId, checks) {
}
// ── Step 2: Database Test ────────────────────────────
+async function detectSocket() {
+ const data = await post('detect_socket', {});
+ const input = document.getElementById('dbSocket');
+ if (data.success && data.suggested) {
+ input.value = data.suggested;
+ alert('Found ' + data.sockets.length + ' socket(s). Picked: ' + data.suggested);
+ } else {
+ alert('No Unix socket found in common paths. Stick with TCP (host=127.0.0.1).');
+ }
+}
+
async function testDb() {
const btn = document.getElementById('dbTestBtn');
btn.disabled = true;
btn.innerHTML = '
Testing...';
- const socketEl = document.getElementById('dbSocket');
+ const $ = id => document.getElementById(id);
dbCredentials = {
- db_host: document.getElementById('dbHost').value.trim() || '127.0.0.1',
- db_port: document.getElementById('dbPort').value,
- db_user: document.getElementById('dbUser').value,
- db_pass: document.getElementById('dbPass').value,
- db_name: document.getElementById('dbName').value,
- db_socket: socketEl ? socketEl.value.trim() : '',
+ db_host: $('dbHost').value.trim() || '127.0.0.1',
+ db_port: $('dbPort').value,
+ db_user: $('dbUser').value,
+ db_pass: $('dbPass').value,
+ db_name: $('dbName').value,
+ db_socket: $('dbSocket') ? $('dbSocket').value.trim() : '',
+ db_prefix: $('dbPrefix') ? $('dbPrefix').value.trim() : '',
+ db_charset: $('dbCharset') ? $('dbCharset').value : '',
+ skip_create_db: $('dbSkipCreate') && $('dbSkipCreate').checked ? '1' : '',
};
const data = await post('test_db', dbCredentials);
- const div = document.getElementById('dbTestResult');
+ const div = $('dbTestResult');
div.classList.remove('hidden');
if (data.success) {
div.className = 'alert alert-success';
div.innerHTML = `
✓ Connected! ${data.message}`;
- document.getElementById('dbNext').disabled = false;
+ // Persist server-resolved charset back into the dropdown so steps 3+ use it.
+ if (data.charset && $('dbCharset')) $('dbCharset').value = data.charset;
+ $('dbNext').disabled = false;
} else {
div.className = 'alert alert-danger';
- div.innerHTML = `
✗ Failed: ${data.message}`;
- document.getElementById('dbNext').disabled = true;
+ let html = `
✗ Failed: ${data.message}`;
+ if (data.suggest_skip_create) {
+ html += `
Tick & retry `;
+ }
+ div.innerHTML = html;
+ $('dbNext').disabled = true;
}
btn.disabled = false;
btn.innerHTML = 'Test Connection';
}
-// ── Step 3: Migrations ──────────────────────────────
+// ── Step 3: Migrations (async per-file, survives short max_execution_time) ──
async function runMigrations() {
- document.getElementById('migrationPre').classList.add('hidden');
- document.getElementById('migrationProgress').classList.remove('hidden');
- document.getElementById('migBack').disabled = true;
- document.getElementById('migNext').classList.add('hidden');
+ const $ = id => document.getElementById(id);
+ $('migrationPre').classList.add('hidden');
+ $('migrationProgress').classList.remove('hidden');
+ $('migBack').disabled = true;
+ $('migNext').classList.add('hidden');
- const log = document.getElementById('migLog');
+ const log = $('migLog');
log.innerHTML = '';
- const data = await post('install_db', dbCredentials);
+ // ── 1. Init: get migration list + applied flags ──
+ const init = await post('install_db_init', dbCredentials);
+ if (!init.success) {
+ $('migStatus').textContent = 'Initialization failed';
+ $('migStatus').style.color = 'var(--danger)';
+ log.innerHTML += `
ERROR: ${init.message || 'Unknown error'}
`;
+ $('migBack').disabled = false;
+ return;
+ }
- const total = data.results ? data.results.length : 0;
+ const list = init.migrations || [];
+ const total = list.length;
let done = 0;
+ let hadError = false;
+
+ const updateProgress = () => {
+ const pct = total > 0 ? Math.round((done / total) * 100) : 0;
+ $('migBar').style.width = pct + '%';
+ $('migCount').textContent = done + ' / ' + total;
+ };
- if (data.results) {
- data.results.forEach(r => {
+ // ── 2. Step through each migration ──
+ for (const m of list) {
+ if (!m.exists) {
done++;
- const pct = Math.round((done / total) * 100);
- document.getElementById('migBar').style.width = pct + '%';
- document.getElementById('migCount').textContent = done + ' / ' + total;
-
- const cls = r.status === 'ok' ? 'ok' : r.status === 'skipped' ? 'skip' : 'err';
- const icon = r.status === 'ok' ? '✓' : r.status === 'skipped' ? '→' : '✗';
- log.innerHTML += `
${icon} ${r.file}: ${r.message}
`;
- log.scrollTop = log.scrollHeight;
- });
+ log.innerHTML += `
→ ${m.file}: not found (skipped)
`;
+ updateProgress();
+ continue;
+ }
+ if (m.applied) {
+ done++;
+ log.innerHTML += `
→ ${m.file}: already applied
`;
+ updateProgress();
+ continue;
+ }
+
+ const pendingRow = document.createElement('div');
+ pendingRow.className = 'pending';
+ pendingRow.innerHTML = `
${m.file}…`;
+ log.appendChild(pendingRow);
+ log.scrollTop = log.scrollHeight;
+
+ const r = await post('install_db_step', { ...dbCredentials, file: m.file, version: m.version });
+ log.removeChild(pendingRow);
+
+ const cls = r.status === 'ok' ? 'ok' : r.status === 'skipped' ? 'skip' : 'err';
+ const icon = r.status === 'ok' ? '✓' : r.status === 'skipped' ? '→' : '✗';
+ log.innerHTML += `
${icon} ${m.file}: ${r.message || ''}
`;
+ log.scrollTop = log.scrollHeight;
+
+ done++;
+ updateProgress();
+
+ if (!r.success && r.status === 'error') {
+ hadError = true;
+ // Stop loop on first hard error so user can read it.
+ break;
+ }
}
- if (data.success) {
- document.getElementById('migStatus').textContent = 'All migrations complete!';
- document.getElementById('migStatus').style.color = 'var(--success)';
- document.getElementById('migNext').classList.remove('hidden');
+ if (!hadError) {
+ $('migStatus').textContent = 'All migrations complete!';
+ $('migStatus').style.color = 'var(--success)';
+ $('migNext').classList.remove('hidden');
} else {
- document.getElementById('migStatus').textContent = 'Installation failed';
- document.getElementById('migStatus').style.color = 'var(--danger)';
- log.innerHTML += `
ERROR: ${data.message || 'Unknown error'}
`;
- document.getElementById('migBack').disabled = false;
+ $('migStatus').textContent = 'Installation failed';
+ $('migStatus').style.color = 'var(--danger)';
+ $('migBack').disabled = false;
}
}
From 35c4d9739edf5c82618e0619bc987627d8d8e201 Mon Sep 17 00:00:00 2001
From: Ayoub Mohamed Samir <7agtyadmin@gmail.com>
Date: Thu, 7 May 2026 07:22:10 +0300
Subject: [PATCH 08/15] P1: Joomla-style table-prefix support across SQL + PHP
runtime (#20)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Introduces optional database-table prefix so multiple KeyGate instances
can coexist in one database (panels like cPanel/Plesk often share a
single DB across customer apps). Default prefix is empty → bit-for-bit
identical schema to the previous release.
Changes:
1. tools/prefix-codemod.php
New one-shot script. Self-discovers the canonical KeyGate table list
from CREATE TABLE / ALTER TABLE statements in database/*.sql, then
rewrites:
- SQL files (32): every `tablename` and unbacktiked SQL-keyword target
becomes `#__tablename`. The `#__` sentinel is the Joomla convention.
- PHP files (~50): every backticked or bare-name SQL table reference
inside string literals becomes `' . t('tablename') . '` (single-quote
concat) or `" . t('tablename') . "` (double-quote concat).
Token-based PHP parser (token_get_all) so comments / identifiers /
non-string code is never touched. Idempotent — second run is a no-op.
Run: docker compose exec web php /tmp/codemod.php --root /var/www/html/activate --apply
2. FINAL_PRODUCTION_SYSTEM/functions/db-helpers.php
New file. Defines `t(string $name): string` returning DB_PREFIX . name.
Also defines a fallback `define('DB_PREFIX', '')` if config.php hasn't
set it — covers all legacy installs.
3. FINAL_PRODUCTION_SYSTEM/constants.php
Loads functions/db-helpers.php at the top so t() is available before
any controller runs.
4. FINAL_PRODUCTION_SYSTEM/database/*.sql
Codemod output: 32 SQL files with `#__` markers. Schema is identical
when prefix='' (the default). 276 backticked refs converted.
5. FINAL_PRODUCTION_SYSTEM/{controllers,api,functions,*}.php
Codemod output: ~362 site rewrites across ~54 files. Every SQL string
literal that referenced a canonical table now resolves through t().
6. FINAL_PRODUCTION_SYSTEM/install/ajax.php
- installerRunSqlFile() substitutes `#__` → $_SESSION['install_db']['prefix']
before running each migration. Defense-in-depth: aborts if any `#__`
remains post-substitution.
- installerT() helper for installer-time queries (mirrors t() but reads
prefix from session, since DB_PREFIX isn't defined yet).
- handleInstallDbInit/Step/All all use `installerT('schema_versions')`
when checking applied migrations.
- handleCreateAdmin uses installerT() for admin_users/acl_roles.
- handleFinalize uses installerT() for system_config/technicians/
trusted_networks/admin_ip_whitelist; passes prefix + charset to
generateConfig().
- handleHealth probes prefix-aware physical table names.
- installerCheckIncompleteState() reads DB_PREFIX from existing
config.php so auto-unlock works on prefixed installs.
- generateConfig() emits define('DB_PREFIX', '...') AND propagates
the auto-detected charset (utf8mb4 or utf8mb3 fallback).
7. FINAL_PRODUCTION_SYSTEM/install/index.php
Inline auto-unlock logic now reads DB_PREFIX from config.php, so the
admin_users probe targets the correct physical table name.
8. FINAL_PRODUCTION_SYSTEM/database/docker-init/00-init.sh
New KEYGATE_DB_PREFIX env var (default empty). Pre-runs `sed` over
every .sql file into a /tmp staging copy so the original (read-only)
mount stays untouched. schema_versions tracking table picks up the
prefix consistently. Validates prefix against ^[a-z][a-z0-9_]{0,9}$.
Checksum is computed against the original file (stable across prefix
choices).
Backward compatibility:
- Existing installs without DB_PREFIX in config.php → db-helpers.php
defaults to empty string → t('admin_users') === 'admin_users'.
- 32 .sql files use `#__` placeholders. Without substitution they're
invalid SQL — but they're never executed without going through either
installerRunSqlFile() or 00-init.sh's sed pass.
- Verified: live admin login + list_keys both succeed against the
pre-existing dev database after the codemod.
- 14/14 frontend tests pass.
Risk register from the plan:
- ✅ Codemod misses dynamic table refs → CI lint will catch (P2 task).
- ✅ FK / TRIGGER / VIEW unqualified table refs → SQL pass handles them.
- ✅ Empty-prefix path emits literal `#__` → installerRunSqlFile asserts
strpos(#__) === false post-substitution and aborts.
- ✅ Prefix collides with reserved name → step-2 UI deny-list +
00-init.sh regex validation.
- ✅ Async runner slows happy-path → fast path retained (install_db_all).
- ✅ Legacy config.php lacks DB_PREFIX → db-helpers.php fallback.
Co-authored-by: ChesnoTech <263363000+ChesnoTech@users.noreply.github.com>
---
.gitignore | 3 +
FINAL_PRODUCTION_SYSTEM/admin_v2.php | 4 +-
.../api/authenticate-usb.php | 10 +-
.../api/change-password.php | 4 +-
.../api/collect-hardware-v2.php | 12 +-
.../api/download-resource.php | 4 +-
.../api/get-alt-server-config.php | 6 +-
.../api/get-client-config.php | 2 +-
FINAL_PRODUCTION_SYSTEM/api/get-key.php | 10 +-
FINAL_PRODUCTION_SYSTEM/api/import-csv.php | 2 +-
FINAL_PRODUCTION_SYSTEM/api/login.php | 8 +-
.../api/middleware/RateLimiter.php | 2 +-
FINAL_PRODUCTION_SYSTEM/api/report-result.php | 22 +-
.../api/submit-hardware.php | 10 +-
FINAL_PRODUCTION_SYSTEM/api/totp-disable.php | 4 +-
.../api/totp-regenerate-backup-codes.php | 4 +-
FINAL_PRODUCTION_SYSTEM/api/totp-setup.php | 10 +-
FINAL_PRODUCTION_SYSTEM/api/totp-verify.php | 6 +-
FINAL_PRODUCTION_SYSTEM/config-production.php | 18 +-
FINAL_PRODUCTION_SYSTEM/config.php | 2 +-
.../config/config-template-enhanced.php | 10 +-
FINAL_PRODUCTION_SYSTEM/constants.php | 5 +
.../controllers/admin/AclController.php | 2 +-
.../controllers/admin/BackupsController.php | 2 +-
.../controllers/admin/BrandingController.php | 4 +-
.../admin/ClientResourcesController.php | 16 +-
.../admin/ComplianceController.php | 30 +-
.../controllers/admin/DashboardController.php | 28 +-
.../controllers/admin/HistoryController.php | 16 +-
.../admin/IntegrationController.php | 12 +-
.../controllers/admin/KeysController.php | 8 +-
.../controllers/admin/LicenseController.php | 6 +-
.../admin/NotificationsController.php | 26 +-
.../admin/ProductVariantsController.php | 24 +-
.../admin/ProductionController.php | 50 +--
.../controllers/admin/SecurityController.php | 12 +-
.../admin/TaskPipelineController.php | 34 +-
.../admin/TechniciansController.php | 18 +-
.../controllers/admin/UpgradeController.php | 26 +-
.../admin/UsbDevicesController.php | 20 +-
.../database/2fa_migration.sql | 16 +-
.../database/acl_migration.sql | 76 ++--
.../database/backup_migration.sql | 12 +-
.../database/client_config_migration.sql | 2 +-
.../database/client_resources_migration.sql | 4 +-
.../database/create_admin.php | 4 +-
.../database/database_admin_security.sql | 26 +-
.../database/database_concurrency_indexes.sql | 16 +-
.../database/database_setup.sql | 16 +-
.../database/database_setup_with_users.sql | 28 +-
.../database/docker-init/00-init.sh | 46 +-
.../database/downloads_acl_migration.sql | 14 +-
.../database/hardware_info_migration.sql | 18 +-
.../database/hardware_info_v2_migration.sql | 56 +--
.../database/hash_temp_passwords.php | 4 +-
.../database/i18n_migration.sql | 6 +-
FINAL_PRODUCTION_SYSTEM/database/install.sql | 44 +-
.../database/integrations_migration.sql | 8 +-
.../database/license_migration.sql | 6 +-
.../database/missing_drivers_migration.sql | 10 +-
.../database/order_field_config_migration.sql | 12 +-
.../database/product_variants_migration.sql | 14 +-
.../production_tracking_migration.sql | 10 +-
.../database/push_notifications_migration.sql | 14 +-
.../database/qc_compliance_migration.sql | 34 +-
.../database/rate_limiting_migration.sql | 4 +-
.../database/rbac_migration.sql | 20 +-
.../database/schema_versions_migration.sql | 2 +-
.../database/seed_demo_compliance.sql | 34 +-
.../database/seed_variants.sql | 22 +-
.../database/task_pipeline_migration.sql | 10 +-
.../database/temp_password_hash_migration.sql | 2 +-
.../database/unallocated_space_migration.sql | 2 +-
.../database/upgrade_system_migration.sql | 2 +-
.../database/usb_devices_migration.sql | 2 +-
FINAL_PRODUCTION_SYSTEM/functions/acl.php | 68 +--
.../functions/admin-helpers.php | 26 +-
.../functions/csv-import.php | 14 +-
.../functions/db-helpers.php | 37 ++
.../functions/integration-helpers.php | 26 +-
.../functions/key-helpers.php | 4 +-
.../functions/license-helpers.php | 12 +-
.../functions/network-utils.php | 6 +-
.../functions/push-helpers.php | 10 +-
.../functions/qc-compliance.php | 34 +-
.../functions/session-helpers.php | 12 +-
.../functions/totp-helpers.php | 10 +-
FINAL_PRODUCTION_SYSTEM/install/ajax.php | 136 ++++--
FINAL_PRODUCTION_SYSTEM/install/index.php | 10 +-
FINAL_PRODUCTION_SYSTEM/secure-admin.php | 4 +-
FINAL_PRODUCTION_SYSTEM/setup/index.php | 4 +-
tools/prefix-codemod.php | 398 ++++++++++++++++++
92 files changed, 1220 insertions(+), 679 deletions(-)
create mode 100644 FINAL_PRODUCTION_SYSTEM/functions/db-helpers.php
create mode 100644 tools/prefix-codemod.php
diff --git a/.gitignore b/.gitignore
index 8cb5ce7..9aa8908 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,9 @@ logs/
# Don't commit 12MB+ artifacts — every dev rebuilds locally on first commit.
**/graphify-out/
+# ── Uploaded client artifacts (per-instance, regenerated at runtime) ──
+FINAL_PRODUCTION_SYSTEM/uploads/client-resources/
+
# ── PHP Dependencies (managed by Composer) ────────────────
FINAL_PRODUCTION_SYSTEM/vendor/
diff --git a/FINAL_PRODUCTION_SYSTEM/admin_v2.php b/FINAL_PRODUCTION_SYSTEM/admin_v2.php
index cf50293..5937524 100644
--- a/FINAL_PRODUCTION_SYSTEM/admin_v2.php
+++ b/FINAL_PRODUCTION_SYSTEM/admin_v2.php
@@ -154,7 +154,7 @@
if (isset($_POST['action']) && $_POST['action'] === 'change_language' && isset($_POST['language'])) {
$newLang = preg_replace('/[^a-z]/', '', strtolower($_POST['language']));
if (in_array($newLang, ['en', 'ru'])) {
- $stmt = $pdo->prepare("UPDATE admin_users SET preferred_language = ? WHERE id = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('admin_users') . "` SET preferred_language = ? WHERE id = ?");
$stmt->execute([$newLang, $admin_session['admin_id']]);
loadLanguage($newLang);
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH'])) {
@@ -404,7 +404,7 @@
// Handle logout
if (isset($_GET['logout'])) {
if (isset($_SESSION['admin_token'])) {
- $stmt = $pdo->prepare("UPDATE admin_sessions SET is_active = 0 WHERE session_token = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('admin_sessions') . "` SET is_active = 0 WHERE session_token = ?");
$stmt->execute([$_SESSION['admin_token']]);
}
session_destroy();
diff --git a/FINAL_PRODUCTION_SYSTEM/api/authenticate-usb.php b/FINAL_PRODUCTION_SYSTEM/api/authenticate-usb.php
index bddcedf..3703043 100644
--- a/FINAL_PRODUCTION_SYSTEM/api/authenticate-usb.php
+++ b/FINAL_PRODUCTION_SYSTEM/api/authenticate-usb.php
@@ -90,8 +90,8 @@
// Find USB device by serial number
$stmt = $pdo->prepare("
SELECT d.*, t.full_name, t.is_active
- FROM usb_devices d
- INNER JOIN technicians t ON d.technician_id = t.technician_id
+ FROM `" . t('usb_devices') . "` d
+ INNER JOIN `" . t('technicians') . "` t ON d.technician_id = t.technician_id
WHERE d.device_serial_number = ?
");
$stmt->execute([$usbSerialNumber]);
@@ -165,7 +165,7 @@
// Insert session
$stmt = $pdo->prepare("
- INSERT INTO active_sessions (
+ INSERT INTO `" . t('active_sessions') . "` (
technician_id, session_token, created_at, expires_at,
is_active, auth_method, usb_device_id, computer_name
) VALUES (?, ?, NOW(), ?, 1, 'usb', ?, ?)
@@ -180,7 +180,7 @@
// Update USB device last used info
$stmt = $pdo->prepare("
- UPDATE usb_devices
+ UPDATE `" . t('usb_devices') . "`
SET last_used_date = NOW(),
last_used_ip = ?,
last_used_computer_name = ?,
@@ -191,7 +191,7 @@
// Update technician last login
$stmt = $pdo->prepare("
- UPDATE technicians
+ UPDATE `" . t('technicians') . "`
SET last_login = NOW(),
failed_login_attempts = 0,
locked_until = NULL
diff --git a/FINAL_PRODUCTION_SYSTEM/api/change-password.php b/FINAL_PRODUCTION_SYSTEM/api/change-password.php
index 1fbaa35..d3449ec 100644
--- a/FINAL_PRODUCTION_SYSTEM/api/change-password.php
+++ b/FINAL_PRODUCTION_SYSTEM/api/change-password.php
@@ -29,7 +29,7 @@
try {
// Get technician details
$stmt = $pdo->prepare("
- SELECT * FROM technicians
+ SELECT * FROM `" . t('technicians') . "`
WHERE technician_id = ? AND is_active = 1
");
$stmt->execute([$technician_id]);
@@ -63,7 +63,7 @@
$new_password_hash = password_hash($new_password, PASSWORD_BCRYPT, ['cost' => BCRYPT_COST]);
$stmt = $pdo->prepare("
- UPDATE technicians
+ UPDATE `" . t('technicians') . "`
SET password_hash = ?, temp_password = NULL, must_change_password = FALSE
WHERE technician_id = ?
");
diff --git a/FINAL_PRODUCTION_SYSTEM/api/collect-hardware-v2.php b/FINAL_PRODUCTION_SYSTEM/api/collect-hardware-v2.php
index 0568dbd..e839e40 100644
--- a/FINAL_PRODUCTION_SYSTEM/api/collect-hardware-v2.php
+++ b/FINAL_PRODUCTION_SYSTEM/api/collect-hardware-v2.php
@@ -26,8 +26,8 @@
// Validate session token and get technician info
$stmt = $pdo->prepare("
SELECT s.technician_id, t.full_name, s.expires_at
- FROM active_sessions s
- INNER JOIN technicians t ON s.technician_id = t.technician_id
+ FROM `" . t('active_sessions') . "` s
+ INNER JOIN `" . t('technicians') . "` t ON s.technician_id = t.technician_id
WHERE s.session_token = ?
AND s.expires_at > NOW()
LIMIT 1
@@ -44,7 +44,7 @@
// Check if hardware info already exists for this order number
$stmt = $pdo->prepare("
SELECT id, collection_timestamp
- FROM hardware_info
+ FROM `" . t('hardware_info') . "`
WHERE order_number = ?
ORDER BY collection_timestamp DESC
LIMIT 1
@@ -164,7 +164,7 @@
// Insert hardware information
$stmt = $pdo->prepare("
- INSERT INTO hardware_info (
+ INSERT INTO `" . t('hardware_info') . "` (
activation_id, order_number, technician_id, session_token,
motherboard_manufacturer, motherboard_product, motherboard_serial, motherboard_version,
bios_manufacturer, bios_version, bios_release_date, bios_serial_number,
@@ -280,7 +280,7 @@
// Log the collection attempt
$stmt = $pdo->prepare("
- INSERT INTO hardware_collection_log (
+ INSERT INTO `" . t('hardware_collection_log') . "` (
order_number, technician_id, session_token, hardware_info_id, collection_status
) VALUES (?, ?, ?, ?, 'success')
");
@@ -316,7 +316,7 @@
try {
if (isset($technicianId) && isset($orderNumber) && isset($sessionToken)) {
$stmt = $pdo->prepare("
- INSERT INTO hardware_collection_log (
+ INSERT INTO `" . t('hardware_collection_log') . "` (
order_number, technician_id, session_token, hardware_info_id,
collection_status, error_message
) VALUES (?, ?, ?, NULL, 'failed', ?)
diff --git a/FINAL_PRODUCTION_SYSTEM/api/download-resource.php b/FINAL_PRODUCTION_SYSTEM/api/download-resource.php
index 509052c..02048a1 100644
--- a/FINAL_PRODUCTION_SYSTEM/api/download-resource.php
+++ b/FINAL_PRODUCTION_SYSTEM/api/download-resource.php
@@ -30,7 +30,7 @@
// Validate session token
$stmt = $pdo->prepare("
SELECT s.technician_id
- FROM active_sessions s
+ FROM `" . t('active_sessions') . "` s
WHERE s.session_token = ? AND s.expires_at > NOW()
");
$stmt->execute([$sessionToken]);
@@ -43,7 +43,7 @@
}
// Look up the resource
- $stmt = $pdo->prepare("SELECT * FROM client_resources WHERE resource_key = ?");
+ $stmt = $pdo->prepare("SELECT * FROM `" . t('client_resources') . "` WHERE resource_key = ?");
$stmt->execute([$resourceKey]);
$resource = $stmt->fetch(PDO::FETCH_ASSOC);
diff --git a/FINAL_PRODUCTION_SYSTEM/api/get-alt-server-config.php b/FINAL_PRODUCTION_SYSTEM/api/get-alt-server-config.php
index fac23f3..519bca3 100644
--- a/FINAL_PRODUCTION_SYSTEM/api/get-alt-server-config.php
+++ b/FINAL_PRODUCTION_SYSTEM/api/get-alt-server-config.php
@@ -23,8 +23,8 @@
// Verify valid session and get technician preferences
$stmt = $pdo->prepare("
SELECT s.technician_id, t.preferred_server
- FROM active_sessions s
- INNER JOIN technicians t ON s.technician_id = t.technician_id
+ FROM `" . t('active_sessions') . "` s
+ INNER JOIN `" . t('technicians') . "` t ON s.technician_id = t.technician_id
WHERE s.session_token = ? AND s.expires_at > NOW()
");
$stmt->execute([$sessionToken]);
@@ -38,7 +38,7 @@
// Helper function to get config value
function getConfig($key) {
global $pdo;
- $stmt = $pdo->prepare("SELECT config_value FROM system_config WHERE config_key = ?");
+ $stmt = $pdo->prepare("SELECT config_value FROM `" . t('system_config') . "` WHERE config_key = ?");
$stmt->execute([$key]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result ? $result['config_value'] : null;
diff --git a/FINAL_PRODUCTION_SYSTEM/api/get-client-config.php b/FINAL_PRODUCTION_SYSTEM/api/get-client-config.php
index 758ad8f..86f924b 100644
--- a/FINAL_PRODUCTION_SYSTEM/api/get-client-config.php
+++ b/FINAL_PRODUCTION_SYSTEM/api/get-client-config.php
@@ -23,7 +23,7 @@
// Verify valid session
$stmt = $pdo->prepare("
SELECT s.technician_id
- FROM active_sessions s
+ FROM `" . t('active_sessions') . "` s
WHERE s.session_token = ? AND s.expires_at > NOW()
");
$stmt->execute([$sessionToken]);
diff --git a/FINAL_PRODUCTION_SYSTEM/api/get-key.php b/FINAL_PRODUCTION_SYSTEM/api/get-key.php
index 24c37fc..5cde2f4 100644
--- a/FINAL_PRODUCTION_SYSTEM/api/get-key.php
+++ b/FINAL_PRODUCTION_SYSTEM/api/get-key.php
@@ -21,7 +21,7 @@
if (qcIsEnabled($pdo)) {
$globalSettings = qcGetGlobalSettings($pdo);
if (!empty($globalSettings['blocking_prevents_key'])) {
- $hwStmt = $pdo->prepare("SELECT id FROM hardware_info WHERE order_number = ? ORDER BY collection_timestamp DESC LIMIT 1");
+ $hwStmt = $pdo->prepare("SELECT id FROM `" . t('hardware_info') . "` WHERE order_number = ? ORDER BY collection_timestamp DESC LIMIT 1");
$hwStmt->execute([$order_number]);
$hwRow = $hwStmt->fetch(PDO::FETCH_ASSOC);
if ($hwRow && qcHasBlockingIssues($pdo, (int) $hwRow['id'])) {
@@ -47,7 +47,7 @@
// Update existing session with new order number if different
if ($existing_session['order_number'] !== $order_number) {
$stmt = $pdo->prepare("
- UPDATE active_sessions
+ UPDATE `" . t('active_sessions') . "`
SET order_number = ?, expires_at = DATE_ADD(NOW(), INTERVAL ? MINUTE)
WHERE id = ?
");
@@ -74,7 +74,7 @@
$pdo->rollback();
// Check if ANY keys exist vs. all keys exhausted (for automatic failover)
- $stmt = $pdo->prepare("SELECT COUNT(*) as available_count FROM oem_keys WHERE key_status IN ('unused', 'retry')");
+ $stmt = $pdo->prepare("SELECT COUNT(*) as available_count FROM `" . t('oem_keys') . "` WHERE key_status IN ('unused', 'retry')");
$stmt->execute();
$availableCount = $stmt->fetch(PDO::FETCH_ASSOC)['available_count'];
@@ -104,7 +104,7 @@
// Insert new session (we already checked for existing sessions above)
$stmt = $pdo->prepare("
- INSERT INTO active_sessions (technician_id, session_token, key_id, order_number, expires_at, auth_method, computer_name)
+ INSERT INTO `" . t('active_sessions') . "` (technician_id, session_token, key_id, order_number, expires_at, auth_method, computer_name)
VALUES (?, ?, ?, ?, ?, 'password', ?)
");
$stmt->execute([$technician_id, $session_token, $key['id'], $order_number, $expires_at, $computerName]);
@@ -121,7 +121,7 @@
// Check key pool levels and send alerts if needed
try {
$edition = $key['product_type'] ?? 'Unknown';
- $poolStmt = $pdo->prepare("SELECT COUNT(*) as remaining FROM oem_keys WHERE key_status IN ('unused', 'retry') AND product_type = ?");
+ $poolStmt = $pdo->prepare("SELECT COUNT(*) as remaining FROM `" . t('oem_keys') . "` WHERE key_status IN ('unused', 'retry') AND product_type = ?");
$poolStmt->execute([$edition]);
$remaining = (int)$poolStmt->fetch()['remaining'];
diff --git a/FINAL_PRODUCTION_SYSTEM/api/import-csv.php b/FINAL_PRODUCTION_SYSTEM/api/import-csv.php
index 98aa1a3..9f1b995 100644
--- a/FINAL_PRODUCTION_SYSTEM/api/import-csv.php
+++ b/FINAL_PRODUCTION_SYSTEM/api/import-csv.php
@@ -66,7 +66,7 @@
// Try to insert key
try {
$stmt = $pdo->prepare("
- INSERT INTO oem_keys (product_key, oem_identifier, barcode, key_status, roll_serial)
+ INSERT INTO `" . t('oem_keys') . "` (product_key, oem_identifier, barcode, key_status, roll_serial)
VALUES (?, ?, ?, ?, 'imported')
");
$stmt->execute([$product_key, $oem_identifier, $barcode, $key_status]);
diff --git a/FINAL_PRODUCTION_SYSTEM/api/login.php b/FINAL_PRODUCTION_SYSTEM/api/login.php
index 3ce3275..7734621 100644
--- a/FINAL_PRODUCTION_SYSTEM/api/login.php
+++ b/FINAL_PRODUCTION_SYSTEM/api/login.php
@@ -16,7 +16,7 @@
try {
// Get technician details (including language preference)
$stmt = $pdo->prepare("
- SELECT * FROM technicians
+ SELECT * FROM `" . t('technicians') . "`
WHERE technician_id = ? AND is_active = 1
");
$stmt->execute([$technician_id]);
@@ -64,7 +64,7 @@
}
$stmt = $pdo->prepare("
- UPDATE technicians
+ UPDATE `" . t('technicians') . "`
SET failed_login_attempts = ?, locked_until = ?
WHERE technician_id = ?
");
@@ -94,7 +94,7 @@
// Login successful - reset failed attempts
$stmt = $pdo->prepare("
- UPDATE technicians
+ UPDATE `" . t('technicians') . "`
SET failed_login_attempts = 0, locked_until = NULL, last_login = NOW()
WHERE technician_id = ?
");
@@ -137,7 +137,7 @@
// Include active product lines for order type selection
try {
- $plStmt = $pdo->query("SELECT id, name, order_pattern, description FROM product_lines WHERE is_active = 1 ORDER BY name ASC");
+ $plStmt = $pdo->query("SELECT id, name, order_pattern, description FROM `" . t('product_lines') . "` WHERE is_active = 1 ORDER BY name ASC");
$productLines = $plStmt->fetchAll(PDO::FETCH_ASSOC);
if (!empty($productLines)) {
$response['product_lines'] = array_map(function($pl) {
diff --git a/FINAL_PRODUCTION_SYSTEM/api/middleware/RateLimiter.php b/FINAL_PRODUCTION_SYSTEM/api/middleware/RateLimiter.php
index 19d5177..7d2e083 100644
--- a/FINAL_PRODUCTION_SYSTEM/api/middleware/RateLimiter.php
+++ b/FINAL_PRODUCTION_SYSTEM/api/middleware/RateLimiter.php
@@ -140,7 +140,7 @@ public function logViolation($identifier, $action, $endpoint, $requestCount, $li
try {
$stmt = $pdo->prepare("
- INSERT INTO rate_limit_violations (
+ INSERT INTO `" . t('rate_limit_violations') . "` (
identifier, action, endpoint, client_ip, user_agent,
request_count, limit_threshold, window_seconds
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
diff --git a/FINAL_PRODUCTION_SYSTEM/api/report-result.php b/FINAL_PRODUCTION_SYSTEM/api/report-result.php
index 579e1ff..d199610 100644
--- a/FINAL_PRODUCTION_SYSTEM/api/report-result.php
+++ b/FINAL_PRODUCTION_SYSTEM/api/report-result.php
@@ -126,7 +126,7 @@ function validateAPIAccess(): bool {
}
// NEW: Check if unique ID already exists (prevent duplicates)
-$stmt = $pdo->prepare("SELECT id FROM activation_attempts WHERE activation_unique_id = ?");
+$stmt = $pdo->prepare("SELECT id FROM `" . t('activation_attempts') . "` WHERE activation_unique_id = ?");
$stmt->execute([$activationUniqueId]);
if ($stmt->fetch()) {
jsonResponse([
@@ -156,7 +156,7 @@ function validateAPIAccess(): bool {
// Record activation attempt with all required fields
$stmt = $pdo->prepare("
- INSERT INTO activation_attempts (
+ INSERT INTO `" . t('activation_attempts') . "` (
key_id,
technician_id,
order_number,
@@ -203,7 +203,7 @@ function validateAPIAccess(): bool {
if ($result === 'success') {
// Success path: Mark key as good and deactivate session
$stmt = $pdo->prepare("
- UPDATE oem_keys
+ UPDATE `" . t('oem_keys') . "`
SET key_status = 'good',
updated_at = NOW()
WHERE id = ?
@@ -212,7 +212,7 @@ function validateAPIAccess(): bool {
// Deactivate session (activation complete)
$stmt = $pdo->prepare("
- UPDATE active_sessions
+ UPDATE `" . t('active_sessions') . "`
SET is_active = 0
WHERE id = ?
");
@@ -226,7 +226,7 @@ function validateAPIAccess(): bool {
} else {
// Failure path: Update fail counter and determine key status
$stmt = $pdo->prepare("
- UPDATE oem_keys
+ UPDATE `" . t('oem_keys') . "`
SET fail_counter = fail_counter + 1,
updated_at = NOW()
WHERE id = ?
@@ -236,7 +236,7 @@ function validateAPIAccess(): bool {
// Get updated fail counter
$stmt = $pdo->prepare("
SELECT fail_counter, product_key, oem_identifier
- FROM oem_keys
+ FROM `" . t('oem_keys') . "`
WHERE id = ?
");
$stmt->execute([$session['key_id']]);
@@ -253,7 +253,7 @@ function validateAPIAccess(): bool {
if ($failCounter >= $maxAttempts) {
// Mark as bad after max failures
$stmt = $pdo->prepare("
- UPDATE oem_keys
+ UPDATE `" . t('oem_keys') . "`
SET key_status = 'bad'
WHERE id = ?
");
@@ -264,7 +264,7 @@ function validateAPIAccess(): bool {
// Deactivate session (key is bad)
$stmt = $pdo->prepare("
- UPDATE active_sessions
+ UPDATE `" . t('active_sessions') . "`
SET is_active = 0
WHERE id = ?
");
@@ -275,7 +275,7 @@ function validateAPIAccess(): bool {
} else {
// Mark for retry
$stmt = $pdo->prepare("
- UPDATE oem_keys
+ UPDATE `" . t('oem_keys') . "`
SET key_status = 'retry'
WHERE id = ?
");
@@ -382,7 +382,7 @@ function sendEmailNotification(
if ($keyData === null) {
$stmt = $pdo->prepare("
SELECT product_key, oem_identifier, fail_counter, key_status
- FROM oem_keys
+ FROM `" . t('oem_keys') . "`
WHERE id = ?
");
$stmt->execute([$session['key_id']]);
@@ -396,7 +396,7 @@ function sendEmailNotification(
// Get technician full name
$stmt = $pdo->prepare("
SELECT full_name, email
- FROM technicians
+ FROM `" . t('technicians') . "`
WHERE technician_id = ?
");
$stmt->execute([$session['technician_id']]);
diff --git a/FINAL_PRODUCTION_SYSTEM/api/submit-hardware.php b/FINAL_PRODUCTION_SYSTEM/api/submit-hardware.php
index e22d856..bb4496b 100644
--- a/FINAL_PRODUCTION_SYSTEM/api/submit-hardware.php
+++ b/FINAL_PRODUCTION_SYSTEM/api/submit-hardware.php
@@ -47,8 +47,8 @@
// Validate session token and get activation_id
$stmt = $pdo->prepare("
SELECT aa.id as activation_id, aa.technician_id, aa.key_id
- FROM activation_attempts aa
- INNER JOIN active_sessions s ON s.technician_id = aa.technician_id
+ FROM `" . t('activation_attempts') . "` aa
+ INNER JOIN `" . t('active_sessions') . "` s ON s.technician_id = aa.technician_id
WHERE s.session_token = ?
AND aa.order_number = ?
AND aa.attempt_result = 'success'
@@ -66,7 +66,7 @@
$activationId = $activation['activation_id'];
// Check if hardware info already exists for this activation
- $stmt = $pdo->prepare("SELECT id FROM hardware_info WHERE activation_id = ?");
+ $stmt = $pdo->prepare("SELECT id FROM `" . t('hardware_info') . "` WHERE activation_id = ?");
$stmt->execute([$activationId]);
if ($stmt->fetch()) {
jsonResponse(['success' => true, 'message' => 'Hardware information already recorded', 'duplicate' => true]);
@@ -77,7 +77,7 @@
// Insert hardware information
$stmt = $pdo->prepare("
- INSERT INTO hardware_info (
+ INSERT INTO `" . t('hardware_info') . "` (
activation_id,
order_number,
motherboard_manufacturer,
@@ -188,7 +188,7 @@
]);
// Update activation_attempts to mark hardware as collected
- $stmt = $pdo->prepare("UPDATE activation_attempts SET hardware_collected = 1 WHERE id = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('activation_attempts') . "` SET hardware_collected = 1 WHERE id = ?");
$stmt->execute([$activationId]);
$hardwareId = $pdo->lastInsertId();
diff --git a/FINAL_PRODUCTION_SYSTEM/api/totp-disable.php b/FINAL_PRODUCTION_SYSTEM/api/totp-disable.php
index 2148be3..9d59c58 100644
--- a/FINAL_PRODUCTION_SYSTEM/api/totp-disable.php
+++ b/FINAL_PRODUCTION_SYSTEM/api/totp-disable.php
@@ -62,7 +62,7 @@
// Code verified - disable 2FA
$stmt = $pdo->prepare("
- UPDATE admin_totp_secrets
+ UPDATE `" . t('admin_totp_secrets') . "`
SET totp_enabled = 0
WHERE admin_id = ?
");
@@ -70,7 +70,7 @@
// Log activity
$stmt = $pdo->prepare("
- INSERT INTO admin_activity_log (admin_id, session_id, action, description, ip_address, user_agent)
+ INSERT INTO `" . t('admin_activity_log') . "` (admin_id, session_id, action, description, ip_address, user_agent)
VALUES (?, ?, 'TOTP_DISABLED', '2FA disabled by user', ?, ?)
");
$stmt->execute([
diff --git a/FINAL_PRODUCTION_SYSTEM/api/totp-regenerate-backup-codes.php b/FINAL_PRODUCTION_SYSTEM/api/totp-regenerate-backup-codes.php
index 0341507..796b8ef 100644
--- a/FINAL_PRODUCTION_SYSTEM/api/totp-regenerate-backup-codes.php
+++ b/FINAL_PRODUCTION_SYSTEM/api/totp-regenerate-backup-codes.php
@@ -65,7 +65,7 @@
// Update database
$stmt = $pdo->prepare("
- UPDATE admin_totp_secrets
+ UPDATE `" . t('admin_totp_secrets') . "`
SET backup_codes = ?
WHERE admin_id = ?
");
@@ -76,7 +76,7 @@
// Log activity
$stmt = $pdo->prepare("
- INSERT INTO admin_activity_log (admin_id, session_id, action, description, ip_address, user_agent)
+ INSERT INTO `" . t('admin_activity_log') . "` (admin_id, session_id, action, description, ip_address, user_agent)
VALUES (?, ?, 'TOTP_BACKUP_REGEN', 'Regenerated 2FA backup codes', ?, ?)
");
$stmt->execute([
diff --git a/FINAL_PRODUCTION_SYSTEM/api/totp-setup.php b/FINAL_PRODUCTION_SYSTEM/api/totp-setup.php
index a6f799c..9e8ca57 100644
--- a/FINAL_PRODUCTION_SYSTEM/api/totp-setup.php
+++ b/FINAL_PRODUCTION_SYSTEM/api/totp-setup.php
@@ -24,7 +24,7 @@
// Verify session is valid in DB
$stmt = $pdo->prepare("
- SELECT admin_id FROM admin_sessions
+ SELECT admin_id FROM `" . t('admin_sessions') . "`
WHERE id = ? AND admin_id = ? AND is_active = 1
");
$stmt->execute([$sessionId, $adminId]);
@@ -33,7 +33,7 @@
}
// Get admin info
-$stmt = $pdo->prepare("SELECT username, email FROM admin_users WHERE id = ?");
+$stmt = $pdo->prepare("SELECT username, email FROM `" . t('admin_users') . "` WHERE id = ?");
$stmt->execute([$adminId]);
$admin = $stmt->fetch(PDO::FETCH_ASSOC);
@@ -75,7 +75,7 @@
// Store in database (not yet enabled)
if ($existing) {
$stmt = $pdo->prepare("
- UPDATE admin_totp_secrets
+ UPDATE `" . t('admin_totp_secrets') . "`
SET totp_secret = ?, backup_codes = ?, totp_enabled = 0, verified_at = NULL, created_at = NOW()
WHERE admin_id = ?
");
@@ -86,7 +86,7 @@
]);
} else {
$stmt = $pdo->prepare("
- INSERT INTO admin_totp_secrets (admin_id, totp_secret, backup_codes, totp_enabled)
+ INSERT INTO `" . t('admin_totp_secrets') . "` (admin_id, totp_secret, backup_codes, totp_enabled)
VALUES (?, ?, ?, 0)
");
$stmt->execute([
@@ -108,7 +108,7 @@
// Log activity
$stmt = $pdo->prepare("
- INSERT INTO admin_activity_log (admin_id, session_id, action, description, ip_address, user_agent)
+ INSERT INTO `" . t('admin_activity_log') . "` (admin_id, session_id, action, description, ip_address, user_agent)
VALUES (?, ?, 'TOTP_SETUP', 'Started 2FA setup', ?, ?)
");
$stmt->execute([
diff --git a/FINAL_PRODUCTION_SYSTEM/api/totp-verify.php b/FINAL_PRODUCTION_SYSTEM/api/totp-verify.php
index 93196a6..441ec30 100644
--- a/FINAL_PRODUCTION_SYSTEM/api/totp-verify.php
+++ b/FINAL_PRODUCTION_SYSTEM/api/totp-verify.php
@@ -69,7 +69,7 @@
// If this is initial setup verification, enable 2FA
if ($isSetup && $totpData['totp_enabled'] == 0) {
$stmt = $pdo->prepare("
- UPDATE admin_totp_secrets
+ UPDATE `" . t('admin_totp_secrets') . "`
SET totp_enabled = 1, verified_at = NOW()
WHERE admin_id = ?
");
@@ -77,7 +77,7 @@
// Log activity
$stmt = $pdo->prepare("
- INSERT INTO admin_activity_log (admin_id, session_id, action, description, ip_address, user_agent)
+ INSERT INTO `" . t('admin_activity_log') . "` (admin_id, session_id, action, description, ip_address, user_agent)
VALUES (?, NULL, 'TOTP_ENABLED', '2FA successfully enabled', ?, ?)
");
$stmt->execute([
@@ -89,7 +89,7 @@
// Log successful verification
$stmt = $pdo->prepare("
- INSERT INTO admin_activity_log (admin_id, session_id, action, description, ip_address, user_agent, totp_verified)
+ INSERT INTO `" . t('admin_activity_log') . "` (admin_id, session_id, action, description, ip_address, user_agent, totp_verified)
VALUES (?, NULL, 'TOTP_VERIFIED', ?, ?, ?, 1)
");
$stmt->execute([
diff --git a/FINAL_PRODUCTION_SYSTEM/config-production.php b/FINAL_PRODUCTION_SYSTEM/config-production.php
index e2850c2..8d004f9 100644
--- a/FINAL_PRODUCTION_SYSTEM/config-production.php
+++ b/FINAL_PRODUCTION_SYSTEM/config-production.php
@@ -89,7 +89,7 @@ function getConfig($key, $useCache = true) {
}
try {
- $stmt = $pdo->prepare("SELECT config_value FROM system_config WHERE config_key = ?");
+ $stmt = $pdo->prepare("SELECT config_value FROM `" . t('system_config') . "` WHERE config_key = ?");
$stmt->execute([$key]);
$result = $stmt->fetch();
$value = $result ? $result['config_value'] : null;
@@ -192,9 +192,9 @@ function validateSession($token) {
try {
$stmt = $pdo->prepare("
SELECT s.*, k.product_key, k.key_status, t.is_active as tech_active
- FROM active_sessions s
- LEFT JOIN oem_keys k ON s.key_id = k.id
- LEFT JOIN technicians t ON s.technician_id = t.technician_id
+ FROM `" . t('active_sessions') . "` s
+ LEFT JOIN `" . t('oem_keys') . "` k ON s.key_id = k.id
+ LEFT JOIN `" . t('technicians') . "` t ON s.technician_id = t.technician_id
WHERE s.session_token = ?
AND s.is_active = 1
AND s.expires_at > NOW()
@@ -233,7 +233,7 @@ function allocateKeyAtomically($pdo, $technician_id, $order_number) {
// Select and lock the best available key
$stmt = $pdo->prepare("
- SELECT * FROM oem_keys
+ SELECT * FROM `" . t('oem_keys') . "`
WHERE key_status IN ('unused', 'retry')
AND (fail_counter < 3 OR key_status = 'unused')
ORDER BY
@@ -250,7 +250,7 @@ function allocateKeyAtomically($pdo, $technician_id, $order_number) {
if ($key) {
// Mark key as in use immediately
$stmt = $pdo->prepare("
- UPDATE oem_keys
+ UPDATE `" . t('oem_keys') . "`
SET key_status = 'allocated',
last_use_date = CURDATE(),
last_use_time = CURTIME(),
@@ -295,7 +295,7 @@ function allocateKeyAtomically($pdo, $technician_id, $order_number) {
function cleanupExpiredSessions($pdo) {
try {
$stmt = $pdo->prepare("
- UPDATE active_sessions
+ UPDATE `" . t('active_sessions') . "`
SET is_active = 0
WHERE expires_at < NOW() AND is_active = 1
LIMIT 1000
@@ -321,8 +321,8 @@ function getActiveSession($pdo, $technician_id) {
$stmt = $pdo->prepare("
SELECT s.*, k.product_key, k.oem_identifier, k.key_status, k.fail_counter
- FROM active_sessions s
- LEFT JOIN oem_keys k ON s.key_id = k.id
+ FROM `" . t('active_sessions') . "` s
+ LEFT JOIN `" . t('oem_keys') . "` k ON s.key_id = k.id
WHERE s.technician_id = ?
AND s.is_active = 1
AND s.expires_at > NOW()
diff --git a/FINAL_PRODUCTION_SYSTEM/config.php b/FINAL_PRODUCTION_SYSTEM/config.php
index 6268dfd..29304e0 100644
--- a/FINAL_PRODUCTION_SYSTEM/config.php
+++ b/FINAL_PRODUCTION_SYSTEM/config.php
@@ -108,7 +108,7 @@ function getConfig($key, $useCache = true) {
}
try {
- $stmt = $pdo->prepare("SELECT config_value FROM system_config WHERE config_key = ?");
+ $stmt = $pdo->prepare("SELECT config_value FROM `" . t('system_config') . "` WHERE config_key = ?");
$stmt->execute([$key]);
$result = $stmt->fetch();
$value = $result ? $result['config_value'] : null;
diff --git a/FINAL_PRODUCTION_SYSTEM/config/config-template-enhanced.php b/FINAL_PRODUCTION_SYSTEM/config/config-template-enhanced.php
index 05474de..289a853 100644
--- a/FINAL_PRODUCTION_SYSTEM/config/config-template-enhanced.php
+++ b/FINAL_PRODUCTION_SYSTEM/config/config-template-enhanced.php
@@ -46,7 +46,7 @@
function getConfig($key, $default = null) {
global $pdo;
try {
- $stmt = $pdo->prepare("SELECT config_value FROM system_config WHERE config_key = ?");
+ $stmt = $pdo->prepare("SELECT config_value FROM `" . t('system_config') . "` WHERE config_key = ?");
$stmt->execute([$key]);
$result = $stmt->fetch();
return $result ? $result['config_value'] : $default;
@@ -61,7 +61,7 @@ function setConfig($key, $value, $description = '') {
global $pdo;
try {
$stmt = $pdo->prepare("
- INSERT INTO system_config (config_key, config_value, description)
+ INSERT INTO `" . t('system_config') . "` (config_key, config_value, description)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE
config_value = VALUES(config_value),
@@ -102,7 +102,7 @@ function validateTechnician($technician_id, $password) {
try {
$stmt = $pdo->prepare("
- SELECT * FROM technicians
+ SELECT * FROM `" . t('technicians') . "`
WHERE technician_id = ? AND is_active = 1
");
$stmt->execute([$technician_id]);
@@ -121,7 +121,7 @@ function validateTechnician($technician_id, $password) {
if (password_verify($password, $technician['password_hash'])) {
// Reset failed attempts on successful login
$stmt = $pdo->prepare("
- UPDATE technicians
+ UPDATE `" . t('technicians') . "`
SET failed_login_attempts = 0, locked_until = NULL, last_login = NOW()
WHERE technician_id = ?
");
@@ -132,7 +132,7 @@ function validateTechnician($technician_id, $password) {
// Increment failed attempts
$stmt = $pdo->prepare("
- UPDATE technicians
+ UPDATE `" . t('technicians') . "`
SET failed_login_attempts = failed_login_attempts + 1,
locked_until = IF(failed_login_attempts + 1 >= 5, DATE_ADD(NOW(), INTERVAL 15 MINUTE), NULL)
WHERE technician_id = ?
diff --git a/FINAL_PRODUCTION_SYSTEM/constants.php b/FINAL_PRODUCTION_SYSTEM/constants.php
index fdb4101..45fc95e 100644
--- a/FINAL_PRODUCTION_SYSTEM/constants.php
+++ b/FINAL_PRODUCTION_SYSTEM/constants.php
@@ -7,6 +7,11 @@
* Import this file wherever constants are needed.
*/
+// ── DB table prefix helper (must load before any PDO query) ────────
+// Empty default for backward-compat with installs that pre-date the
+// prefix release. The web installer overwrites DB_PREFIX in config.php.
+require_once __DIR__ . '/functions/db-helpers.php';
+
// ── Authentication ─────────────────────────────────────────────────
define('BCRYPT_COST', 12);
define('PASSWORD_MIN_LENGTH', 8);
diff --git a/FINAL_PRODUCTION_SYSTEM/controllers/admin/AclController.php b/FINAL_PRODUCTION_SYSTEM/controllers/admin/AclController.php
index a05b65d..8c9e914 100644
--- a/FINAL_PRODUCTION_SYSTEM/controllers/admin/AclController.php
+++ b/FINAL_PRODUCTION_SYSTEM/controllers/admin/AclController.php
@@ -62,7 +62,7 @@ function handle_acl_create_role(PDO $pdo, array $admin_session, ?array $json_inp
}
// Rate limit: max 20 custom roles
- $stmt = $pdo->prepare("SELECT COUNT(*) FROM acl_roles WHERE is_system_role = 0");
+ $stmt = $pdo->prepare("SELECT COUNT(*) FROM `" . t('acl_roles') . "` WHERE is_system_role = 0");
$stmt->execute();
if ((int)$stmt->fetchColumn() >= 20) {
jsonResponse(['success' => false, 'error' => 'Maximum custom roles limit reached (20)']);
diff --git a/FINAL_PRODUCTION_SYSTEM/controllers/admin/BackupsController.php b/FINAL_PRODUCTION_SYSTEM/controllers/admin/BackupsController.php
index 89a5d0c..78f283e 100644
--- a/FINAL_PRODUCTION_SYSTEM/controllers/admin/BackupsController.php
+++ b/FINAL_PRODUCTION_SYSTEM/controllers/admin/BackupsController.php
@@ -8,7 +8,7 @@ function handle_list_backups(PDO $pdo, array $admin_session): void {
requirePermission('view_backups', $admin_session);
$stmt = $pdo->query("
- SELECT * FROM backup_history
+ SELECT * FROM `" . t('backup_history') . "`
ORDER BY created_at DESC
LIMIT 50
");
diff --git a/FINAL_PRODUCTION_SYSTEM/controllers/admin/BrandingController.php b/FINAL_PRODUCTION_SYSTEM/controllers/admin/BrandingController.php
index ea96a58..a23d8ea 100644
--- a/FINAL_PRODUCTION_SYSTEM/controllers/admin/BrandingController.php
+++ b/FINAL_PRODUCTION_SYSTEM/controllers/admin/BrandingController.php
@@ -140,7 +140,7 @@ function handle_upload_brand_asset(PDO $pdo, array $admin_session): void {
// Store relative path in system_config
$relativePath = 'uploads/branding/' . $storedFilename;
$stmt = $pdo->prepare("
- INSERT INTO system_config (config_key, config_value, description, updated_at)
+ INSERT INTO `" . t('system_config') . "` (config_key, config_value, description, updated_at)
VALUES (?, ?, '', NOW())
ON DUPLICATE KEY UPDATE config_value = ?, updated_at = NOW()
");
@@ -181,7 +181,7 @@ function handle_delete_brand_asset(PDO $pdo, array $admin_session, ?array $json_
// Clear config value
$stmt = $pdo->prepare("
- INSERT INTO system_config (config_key, config_value, description, updated_at)
+ INSERT INTO `" . t('system_config') . "` (config_key, config_value, description, updated_at)
VALUES (?, '', '', NOW())
ON DUPLICATE KEY UPDATE config_value = '', updated_at = NOW()
");
diff --git a/FINAL_PRODUCTION_SYSTEM/controllers/admin/ClientResourcesController.php b/FINAL_PRODUCTION_SYSTEM/controllers/admin/ClientResourcesController.php
index 5447c94..a9ce0e0 100644
--- a/FINAL_PRODUCTION_SYSTEM/controllers/admin/ClientResourcesController.php
+++ b/FINAL_PRODUCTION_SYSTEM/controllers/admin/ClientResourcesController.php
@@ -68,7 +68,7 @@ function handle_upload_client_resource(PDO $pdo, array $admin_session): void {
$destPath = $uploadDir . '/' . $storedFilename;
// Delete existing resource with same key (replace mode)
- $existing = $pdo->prepare("SELECT filename FROM client_resources WHERE resource_key = ?");
+ $existing = $pdo->prepare("SELECT filename FROM `" . t('client_resources') . "` WHERE resource_key = ?");
$existing->execute([$resourceKey]);
$oldFile = $existing->fetchColumn();
if ($oldFile) {
@@ -76,7 +76,7 @@ function handle_upload_client_resource(PDO $pdo, array $admin_session): void {
if (file_exists($oldPath)) {
unlink($oldPath);
}
- $pdo->prepare("DELETE FROM client_resources WHERE resource_key = ?")->execute([$resourceKey]);
+ $pdo->prepare("DELETE FROM `" . t('client_resources') . "` WHERE resource_key = ?")->execute([$resourceKey]);
}
// Move uploaded file
@@ -95,7 +95,7 @@ function handle_upload_client_resource(PDO $pdo, array $admin_session): void {
// Insert DB record
$stmt = $pdo->prepare("
- INSERT INTO client_resources (resource_key, filename, original_filename, file_size, mime_type, checksum_sha256, description, uploaded_by)
+ INSERT INTO `" . t('client_resources') . "` (resource_key, filename, original_filename, file_size, mime_type, checksum_sha256, description, uploaded_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([$resourceKey, $storedFilename, $originalName, $fileSize, $mimeType, $checksum, $description, $adminId]);
@@ -134,7 +134,7 @@ function handle_delete_client_resource(PDO $pdo, array $admin_session, ?array $j
return;
}
- $stmt = $pdo->prepare("SELECT filename FROM client_resources WHERE resource_key = ?");
+ $stmt = $pdo->prepare("SELECT filename FROM `" . t('client_resources') . "` WHERE resource_key = ?");
$stmt->execute([$resourceKey]);
$filename = $stmt->fetchColumn();
@@ -151,7 +151,7 @@ function handle_delete_client_resource(PDO $pdo, array $admin_session, ?array $j
}
// Delete DB record
- $pdo->prepare("DELETE FROM client_resources WHERE resource_key = ?")->execute([$resourceKey]);
+ $pdo->prepare("DELETE FROM `" . t('client_resources') . "` WHERE resource_key = ?")->execute([$resourceKey]);
logAdminActivity(
$admin_session['admin_id'],
@@ -172,8 +172,8 @@ function handle_list_client_resources(PDO $pdo, array $admin_session): void {
$stmt = $pdo->prepare("
SELECT cr.*, au.username AS uploaded_by_name
- FROM client_resources cr
- LEFT JOIN admin_users au ON cr.uploaded_by = au.id
+ FROM `" . t('client_resources') . "` cr
+ LEFT JOIN `" . t('admin_users') . "` au ON cr.uploaded_by = au.id
ORDER BY cr.created_at DESC
");
$stmt->execute();
@@ -200,7 +200,7 @@ function handle_download_client_resource(PDO $pdo, array $admin_session): void {
return;
}
- $stmt = $pdo->prepare("SELECT filename, original_filename, mime_type, file_size, checksum_sha256 FROM client_resources WHERE resource_key = ?");
+ $stmt = $pdo->prepare("SELECT filename, original_filename, mime_type, file_size, checksum_sha256 FROM `" . t('client_resources') . "` WHERE resource_key = ?");
$stmt->execute([$resourceKey]);
$resource = $stmt->fetch(PDO::FETCH_ASSOC);
diff --git a/FINAL_PRODUCTION_SYSTEM/controllers/admin/ComplianceController.php b/FINAL_PRODUCTION_SYSTEM/controllers/admin/ComplianceController.php
index 6c4395a..94a4350 100644
--- a/FINAL_PRODUCTION_SYSTEM/controllers/admin/ComplianceController.php
+++ b/FINAL_PRODUCTION_SYSTEM/controllers/admin/ComplianceController.php
@@ -23,7 +23,7 @@ function handle_qc_save_settings(PDO $pdo, array $admin_session, ?array $json_in
foreach ($allowedKeys as $key) {
if (isset($json_input[$key])) {
- $stmt = $pdo->prepare("UPDATE qc_global_settings SET setting_value = ?, updated_by = ? WHERE setting_key = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('qc_global_settings') . "` SET setting_value = ?, updated_by = ? WHERE setting_key = ?");
$stmt->execute([$json_input[$key], $admin_session['admin_id'], $key]);
}
}
@@ -95,7 +95,7 @@ function handle_qc_list_motherboards(PDO $pdo, array $admin_session, ?array $jso
unset($row);
// Distinct manufacturers for filter dropdown
- $mfrs = $pdo->query("SELECT DISTINCT manufacturer FROM qc_motherboard_registry ORDER BY manufacturer")->fetchAll(PDO::FETCH_COLUMN);
+ $mfrs = $pdo->query("SELECT DISTINCT manufacturer FROM `" . t('qc_motherboard_registry') . "` ORDER BY manufacturer")->fetchAll(PDO::FETCH_COLUMN);
jsonResponse([
'success' => true,
@@ -111,7 +111,7 @@ function handle_qc_get_motherboard(PDO $pdo, array $admin_session, ?array $json_
requirePermission('view_compliance', $admin_session);
$id = (int) ($_GET['id'] ?? 0);
- $stmt = $pdo->prepare("SELECT * FROM qc_motherboard_registry WHERE id = ?");
+ $stmt = $pdo->prepare("SELECT * FROM `" . t('qc_motherboard_registry') . "` WHERE id = ?");
$stmt->execute([$id]);
$board = $stmt->fetch(PDO::FETCH_ASSOC);
@@ -161,7 +161,7 @@ function handle_qc_update_motherboard(PDO $pdo, array $admin_session, ?array $js
$params[] = $admin_session['admin_id'];
$params[] = $id;
- $stmt = $pdo->prepare("UPDATE qc_motherboard_registry SET " . implode(", ", $fields) . " WHERE id = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('qc_motherboard_registry') . "` SET " . implode(", ", $fields) . " WHERE id = ?");
$stmt->execute($params);
logAdminActivity($admin_session['admin_id'], $admin_session['id'], 'QC_MOTHERBOARD_UPDATE', "Updated motherboard registry #$id");
@@ -174,14 +174,14 @@ function handle_qc_list_manufacturers(PDO $pdo, array $admin_session, ?array $js
requirePermission('view_compliance', $admin_session);
// Configured manufacturers
- $stmt = $pdo->query("SELECT * FROM qc_manufacturer_defaults ORDER BY manufacturer");
+ $stmt = $pdo->query("SELECT * FROM `" . t('qc_manufacturer_defaults') . "` ORDER BY manufacturer");
$configured = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Unconfigured: manufacturers seen in registry but no defaults entry
$stmt = $pdo->query("
SELECT DISTINCT r.manufacturer
- FROM qc_motherboard_registry r
- LEFT JOIN qc_manufacturer_defaults md ON r.manufacturer = md.manufacturer
+ FROM `" . t('qc_motherboard_registry') . "` r
+ LEFT JOIN `" . t('qc_manufacturer_defaults') . "` md ON r.manufacturer = md.manufacturer
WHERE md.id IS NULL
ORDER BY r.manufacturer
");
@@ -200,7 +200,7 @@ function handle_qc_update_manufacturer(PDO $pdo, array $admin_session, ?array $j
}
$stmt = $pdo->prepare("
- INSERT INTO qc_manufacturer_defaults (manufacturer, secure_boot_required, secure_boot_enforcement, min_bios_version, recommended_bios_version, bios_enforcement, hackbgrt_enforcement, notes, updated_by)
+ INSERT INTO `" . t('qc_manufacturer_defaults') . "` (manufacturer, secure_boot_required, secure_boot_enforcement, min_bios_version, recommended_bios_version, bios_enforcement, hackbgrt_enforcement, notes, updated_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
secure_boot_required = VALUES(secure_boot_required),
@@ -454,7 +454,7 @@ function handle_qc_get_stats(PDO $pdo, array $admin_session, ?array $json_input
requirePermission('view_compliance', $admin_session);
// Result counts
- $stmt = $pdo->query("SELECT check_result, COUNT(*) as cnt FROM qc_compliance_results GROUP BY check_result");
+ $stmt = $pdo->query("SELECT check_result, COUNT(*) as cnt FROM `" . t('qc_compliance_results') . "` GROUP BY check_result");
$resultCounts = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
$total = array_sum($resultCounts);
@@ -463,8 +463,8 @@ function handle_qc_get_stats(PDO $pdo, array $admin_session, ?array $json_input
// Top failing boards
$stmt = $pdo->query("
SELECT hi.motherboard_manufacturer, hi.motherboard_product, COUNT(*) as fail_count
- FROM qc_compliance_results cr
- JOIN hardware_info hi ON cr.hardware_info_id = hi.id
+ FROM `" . t('qc_compliance_results') . "` cr
+ JOIN `" . t('hardware_info') . "` hi ON cr.hardware_info_id = hi.id
WHERE cr.check_result = 'fail'
GROUP BY hi.motherboard_manufacturer, hi.motherboard_product
ORDER BY fail_count DESC
@@ -473,19 +473,19 @@ function handle_qc_get_stats(PDO $pdo, array $admin_session, ?array $json_input
$topFailing = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Unresolved blocking
- $stmt = $pdo->query("SELECT COUNT(DISTINCT hardware_info_id) FROM qc_compliance_results WHERE enforcement_level = 3 AND check_result = 'fail'");
+ $stmt = $pdo->query("SELECT COUNT(DISTINCT hardware_info_id) FROM `" . t('qc_compliance_results') . "` WHERE enforcement_level = 3 AND check_result = 'fail'");
$unresolvedBlocking = (int) $stmt->fetchColumn();
// Registry stats
- $stmt = $pdo->query("SELECT COUNT(*) FROM qc_motherboard_registry");
+ $stmt = $pdo->query("SELECT COUNT(*) FROM `" . t('qc_motherboard_registry') . "`");
$registeredBoards = (int) $stmt->fetchColumn();
- $stmt = $pdo->query("SELECT COUNT(*) FROM qc_manufacturer_defaults");
+ $stmt = $pdo->query("SELECT COUNT(*) FROM `" . t('qc_manufacturer_defaults') . "`");
$mfrsWithDefaults = (int) $stmt->fetchColumn();
// Check type breakdown
$stmt = $pdo->query("
SELECT check_type, check_result, COUNT(*) as cnt
- FROM qc_compliance_results
+ FROM `" . t('qc_compliance_results') . "`
GROUP BY check_type, check_result
");
$byType = [];
diff --git a/FINAL_PRODUCTION_SYSTEM/controllers/admin/DashboardController.php b/FINAL_PRODUCTION_SYSTEM/controllers/admin/DashboardController.php
index 41ee81f..dee6f2c 100644
--- a/FINAL_PRODUCTION_SYSTEM/controllers/admin/DashboardController.php
+++ b/FINAL_PRODUCTION_SYSTEM/controllers/admin/DashboardController.php
@@ -17,7 +17,7 @@ function buildReportHtml(PDO $pdo, string $reportType): string {
switch ($reportType) {
case 'summary':
- $stmt = $pdo->query("SELECT key_status, COUNT(*) as count FROM oem_keys GROUP BY key_status");
+ $stmt = $pdo->query("SELECT key_status, COUNT(*) as count FROM `" . t('oem_keys') . "` GROUP BY key_status");
$keyStats = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
$totalKeys = array_sum($keyStats);
@@ -32,7 +32,7 @@ function buildReportHtml(PDO $pdo, string $reportType): string {
$html .= '';
// Technician summary
- $stmt = $pdo->query("SELECT is_active, COUNT(*) as count FROM technicians GROUP BY is_active");
+ $stmt = $pdo->query("SELECT is_active, COUNT(*) as count FROM `" . t('technicians') . "` GROUP BY is_active");
$techStats = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
$html .= '
' . __('keys.report_tech_summary') . ' ';
$html .= '
' . __('keys.status') . ' ' . __('common.count') . ' ';
@@ -46,7 +46,7 @@ function buildReportHtml(PDO $pdo, string $reportType): string {
SELECT DATE(attempted_date) as date, COUNT(*) as count,
SUM(CASE WHEN attempt_result = 'success' THEN 1 ELSE 0 END) as successes,
SUM(CASE WHEN attempt_result != 'success' THEN 1 ELSE 0 END) as failures
- FROM activation_attempts
+ FROM `" . t('activation_attempts') . "`
WHERE attempted_date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
GROUP BY DATE(attempted_date)
ORDER BY date DESC
@@ -66,8 +66,8 @@ function buildReportHtml(PDO $pdo, string $reportType): string {
$stmt = $pdo->query("
SELECT aa.attempted_date, aa.technician_id, aa.order_number,
k.product_key, aa.notes
- FROM activation_attempts aa
- LEFT JOIN oem_keys k ON aa.key_id = k.id
+ FROM `" . t('activation_attempts') . "` aa
+ LEFT JOIN `" . t('oem_keys') . "` k ON aa.key_id = k.id
WHERE aa.attempt_result != 'success'
ORDER BY aa.attempted_date DESC
LIMIT 100
@@ -94,7 +94,7 @@ function buildReportHtml(PDO $pdo, string $reportType): string {
COUNT(*) as total,
SUM(CASE WHEN attempt_result = 'success' THEN 1 ELSE 0 END) as successes,
COUNT(DISTINCT technician_id) as unique_techs
- FROM activation_attempts
+ FROM `" . t('activation_attempts') . "`
GROUP BY DATE_FORMAT(attempted_date, '%Y-%m')
ORDER BY month DESC
LIMIT 12
@@ -120,7 +120,7 @@ function handle_get_stats(PDO $pdo, array $admin_session): void {
$stats = [];
// Key statistics
- $stmt = $pdo->query("SELECT key_status, COUNT(*) as count FROM oem_keys GROUP BY key_status");
+ $stmt = $pdo->query("SELECT key_status, COUNT(*) as count FROM `" . t('oem_keys') . "` GROUP BY key_status");
$key_stats = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
$stats['keys'] = [
'unused' => $key_stats['unused'] ?? 0,
@@ -132,7 +132,7 @@ function handle_get_stats(PDO $pdo, array $admin_session): void {
];
// Technician statistics
- $stmt = $pdo->query("SELECT is_active, COUNT(*) as count FROM technicians GROUP BY is_active");
+ $stmt = $pdo->query("SELECT is_active, COUNT(*) as count FROM `" . t('technicians') . "` GROUP BY is_active");
$tech_stats = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
$stats['technicians'] = [
'active' => $tech_stats[1] ?? 0,
@@ -141,13 +141,13 @@ function handle_get_stats(PDO $pdo, array $admin_session): void {
];
// Activation statistics
- $stmt = $pdo->query("SELECT COUNT(*) FROM activation_attempts WHERE DATE(attempted_date) = CURDATE()");
+ $stmt = $pdo->query("SELECT COUNT(*) FROM `" . t('activation_attempts') . "` WHERE DATE(attempted_date) = CURDATE()");
$stats['activations']['today'] = $stmt->fetchColumn();
- $stmt = $pdo->query("SELECT COUNT(*) FROM activation_attempts WHERE YEARWEEK(attempted_date, 1) = YEARWEEK(CURDATE(), 1)");
+ $stmt = $pdo->query("SELECT COUNT(*) FROM `" . t('activation_attempts') . "` WHERE YEARWEEK(attempted_date, 1) = YEARWEEK(CURDATE(), 1)");
$stats['activations']['week'] = $stmt->fetchColumn();
- $stmt = $pdo->query("SELECT COUNT(*) FROM activation_attempts WHERE YEAR(attempted_date) = YEAR(CURDATE()) AND MONTH(attempted_date) = MONTH(CURDATE())");
+ $stmt = $pdo->query("SELECT COUNT(*) FROM `" . t('activation_attempts') . "` WHERE YEAR(attempted_date) = YEAR(CURDATE()) AND MONTH(attempted_date) = MONTH(CURDATE())");
$stats['activations']['month'] = $stmt->fetchColumn();
// Daily activation trend — fetch all history, let frontend slice by range
@@ -156,7 +156,7 @@ function handle_get_stats(PDO $pdo, array $admin_session): void {
COUNT(*) as total,
SUM(CASE WHEN attempt_result = 'success' THEN 1 ELSE 0 END) as successes,
SUM(CASE WHEN attempt_result != 'success' THEN 1 ELSE 0 END) as failures
- FROM activation_attempts
+ FROM `" . t('activation_attempts') . "`
GROUP BY DATE(attempted_date)
ORDER BY date ASC
");
@@ -188,8 +188,8 @@ function handle_get_stats(PDO $pdo, array $admin_session): void {
// Recent activity
$stmt = $pdo->prepare("
SELECT aal.created_at, au.username, aal.action, aal.description
- FROM admin_activity_log aal
- LEFT JOIN admin_users au ON aal.admin_id = au.id
+ FROM `" . t('admin_activity_log') . "` aal
+ LEFT JOIN `" . t('admin_users') . "` au ON aal.admin_id = au.id
ORDER BY aal.created_at DESC
LIMIT 10
");
diff --git a/FINAL_PRODUCTION_SYSTEM/controllers/admin/HistoryController.php b/FINAL_PRODUCTION_SYSTEM/controllers/admin/HistoryController.php
index 50dc1e4..705e017 100644
--- a/FINAL_PRODUCTION_SYSTEM/controllers/admin/HistoryController.php
+++ b/FINAL_PRODUCTION_SYSTEM/controllers/admin/HistoryController.php
@@ -71,10 +71,10 @@ function handle_get_hardware(PDO $pdo, array $admin_session): void {
$stmt = $pdo->prepare("
SELECT h.*, aa.order_number, aa.attempted_at, aa.technician_id,
t.full_name as technician_name, k.product_key
- FROM hardware_info h
- INNER JOIN activation_attempts aa ON h.activation_id = aa.id
- LEFT JOIN technicians t ON aa.technician_id = t.technician_id
- LEFT JOIN oem_keys k ON aa.key_id = k.id
+ FROM `" . t('hardware_info') . "` h
+ INNER JOIN `" . t('activation_attempts') . "` aa ON h.activation_id = aa.id
+ LEFT JOIN `" . t('technicians') . "` t ON aa.technician_id = t.technician_id
+ LEFT JOIN `" . t('oem_keys') . "` k ON aa.key_id = k.id
WHERE h.activation_id = ?
");
$stmt->execute([$activationId]);
@@ -101,10 +101,10 @@ function handle_get_hardware_by_order(PDO $pdo, array $admin_session): void {
aa.attempt_result as activation_result,
aa.attempted_at as activation_time,
k.product_key
- FROM hardware_info h
- LEFT JOIN technicians t ON h.technician_id = t.technician_id
- LEFT JOIN activation_attempts aa ON h.activation_id = aa.id
- LEFT JOIN oem_keys k ON aa.key_id = k.id
+ FROM `" . t('hardware_info') . "` h
+ LEFT JOIN `" . t('technicians') . "` t ON h.technician_id = t.technician_id
+ LEFT JOIN `" . t('activation_attempts') . "` aa ON h.activation_id = aa.id
+ LEFT JOIN `" . t('oem_keys') . "` k ON aa.key_id = k.id
WHERE h.order_number = ?
ORDER BY h.collection_timestamp DESC
LIMIT 1
diff --git a/FINAL_PRODUCTION_SYSTEM/controllers/admin/IntegrationController.php b/FINAL_PRODUCTION_SYSTEM/controllers/admin/IntegrationController.php
index a093ec4..9d64e59 100644
--- a/FINAL_PRODUCTION_SYSTEM/controllers/admin/IntegrationController.php
+++ b/FINAL_PRODUCTION_SYSTEM/controllers/admin/IntegrationController.php
@@ -9,7 +9,7 @@
function handle_list_integrations(PDO $pdo, array $admin_session): void {
requirePermission('system_settings', $admin_session);
- $stmt = $pdo->query("SELECT * FROM integrations ORDER BY id ASC");
+ $stmt = $pdo->query("SELECT * FROM `" . t('integrations') . "` ORDER BY id ASC");
$rows = $stmt->fetchAll();
// Decode config JSON and mask sensitive fields
@@ -29,7 +29,7 @@ function handle_list_integrations(PDO $pdo, array $admin_session): void {
COUNT(*) as total,
SUM(status = 'failed') as failed,
SUM(status = 'pending') as pending
- FROM integration_events WHERE integration_id = ?
+ FROM `" . t('integration_events') . "` WHERE integration_id = ?
");
$countStmt->execute([$row['id']]);
$row['event_counts'] = $countStmt->fetch();
@@ -48,7 +48,7 @@ function handle_get_integration(PDO $pdo, array $admin_session): void {
return;
}
- $stmt = $pdo->prepare("SELECT * FROM integrations WHERE integration_key = ?");
+ $stmt = $pdo->prepare("SELECT * FROM `" . t('integrations') . "` WHERE integration_key = ?");
$stmt->execute([$key]);
$intg = $stmt->fetch();
if (!$intg) {
@@ -61,7 +61,7 @@ function handle_get_integration(PDO $pdo, array $admin_session): void {
// Recent events (last 20)
$evtStmt = $pdo->prepare("
SELECT id, event_type, status, response_code, error_message, created_at, processed_at
- FROM integration_events
+ FROM `" . t('integration_events') . "`
WHERE integration_id = ?
ORDER BY created_at DESC LIMIT 20
");
@@ -85,7 +85,7 @@ function handle_save_integration(PDO $pdo, array $admin_session, ?array $json_in
return;
}
- $stmt = $pdo->prepare("SELECT * FROM integrations WHERE integration_key = ?");
+ $stmt = $pdo->prepare("SELECT * FROM `" . t('integrations') . "` WHERE integration_key = ?");
$stmt->execute([$key]);
$intg = $stmt->fetch();
if (!$intg) {
@@ -111,7 +111,7 @@ function handle_save_integration(PDO $pdo, array $admin_session, ?array $json_in
}
$updateStmt = $pdo->prepare("
- UPDATE integrations
+ UPDATE `" . t('integrations') . "`
SET enabled = ?, config = ?, updated_at = NOW()
WHERE integration_key = ?
");
diff --git a/FINAL_PRODUCTION_SYSTEM/controllers/admin/KeysController.php b/FINAL_PRODUCTION_SYSTEM/controllers/admin/KeysController.php
index b2f6cbe..86115eb 100644
--- a/FINAL_PRODUCTION_SYSTEM/controllers/admin/KeysController.php
+++ b/FINAL_PRODUCTION_SYSTEM/controllers/admin/KeysController.php
@@ -105,7 +105,7 @@ function handle_recycle_key(PDO $pdo, array $admin_session): void {
$id = intval($_POST['id'] ?? 0);
$stmt = $pdo->prepare("
- UPDATE oem_keys
+ UPDATE `" . t('oem_keys') . "`
SET key_status = 'unused', last_use_date = NULL, last_use_time = NULL
WHERE id = ?
");
@@ -126,7 +126,7 @@ function handle_delete_key(PDO $pdo, array $admin_session): void {
$id = intval($_POST['id'] ?? 0);
- $stmt = $pdo->prepare("DELETE FROM oem_keys WHERE id = ?");
+ $stmt = $pdo->prepare("DELETE FROM `" . t('oem_keys') . "` WHERE id = ?");
$stmt->execute([$id]);
logAdminActivity(
@@ -265,9 +265,9 @@ function handle_add_keys(PDO $pdo, array $admin_session, ?array $json_input = nu
$pdo->beginTransaction();
try {
- $checkStmt = $pdo->prepare("SELECT id FROM oem_keys WHERE product_key = ?");
+ $checkStmt = $pdo->prepare("SELECT id FROM `" . t('oem_keys') . "` WHERE product_key = ?");
$insertStmt = $pdo->prepare("
- INSERT INTO oem_keys (product_key, oem_identifier, roll_serial, key_status, created_at)
+ INSERT INTO `" . t('oem_keys') . "` (product_key, oem_identifier, roll_serial, key_status, created_at)
VALUES (?, ?, ?, 'unused', NOW())
");
diff --git a/FINAL_PRODUCTION_SYSTEM/controllers/admin/LicenseController.php b/FINAL_PRODUCTION_SYSTEM/controllers/admin/LicenseController.php
index 165c59b..4020115 100644
--- a/FINAL_PRODUCTION_SYSTEM/controllers/admin/LicenseController.php
+++ b/FINAL_PRODUCTION_SYSTEM/controllers/admin/LicenseController.php
@@ -17,12 +17,12 @@ function handle_license_status(PDO $pdo, array $admin_session, $json_input): voi
$techCount = 0;
$keyCount = 0;
try {
- $stmt = $pdo->query("SELECT COUNT(*) FROM technicians WHERE status = 'active'");
+ $stmt = $pdo->query("SELECT COUNT(*) FROM `" . t('technicians') . "` WHERE status = 'active'");
$techCount = (int)$stmt->fetchColumn();
} catch (Exception $e) { /* table may not exist */ }
try {
- $stmt = $pdo->query("SELECT COUNT(*) FROM oem_keys");
+ $stmt = $pdo->query("SELECT COUNT(*) FROM `" . t('oem_keys') . "`");
$keyCount = (int)$stmt->fetchColumn();
} catch (Exception $e) { /* table may not exist */ }
@@ -76,7 +76,7 @@ function handle_license_register(PDO $pdo, array $admin_session, $json_input): v
function handle_license_deactivate(PDO $pdo, array $admin_session, $json_input): void {
requirePermission('system_settings', $admin_session);
- $pdo->exec("UPDATE license_info SET is_active = 0");
+ $pdo->exec("UPDATE `" . t('license_info') . "` SET is_active = 0");
saveConfigBatch($pdo, ['license_tier' => 'community']);
logAdminActivity(
diff --git a/FINAL_PRODUCTION_SYSTEM/controllers/admin/NotificationsController.php b/FINAL_PRODUCTION_SYSTEM/controllers/admin/NotificationsController.php
index 81233f1..9e65f2c 100644
--- a/FINAL_PRODUCTION_SYSTEM/controllers/admin/NotificationsController.php
+++ b/FINAL_PRODUCTION_SYSTEM/controllers/admin/NotificationsController.php
@@ -39,7 +39,7 @@ function handle_push_subscribe(PDO $pdo, array $admin_session, ?array $json_inpu
// Upsert: insert or re-activate existing subscription
$stmt = $pdo->prepare("
- INSERT INTO push_subscriptions (admin_id, endpoint, p256dh_key, auth_key, user_agent, is_active)
+ INSERT INTO `" . t('push_subscriptions') . "` (admin_id, endpoint, p256dh_key, auth_key, user_agent, is_active)
VALUES (?, ?, ?, ?, ?, 1)
ON DUPLICATE KEY UPDATE p256dh_key = VALUES(p256dh_key), auth_key = VALUES(auth_key),
user_agent = VALUES(user_agent), is_active = 1, last_used_at = NOW()
@@ -58,10 +58,10 @@ function handle_push_unsubscribe(PDO $pdo, array $admin_session, ?array $json_in
if (empty($endpoint)) {
// Deactivate all subscriptions for this admin
- $stmt = $pdo->prepare("UPDATE push_subscriptions SET is_active = 0 WHERE admin_id = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('push_subscriptions') . "` SET is_active = 0 WHERE admin_id = ?");
$stmt->execute([$adminId]);
} else {
- $stmt = $pdo->prepare("UPDATE push_subscriptions SET is_active = 0 WHERE admin_id = ? AND endpoint = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('push_subscriptions') . "` SET is_active = 0 WHERE admin_id = ? AND endpoint = ?");
$stmt->execute([$adminId, $endpoint]);
}
@@ -75,7 +75,7 @@ function handle_get_push_preferences(PDO $pdo, array $admin_session): void {
$adminId = (int)$admin_session['admin_id'];
// Get saved preferences
- $stmt = $pdo->prepare("SELECT category, enabled FROM push_preferences WHERE admin_id = ?");
+ $stmt = $pdo->prepare("SELECT category, enabled FROM `" . t('push_preferences') . "` WHERE admin_id = ?");
$stmt->execute([$adminId]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
@@ -90,7 +90,7 @@ function handle_get_push_preferences(PDO $pdo, array $admin_session): void {
}
// Check if admin has any active subscriptions
- $subStmt = $pdo->prepare("SELECT COUNT(*) FROM push_subscriptions WHERE admin_id = ? AND is_active = 1");
+ $subStmt = $pdo->prepare("SELECT COUNT(*) FROM `" . t('push_subscriptions') . "` WHERE admin_id = ? AND is_active = 1");
$subStmt->execute([$adminId]);
$hasSubscription = (int)$subStmt->fetchColumn() > 0;
@@ -117,7 +117,7 @@ function handle_save_push_preferences(PDO $pdo, array $admin_session, ?array $js
$validCategories = ['security', 'keys', 'technicians', 'system', 'devices', 'activation'];
$stmt = $pdo->prepare("
- INSERT INTO push_preferences (admin_id, category, enabled)
+ INSERT INTO `" . t('push_preferences') . "` (admin_id, category, enabled)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE enabled = VALUES(enabled)
");
@@ -141,7 +141,7 @@ function handle_get_notifications(PDO $pdo, array $admin_session): void {
// Get 50 most recent notifications
$stmt = $pdo->prepare("
SELECT id, category, title_key, body, action_url, is_read, created_at
- FROM notifications
+ FROM `" . t('notifications') . "`
WHERE admin_id = ?
ORDER BY created_at DESC
LIMIT 50
@@ -150,7 +150,7 @@ function handle_get_notifications(PDO $pdo, array $admin_session): void {
$notifications = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Get unread count
- $countStmt = $pdo->prepare("SELECT COUNT(*) FROM notifications WHERE admin_id = ? AND is_read = 0");
+ $countStmt = $pdo->prepare("SELECT COUNT(*) FROM `" . t('notifications') . "` WHERE admin_id = ? AND is_read = 0");
$countStmt->execute([$adminId]);
$unreadCount = (int)$countStmt->fetchColumn();
@@ -170,7 +170,7 @@ function handle_send_test_notification(PDO $pdo, array $admin_session, ?array $j
// Insert a bell notification for this admin
$stmt = $pdo->prepare("
- INSERT INTO notifications (admin_id, category, title_key, body, action_url)
+ INSERT INTO `" . t('notifications') . "` (admin_id, category, title_key, body, action_url)
VALUES (?, 'system', 'notif.title.system', ?, 'admin_v2.php#notifications')
");
$testBody = $type === 'sound'
@@ -182,8 +182,8 @@ function handle_send_test_notification(PDO $pdo, array $admin_session, ?array $j
if ($type === 'push') {
$subStmt = $pdo->prepare("
SELECT ps.endpoint, ps.p256dh_key, ps.auth_key, au.preferred_language
- FROM push_subscriptions ps
- JOIN admin_users au ON ps.admin_id = au.id
+ FROM `" . t('push_subscriptions') . "` ps
+ JOIN `" . t('admin_users') . "` au ON ps.admin_id = au.id
WHERE ps.admin_id = ? AND ps.is_active = 1
");
$subStmt->execute([$adminId]);
@@ -220,7 +220,7 @@ function handle_send_test_notification(PDO $pdo, array $admin_session, ?array $j
foreach ($webPush->flush() as $report) {
if ($report->isSubscriptionExpired()) {
$endpoint = $report->getRequest()->getUri()->__toString();
- $pdo->prepare("UPDATE push_subscriptions SET is_active = 0 WHERE endpoint = ?")
+ $pdo->prepare("UPDATE `" . t('push_subscriptions') . "` SET is_active = 0 WHERE endpoint = ?")
->execute([$endpoint]);
}
}
@@ -240,7 +240,7 @@ function handle_mark_notifications_read(PDO $pdo, array $admin_session, ?array $
if ($ids === null || (is_array($ids) && empty($ids))) {
// Mark all as read
- $stmt = $pdo->prepare("UPDATE notifications SET is_read = 1 WHERE admin_id = ? AND is_read = 0");
+ $stmt = $pdo->prepare("UPDATE `" . t('notifications') . "` SET is_read = 1 WHERE admin_id = ? AND is_read = 0");
$stmt->execute([$adminId]);
} elseif (is_array($ids)) {
$intIds = array_map('intval', $ids);
diff --git a/FINAL_PRODUCTION_SYSTEM/controllers/admin/ProductVariantsController.php b/FINAL_PRODUCTION_SYSTEM/controllers/admin/ProductVariantsController.php
index 100843f..fa406e6 100644
--- a/FINAL_PRODUCTION_SYSTEM/controllers/admin/ProductVariantsController.php
+++ b/FINAL_PRODUCTION_SYSTEM/controllers/admin/ProductVariantsController.php
@@ -14,8 +14,8 @@ function handle_get_product_lines(PDO $pdo, array $admin_session, ?array $json_i
$stmt = $pdo->query("
SELECT pl.*,
COUNT(DISTINCT pv.id) AS variant_count
- FROM product_lines pl
- LEFT JOIN product_variants pv ON pv.line_id = pl.id AND pv.is_active = 1
+ FROM `" . t('product_lines') . "` pl
+ LEFT JOIN `" . t('product_variants') . "` pv ON pv.line_id = pl.id AND pv.is_active = 1
GROUP BY pl.id
ORDER BY pl.name
");
@@ -32,7 +32,7 @@ function handle_get_product_line(PDO $pdo, array $admin_session, ?array $json_in
jsonResponse(['success' => false, 'error' => 'Invalid line ID'], 400);
}
- $stmt = $pdo->prepare("SELECT * FROM product_lines WHERE id = ?");
+ $stmt = $pdo->prepare("SELECT * FROM `" . t('product_lines') . "` WHERE id = ?");
$stmt->execute([$id]);
$line = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$line) {
@@ -42,7 +42,7 @@ function handle_get_product_line(PDO $pdo, array $admin_session, ?array $json_in
// Fetch variants
$stmt = $pdo->prepare("
SELECT pv.*
- FROM product_variants pv
+ FROM `" . t('product_variants') . "` pv
WHERE pv.line_id = ? AND pv.is_active = 1
ORDER BY pv.disk_size_min_mb
");
@@ -104,7 +104,7 @@ function handle_save_product_line(PDO $pdo, array $admin_session, ?array $json_i
if ($id > 0) {
// Update
$stmt = $pdo->prepare("
- UPDATE product_lines
+ UPDATE `" . t('product_lines') . "`
SET name = ?, order_pattern = ?, description = ?, enforcement_level = ?, is_active = ?,
secure_boot_enforcement = ?, bios_enforcement = ?, hackbgrt_enforcement = ?,
partition_enforcement = ?, missing_drivers_enforcement = ?
@@ -116,7 +116,7 @@ function handle_save_product_line(PDO $pdo, array $admin_session, ?array $json_i
} else {
// Insert
$stmt = $pdo->prepare("
- INSERT INTO product_lines (name, order_pattern, description, enforcement_level, is_active,
+ INSERT INTO `" . t('product_lines') . "` (name, order_pattern, description, enforcement_level, is_active,
secure_boot_enforcement, bios_enforcement, hackbgrt_enforcement,
partition_enforcement, missing_drivers_enforcement)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
@@ -146,7 +146,7 @@ function handle_delete_product_line(PDO $pdo, array $admin_session, ?array $json
jsonResponse(['success' => false, 'error' => 'Invalid line ID'], 400);
}
- $stmt = $pdo->prepare("UPDATE product_lines SET is_active = 0 WHERE id = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('product_lines') . "` SET is_active = 0 WHERE id = ?");
$stmt->execute([$id]);
logAdminActivity($admin_session['admin_id'], $admin_session['id'], 'PRODUCT_LINE_DELETE', "Deactivated product line ID: $id");
@@ -177,14 +177,14 @@ function handle_save_product_variant(PDO $pdo, array $admin_session, ?array $jso
try {
if ($id > 0) {
$stmt = $pdo->prepare("
- UPDATE product_variants
+ UPDATE `" . t('product_variants') . "`
SET line_id = ?, name = ?, disk_size_min_mb = ?, disk_size_max_mb = ?, is_active = ?
WHERE id = ?
");
$stmt->execute([$lineId, $name, $diskSizeMin, $diskSizeMax, $isActive, $id]);
} else {
$stmt = $pdo->prepare("
- INSERT INTO product_variants (line_id, name, disk_size_min_mb, disk_size_max_mb, is_active)
+ INSERT INTO `" . t('product_variants') . "` (line_id, name, disk_size_min_mb, disk_size_max_mb, is_active)
VALUES (?, ?, ?, ?, ?)
");
$stmt->execute([$lineId, $name, $diskSizeMin, $diskSizeMax, $isActive]);
@@ -192,12 +192,12 @@ function handle_save_product_variant(PDO $pdo, array $admin_session, ?array $jso
}
// Replace partitions: delete existing, insert new
- $stmt = $pdo->prepare("DELETE FROM product_variant_partitions WHERE variant_id = ?");
+ $stmt = $pdo->prepare("DELETE FROM `" . t('product_variant_partitions') . "` WHERE variant_id = ?");
$stmt->execute([$id]);
if (!empty($partitions)) {
$stmt = $pdo->prepare("
- INSERT INTO product_variant_partitions
+ INSERT INTO `" . t('product_variant_partitions') . "`
(variant_id, partition_order, partition_name, partition_type, expected_size_mb, tolerance_percent, is_flexible)
VALUES (?, ?, ?, ?, ?, ?, ?)
");
@@ -257,7 +257,7 @@ function handle_delete_product_variant(PDO $pdo, array $admin_session, ?array $j
jsonResponse(['success' => false, 'error' => 'Invalid variant ID'], 400);
}
- $stmt = $pdo->prepare("UPDATE product_variants SET is_active = 0 WHERE id = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('product_variants') . "` SET is_active = 0 WHERE id = ?");
$stmt->execute([$id]);
logAdminActivity($admin_session['admin_id'], $admin_session['id'], 'PRODUCT_VARIANT_DELETE', "Deactivated variant ID: $id");
diff --git a/FINAL_PRODUCTION_SYSTEM/controllers/admin/ProductionController.php b/FINAL_PRODUCTION_SYSTEM/controllers/admin/ProductionController.php
index ab4452b..ebf473c 100644
--- a/FINAL_PRODUCTION_SYSTEM/controllers/admin/ProductionController.php
+++ b/FINAL_PRODUCTION_SYSTEM/controllers/admin/ProductionController.php
@@ -74,10 +74,10 @@ function handle_get_build_report(PDO $pdo, array $admin_session, $json_input): v
$uuid = $json_input['uuid'] ?? $_GET['uuid'] ?? '';
if ($id > 0) {
- $stmt = $pdo->prepare("SELECT * FROM computer_build_reports WHERE id = ?");
+ $stmt = $pdo->prepare("SELECT * FROM `" . t('computer_build_reports') . "` WHERE id = ?");
$stmt->execute([$id]);
} elseif ($uuid) {
- $stmt = $pdo->prepare("SELECT * FROM computer_build_reports WHERE report_uuid = ?");
+ $stmt = $pdo->prepare("SELECT * FROM `" . t('computer_build_reports') . "` WHERE report_uuid = ?");
$stmt->execute([$uuid]);
} else {
jsonResponse(['success' => false, 'error' => 'Report ID or UUID required']);
@@ -99,7 +99,7 @@ function handle_export_build_report(PDO $pdo, array $admin_session, $json_input)
$id = (int)($json_input['id'] ?? $_GET['id'] ?? 0);
$format = $json_input['format'] ?? $_GET['format'] ?? 'json';
- $stmt = $pdo->prepare("SELECT * FROM computer_build_reports WHERE id = ?");
+ $stmt = $pdo->prepare("SELECT * FROM `" . t('computer_build_reports') . "` WHERE id = ?");
$stmt->execute([$id]);
$report = $stmt->fetch(PDO::FETCH_ASSOC);
@@ -204,7 +204,7 @@ function handle_update_build_report_shipping(PDO $pdo, array $admin_session, $js
}
$params[] = $id;
- $stmt = $pdo->prepare("UPDATE computer_build_reports SET " . implode(', ', $sets) . " WHERE id = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('computer_build_reports') . "` SET " . implode(', ', $sets) . " WHERE id = ?");
$stmt->execute($params);
logAdminActivity($admin_session['admin_id'], $admin_session['id'] ?? 0, 'CBR_SHIPPING_UPDATED', "Updated CBR #{$id} shipping: {$shippingStatus}");
@@ -228,13 +228,13 @@ function handle_get_key_pool_status(PDO $pdo, array $admin_session, $json_input)
SUM(CASE WHEN key_status = 'allocated' THEN 1 ELSE 0 END) AS allocated_keys,
SUM(CASE WHEN key_status = 'good' THEN 1 ELSE 0 END) AS used_keys,
SUM(CASE WHEN key_status = 'bad' THEN 1 ELSE 0 END) AS bad_keys
- FROM oem_keys
+ FROM `" . t('oem_keys') . "`
GROUP BY oem_identifier
");
$pools = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Get pool config
- $configStmt = $pdo->query("SELECT * FROM key_pool_config ORDER BY product_edition");
+ $configStmt = $pdo->query("SELECT * FROM `" . t('key_pool_config') . "` ORDER BY product_edition");
$configs = [];
foreach ($configStmt->fetchAll(PDO::FETCH_ASSOC) as $c) {
$configs[$c['product_edition']] = $c;
@@ -287,7 +287,7 @@ function handle_save_key_pool_config(PDO $pdo, array $admin_session, $json_input
}
$stmt = $pdo->prepare("
- INSERT INTO key_pool_config (product_edition, low_threshold, critical_threshold, auto_notify, notify_email)
+ INSERT INTO `" . t('key_pool_config') . "` (product_edition, low_threshold, critical_threshold, auto_notify, notify_email)
VALUES (?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
low_threshold = VALUES(low_threshold),
@@ -326,15 +326,15 @@ function handle_check_hardware_binding(PDO $pdo, array $admin_session, $json_inp
// Return recent bindings
$stmt = $pdo->query("
SELECT hkb.*, ok.product_key, ok.oem_identifier AS product_type
- FROM hardware_key_bindings hkb
- LEFT JOIN oem_keys ok ON ok.id = hkb.product_key_id
+ FROM `" . t('hardware_key_bindings') . "` hkb
+ LEFT JOIN `" . t('oem_keys') . "` ok ON ok.id = hkb.product_key_id
ORDER BY hkb.bound_at DESC LIMIT 50
");
} else {
$stmt = $pdo->prepare("
SELECT hkb.*, ok.product_key, ok.oem_identifier AS product_type
- FROM hardware_key_bindings hkb
- LEFT JOIN oem_keys ok ON ok.id = hkb.product_key_id
+ FROM `" . t('hardware_key_bindings') . "` hkb
+ LEFT JOIN `" . t('oem_keys') . "` ok ON ok.id = hkb.product_key_id
WHERE " . implode(' AND ', $where) . "
ORDER BY hkb.bound_at DESC LIMIT 50
");
@@ -348,7 +348,7 @@ function handle_check_hardware_binding(PDO $pdo, array $admin_session, $json_inp
if ($keyId > 0) {
$conflictStmt = $pdo->prepare("
SELECT device_fingerprint, motherboard_serial, bound_at
- FROM hardware_key_bindings
+ FROM `" . t('hardware_key_bindings') . "`
WHERE product_key_id = ? AND status = 'active'
");
$conflictStmt->execute([$keyId]);
@@ -371,7 +371,7 @@ function handle_release_hardware_binding(PDO $pdo, array $admin_session, $json_i
}
$stmt = $pdo->prepare("
- UPDATE hardware_key_bindings
+ UPDATE `" . t('hardware_key_bindings') . "`
SET status = 'released', released_at = NOW(), released_by_admin_id = ?
WHERE id = ? AND status = 'active'
");
@@ -411,7 +411,7 @@ function handle_import_dpk_batch(PDO $pdo, array $admin_session): void {
// Create batch record
$batchStmt = $pdo->prepare("
- INSERT INTO dpk_import_batches
+ INSERT INTO `" . t('dpk_import_batches') . "`
(batch_name, import_source, product_edition, source_filename, source_checksum,
imported_by_admin_id, imported_by_username, import_status)
VALUES (?, ?, ?, ?, ?, ?, ?, 'processing')
@@ -446,9 +446,9 @@ function handle_import_dpk_batch(PDO $pdo, array $admin_session): void {
$duplicates = 0;
$failed = 0;
- $checkExisting = $pdo->prepare("SELECT COUNT(*) FROM oem_keys WHERE product_key = ?");
+ $checkExisting = $pdo->prepare("SELECT COUNT(*) FROM `" . t('oem_keys') . "` WHERE product_key = ?");
$insertKey = $pdo->prepare("
- INSERT INTO oem_keys (product_key, oem_identifier, key_status)
+ INSERT INTO `" . t('oem_keys') . "` (product_key, oem_identifier, key_status)
VALUES (?, ?, 'unused')
");
@@ -469,7 +469,7 @@ function handle_import_dpk_batch(PDO $pdo, array $admin_session): void {
// Update batch record
$pdo->prepare("
- UPDATE dpk_import_batches
+ UPDATE `" . t('dpk_import_batches') . "`
SET total_keys = ?, imported_keys = ?, duplicate_keys = ?, failed_keys = ?,
import_status = 'completed', completed_at = NOW()
WHERE id = ?
@@ -483,7 +483,7 @@ function handle_import_dpk_batch(PDO $pdo, array $admin_session): void {
// Update key pool replenishment timestamp
if ($productEdition && $imported > 0) {
$pdo->prepare("
- UPDATE key_pool_config SET last_replenished_at = NOW() WHERE product_edition = ?
+ UPDATE `" . t('key_pool_config') . "` SET last_replenished_at = NOW() WHERE product_edition = ?
")->execute([$productEdition]);
}
@@ -526,7 +526,7 @@ function parseDPKXml(string $content): array {
function handle_list_dpk_batches(PDO $pdo, array $admin_session, $json_input): void {
requirePermission('view_keys', $admin_session);
- $stmt = $pdo->query("SELECT * FROM dpk_import_batches ORDER BY created_at DESC LIMIT 50");
+ $stmt = $pdo->query("SELECT * FROM `" . t('dpk_import_batches') . "` ORDER BY created_at DESC LIMIT 50");
jsonResponse(['success' => true, 'batches' => $stmt->fetchAll(PDO::FETCH_ASSOC)]);
}
@@ -621,13 +621,13 @@ function handle_save_work_order(PDO $pdo, array $admin_session, $json_input): vo
}
$params[] = $id;
- $stmt = $pdo->prepare("UPDATE work_orders SET " . implode(', ', $sets) . " WHERE id = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('work_orders') . "` SET " . implode(', ', $sets) . " WHERE id = ?");
$stmt->execute($params);
} else {
$data['created_by_admin_id'] = (int)$admin_session['admin_id'];
$cols = array_keys($data);
$placeholders = array_fill(0, count($cols), '?');
- $stmt = $pdo->prepare("INSERT INTO work_orders (" . implode(',', $cols) . ") VALUES (" . implode(',', $placeholders) . ")");
+ $stmt = $pdo->prepare("INSERT INTO `" . t('work_orders') . "` (" . implode(',', $cols) . ") VALUES (" . implode(',', $placeholders) . ")");
$stmt->execute(array_values($data));
$id = (int)$pdo->lastInsertId();
}
@@ -646,7 +646,7 @@ function handle_get_work_order(PDO $pdo, array $admin_session, $json_input): voi
return;
}
- $stmt = $pdo->prepare("SELECT * FROM work_orders WHERE id = ?");
+ $stmt = $pdo->prepare("SELECT * FROM `" . t('work_orders') . "` WHERE id = ?");
$stmt->execute([$id]);
$order = $stmt->fetch(PDO::FETCH_ASSOC);
@@ -659,7 +659,7 @@ function handle_get_work_order(PDO $pdo, array $admin_session, $json_input): voi
$cbrStmt = $pdo->prepare("
SELECT id, report_uuid, order_number, activation_status, shipping_status,
motherboard_model, cpu_model, created_at
- FROM computer_build_reports
+ FROM `" . t('computer_build_reports') . "`
WHERE work_order_id = ?
ORDER BY created_at ASC
");
@@ -679,7 +679,7 @@ function handle_delete_work_order(PDO $pdo, array $admin_session, $json_input):
}
// Only allow deleting draft/cancelled orders
- $check = $pdo->prepare("SELECT status, work_order_number FROM work_orders WHERE id = ?");
+ $check = $pdo->prepare("SELECT status, work_order_number FROM `" . t('work_orders') . "` WHERE id = ?");
$check->execute([$id]);
$row = $check->fetch();
@@ -692,7 +692,7 @@ function handle_delete_work_order(PDO $pdo, array $admin_session, $json_input):
return;
}
- $pdo->prepare("DELETE FROM work_orders WHERE id = ?")->execute([$id]);
+ $pdo->prepare("DELETE FROM `" . t('work_orders') . "` WHERE id = ?")->execute([$id]);
logAdminActivity($admin_session['admin_id'], $admin_session['id'] ?? 0, 'WORK_ORDER_DELETED', "Deleted work order: {$row['work_order_number']}");
diff --git a/FINAL_PRODUCTION_SYSTEM/controllers/admin/SecurityController.php b/FINAL_PRODUCTION_SYSTEM/controllers/admin/SecurityController.php
index dd3913d..718eaac 100644
--- a/FINAL_PRODUCTION_SYSTEM/controllers/admin/SecurityController.php
+++ b/FINAL_PRODUCTION_SYSTEM/controllers/admin/SecurityController.php
@@ -9,7 +9,7 @@ function handle_get_2fa_status(PDO $pdo, array $admin_session): void {
try {
$stmt = $pdo->prepare("
SELECT totp_enabled, verified_at, backup_codes
- FROM admin_totp_secrets
+ FROM `" . t('admin_totp_secrets') . "`
WHERE admin_id = ?
");
$stmt->execute([$admin_session['admin_id']]);
@@ -48,8 +48,8 @@ function handle_list_trusted_networks(PDO $pdo, array $admin_session): void {
$stmt = $pdo->query("
SELECT tn.*, au.username as created_by_username
- FROM trusted_networks tn
- LEFT JOIN admin_users au ON tn.created_by_admin_id = au.id
+ FROM `" . t('trusted_networks') . "` tn
+ LEFT JOIN `" . t('admin_users') . "` au ON tn.created_by_admin_id = au.id
ORDER BY tn.created_at DESC
");
$networks = $stmt->fetchAll(PDO::FETCH_ASSOC);
@@ -78,7 +78,7 @@ function handle_add_trusted_network(PDO $pdo, array $admin_session, ?array $json
}
$stmt = $pdo->prepare("
- INSERT INTO trusted_networks (
+ INSERT INTO `" . t('trusted_networks') . "` (
network_name, ip_range, bypass_2fa, allow_usb_auth, description, created_by_admin_id
) VALUES (?, ?, ?, ?, ?, ?)
");
@@ -99,7 +99,7 @@ function handle_delete_trusted_network(PDO $pdo, array $admin_session, ?array $j
$networkId = intval($json_input['network_id'] ?? 0);
- $stmt = $pdo->prepare("SELECT network_name FROM trusted_networks WHERE id = ?");
+ $stmt = $pdo->prepare("SELECT network_name FROM `" . t('trusted_networks') . "` WHERE id = ?");
$stmt->execute([$networkId]);
$network = $stmt->fetch(PDO::FETCH_ASSOC);
@@ -108,7 +108,7 @@ function handle_delete_trusted_network(PDO $pdo, array $admin_session, ?array $j
return;
}
- $stmt = $pdo->prepare("DELETE FROM trusted_networks WHERE id = ?");
+ $stmt = $pdo->prepare("DELETE FROM `" . t('trusted_networks') . "` WHERE id = ?");
$stmt->execute([$networkId]);
logAdminActivity(
diff --git a/FINAL_PRODUCTION_SYSTEM/controllers/admin/TaskPipelineController.php b/FINAL_PRODUCTION_SYSTEM/controllers/admin/TaskPipelineController.php
index 6914b76..bc00b25 100644
--- a/FINAL_PRODUCTION_SYSTEM/controllers/admin/TaskPipelineController.php
+++ b/FINAL_PRODUCTION_SYSTEM/controllers/admin/TaskPipelineController.php
@@ -15,7 +15,7 @@ function handle_list_task_templates(PDO $pdo, array $admin_session, $json_input)
requirePermission('system_settings', $admin_session);
$stmt = $pdo->query("
- SELECT * FROM task_templates
+ SELECT * FROM `" . t('task_templates') . "`
ORDER BY is_system DESC, task_key ASC
");
@@ -54,13 +54,13 @@ function handle_save_task_template(PDO $pdo, array $admin_session, $json_input):
if ($id > 0) {
// Check not editing a system task's key/type
- $existing = $pdo->prepare("SELECT is_system FROM task_templates WHERE id = ?");
+ $existing = $pdo->prepare("SELECT is_system FROM `" . t('task_templates') . "` WHERE id = ?");
$existing->execute([$id]);
$row = $existing->fetch();
if ($row && $row['is_system']) {
// System tasks: only allow editing name, description, timeout, on_failure, icon
$stmt = $pdo->prepare("
- UPDATE task_templates
+ UPDATE `" . t('task_templates') . "`
SET task_name = ?, description = ?, default_timeout_seconds = ?,
default_on_failure = ?, icon = ?
WHERE id = ?
@@ -68,7 +68,7 @@ function handle_save_task_template(PDO $pdo, array $admin_session, $json_input):
$stmt->execute([$taskName, $description, $defaultTimeout, $defaultOnFailure, $icon, $id]);
} else {
$stmt = $pdo->prepare("
- UPDATE task_templates
+ UPDATE `" . t('task_templates') . "`
SET task_key = ?, task_name = ?, task_type = ?, description = ?,
default_code = ?, default_timeout_seconds = ?, default_on_failure = ?, icon = ?
WHERE id = ? AND is_system = 0
@@ -77,7 +77,7 @@ function handle_save_task_template(PDO $pdo, array $admin_session, $json_input):
}
} else {
$stmt = $pdo->prepare("
- INSERT INTO task_templates
+ INSERT INTO `" . t('task_templates') . "`
(task_key, task_name, task_type, description, default_code,
default_timeout_seconds, default_on_failure, is_system, icon)
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)
@@ -101,7 +101,7 @@ function handle_delete_task_template(PDO $pdo, array $admin_session, $json_input
}
// Cannot delete system tasks
- $check = $pdo->prepare("SELECT is_system, task_key FROM task_templates WHERE id = ?");
+ $check = $pdo->prepare("SELECT is_system, task_key FROM `" . t('task_templates') . "` WHERE id = ?");
$check->execute([$id]);
$row = $check->fetch();
if (!$row) {
@@ -114,8 +114,8 @@ function handle_delete_task_template(PDO $pdo, array $admin_session, $json_input
}
// Remove from all product line assignments first
- $pdo->prepare("DELETE FROM product_line_tasks WHERE task_template_id = ?")->execute([$id]);
- $pdo->prepare("DELETE FROM task_templates WHERE id = ? AND is_system = 0")->execute([$id]);
+ $pdo->prepare("DELETE FROM `" . t('product_line_tasks') . "` WHERE task_template_id = ?")->execute([$id]);
+ $pdo->prepare("DELETE FROM `" . t('task_templates') . "` WHERE id = ? AND is_system = 0")->execute([$id]);
logAdminActivity($admin_session['admin_id'], $admin_session['id'] ?? 0, 'TASK_TEMPLATE_DELETED', "Deleted task template: {$row['task_key']} (#{$id})");
@@ -137,8 +137,8 @@ function handle_get_product_line_tasks(PDO $pdo, array $admin_session, $json_inp
SELECT plt.*, tt.task_key, tt.task_name AS template_name, tt.task_type,
tt.description AS template_description, tt.default_code,
tt.default_timeout_seconds, tt.default_on_failure, tt.is_system, tt.icon
- FROM product_line_tasks plt
- JOIN task_templates tt ON tt.id = plt.task_template_id
+ FROM `" . t('product_line_tasks') . "` plt
+ JOIN `" . t('task_templates') . "` tt ON tt.id = plt.task_template_id
WHERE plt.product_line_id = ?
ORDER BY plt.sort_order ASC
");
@@ -165,11 +165,11 @@ function handle_save_product_line_tasks(PDO $pdo, array $admin_session, $json_in
$pdo->beginTransaction();
try {
// Remove existing assignments
- $pdo->prepare("DELETE FROM product_line_tasks WHERE product_line_id = ?")->execute([$productLineId]);
+ $pdo->prepare("DELETE FROM `" . t('product_line_tasks') . "` WHERE product_line_id = ?")->execute([$productLineId]);
// Insert new assignments in order
$insertStmt = $pdo->prepare("
- INSERT INTO product_line_tasks
+ INSERT INTO `" . t('product_line_tasks') . "`
(product_line_id, task_template_id, sort_order, enabled,
custom_name, custom_code, custom_timeout_seconds, custom_on_failure)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
@@ -246,7 +246,7 @@ function handle_get_activation_pipeline(PDO $pdo, array $admin_session, $json_in
SELECT id AS task_template_id, task_key, task_name, task_type,
default_code AS code, default_timeout_seconds AS timeout_seconds,
default_on_failure AS on_failure, 1 AS enabled
- FROM task_templates
+ FROM `" . t('task_templates') . "`
WHERE is_system = 1
ORDER BY id ASC
");
@@ -263,8 +263,8 @@ function handle_get_activation_pipeline(PDO $pdo, array $admin_session, $json_in
COALESCE(plt.custom_timeout_seconds, tt.default_timeout_seconds) AS timeout_seconds,
COALESCE(plt.custom_on_failure, tt.default_on_failure) AS on_failure,
plt.enabled
- FROM product_line_tasks plt
- JOIN task_templates tt ON tt.id = plt.task_template_id
+ FROM `" . t('product_line_tasks') . "` plt
+ JOIN `" . t('task_templates') . "` tt ON tt.id = plt.task_template_id
WHERE plt.product_line_id = ? AND plt.enabled = 1
ORDER BY plt.sort_order ASC
");
@@ -277,7 +277,7 @@ function handle_get_activation_pipeline(PDO $pdo, array $admin_session, $json_in
SELECT id AS task_template_id, task_key, task_name, task_type,
default_code AS code, default_timeout_seconds AS timeout_seconds,
default_on_failure AS on_failure, 1 AS enabled
- FROM task_templates
+ FROM `" . t('task_templates') . "`
WHERE is_system = 1
ORDER BY id ASC
");
@@ -297,7 +297,7 @@ function handle_log_task_execution(PDO $pdo, array $admin_session, $json_input):
}
$stmt = $pdo->prepare("
- INSERT INTO task_execution_log
+ INSERT INTO `" . t('task_execution_log') . "`
(activation_attempt_id, product_line_id, task_template_id, task_key,
task_name, status, started_at, completed_at, duration_ms,
output, error_message, technician_id, order_number)
diff --git a/FINAL_PRODUCTION_SYSTEM/controllers/admin/TechniciansController.php b/FINAL_PRODUCTION_SYSTEM/controllers/admin/TechniciansController.php
index 9980625..8ab6659 100644
--- a/FINAL_PRODUCTION_SYSTEM/controllers/admin/TechniciansController.php
+++ b/FINAL_PRODUCTION_SYSTEM/controllers/admin/TechniciansController.php
@@ -54,7 +54,7 @@ function handle_list_technicians(PDO $pdo, array $admin_session): void {
$stmt = $pdo->query("
SELECT id, technician_id, full_name, email, is_active
- FROM technicians
+ FROM `" . t('technicians') . "`
ORDER BY full_name ASC
");
$technicians = $stmt->fetchAll(PDO::FETCH_ASSOC);
@@ -92,7 +92,7 @@ function handle_add_tech(PDO $pdo, array $admin_session): void {
$pdo->beginTransaction();
// Check + insert inside transaction to prevent TOCTOU race condition
- $stmt = $pdo->prepare("SELECT COUNT(*) FROM technicians WHERE technician_id = ?");
+ $stmt = $pdo->prepare("SELECT COUNT(*) FROM `" . t('technicians') . "` WHERE technician_id = ?");
$stmt->execute([$tech_id]);
if ($stmt->fetchColumn() > 0) {
$pdo->rollBack();
@@ -101,7 +101,7 @@ function handle_add_tech(PDO $pdo, array $admin_session): void {
}
$stmt = $pdo->prepare("
- INSERT INTO technicians (technician_id, password_hash, full_name, email, is_active, preferred_language)
+ INSERT INTO `" . t('technicians') . "` (technician_id, password_hash, full_name, email, is_active, preferred_language)
VALUES (?, ?, ?, ?, ?, ?)
");
$stmt->execute([$tech_id, $password_hash, $full_name, $email, $is_active, $preferred_language]);
@@ -136,7 +136,7 @@ function handle_edit_tech(PDO $pdo, array $admin_session): void {
$is_active = isset($_POST['is_active']) ? 1 : 0;
$stmt = $pdo->prepare("
- UPDATE technicians
+ UPDATE `" . t('technicians') . "`
SET full_name = ?, email = ?, is_active = ?
WHERE id = ?
");
@@ -161,7 +161,7 @@ function handle_get_tech(PDO $pdo, array $admin_session): void {
$stmt = $pdo->prepare("
SELECT id, technician_id, full_name, email, is_active, preferred_server, preferred_language
- FROM technicians
+ FROM `" . t('technicians') . "`
WHERE id = ?
");
$stmt->execute([$techId]);
@@ -200,7 +200,7 @@ function handle_update_tech(PDO $pdo, array $admin_session, ?array $json_input =
}
$stmt = $pdo->prepare("
- UPDATE technicians
+ UPDATE `" . t('technicians') . "`
SET full_name = ?, email = ?, preferred_language = ?, is_active = ?
WHERE id = ?
");
@@ -230,7 +230,7 @@ function handle_reset_password(PDO $pdo, array $admin_session): void {
$password_hash = password_hash($new_password, PASSWORD_BCRYPT, ['cost' => BCRYPT_COST]);
$stmt = $pdo->prepare("
- UPDATE technicians
+ UPDATE `" . t('technicians') . "`
SET password_hash = ?, must_change_password = 1
WHERE id = ?
");
@@ -252,7 +252,7 @@ function handle_toggle_tech(PDO $pdo, array $admin_session): void {
$id = intval($_POST['id'] ?? 0);
$stmt = $pdo->prepare("
- UPDATE technicians
+ UPDATE `" . t('technicians') . "`
SET is_active = NOT is_active
WHERE id = ?
");
@@ -273,7 +273,7 @@ function handle_delete_tech(PDO $pdo, array $admin_session): void {
$id = intval($_POST['id'] ?? 0);
- $stmt = $pdo->prepare("DELETE FROM technicians WHERE id = ?");
+ $stmt = $pdo->prepare("DELETE FROM `" . t('technicians') . "` WHERE id = ?");
$stmt->execute([$id]);
logAdminActivity(
diff --git a/FINAL_PRODUCTION_SYSTEM/controllers/admin/UpgradeController.php b/FINAL_PRODUCTION_SYSTEM/controllers/admin/UpgradeController.php
index 0129469..7325a9c 100644
--- a/FINAL_PRODUCTION_SYSTEM/controllers/admin/UpgradeController.php
+++ b/FINAL_PRODUCTION_SYSTEM/controllers/admin/UpgradeController.php
@@ -34,7 +34,7 @@ function getBackupDir(): string {
}
function loadUpgradeRow(PDO $pdo, int $upgradeId): ?array {
- $stmt = $pdo->prepare("SELECT * FROM upgrade_history WHERE id = ?");
+ $stmt = $pdo->prepare("SELECT * FROM `" . t('upgrade_history') . "` WHERE id = ?");
$stmt->execute([$upgradeId]);
return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
}
@@ -47,7 +47,7 @@ function updateUpgradeStatus(PDO $pdo, int $id, string $status, array $extra = [
$params[] = $val;
}
$params[] = $id;
- $sql = "UPDATE upgrade_history SET " . implode(', ', $sets) . " WHERE id = ?";
+ $sql = "UPDATE `" . t('upgrade_history') . "` SET " . implode(', ', $sets) . " WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
}
@@ -275,7 +275,7 @@ function handle_upgrade_download_github(PDO $pdo, array $admin_session, $json_in
// Check no active upgrade
$stmt = $pdo->prepare("
- SELECT id, status FROM upgrade_history
+ SELECT id, status FROM `" . t('upgrade_history') . "`
WHERE status NOT IN ('completed', 'failed', 'rolled_back')
LIMIT 1
");
@@ -368,7 +368,7 @@ function handle_upgrade_download_github(PDO $pdo, array $admin_session, $json_in
// Create upgrade_history row
$stmt = $pdo->prepare("
- INSERT INTO upgrade_history
+ INSERT INTO `" . t('upgrade_history') . "`
(from_version, to_version, from_version_code, to_version_code,
status, manifest_json, package_filename, package_checksum,
admin_id, admin_username)
@@ -416,7 +416,7 @@ function handle_upgrade_get_status(PDO $pdo, array $admin_session, $json_input):
// Active (non-terminal) upgrade
$stmt = $pdo->query("
- SELECT * FROM upgrade_history
+ SELECT * FROM `" . t('upgrade_history') . "`
WHERE status NOT IN ('completed', 'failed', 'rolled_back')
ORDER BY created_at DESC LIMIT 1
");
@@ -427,7 +427,7 @@ function handle_upgrade_get_status(PDO $pdo, array $admin_session, $json_input):
SELECT id, from_version, to_version, status, package_filename,
error_message, started_at, completed_at, rolled_back_at,
admin_username, created_at
- FROM upgrade_history
+ FROM `" . t('upgrade_history') . "`
ORDER BY created_at DESC LIMIT 10
");
$recentUpgrades = $stmt2->fetchAll(PDO::FETCH_ASSOC);
@@ -511,7 +511,7 @@ function handle_upgrade_upload_package(PDO $pdo, array $admin_session): void {
// Check no active upgrade in progress
$stmt = $pdo->prepare("
- SELECT id, status FROM upgrade_history
+ SELECT id, status FROM `" . t('upgrade_history') . "`
WHERE status NOT IN ('completed', 'failed', 'rolled_back')
LIMIT 1
");
@@ -536,7 +536,7 @@ function handle_upgrade_upload_package(PDO $pdo, array $admin_session): void {
// Create upgrade_history row
$stmt = $pdo->prepare("
- INSERT INTO upgrade_history
+ INSERT INTO `" . t('upgrade_history') . "`
(from_version, to_version, from_version_code, to_version_code,
status, manifest_json, package_filename, package_checksum,
admin_id, admin_username)
@@ -859,7 +859,7 @@ function handle_upgrade_apply(PDO $pdo, array $admin_session, $json_input): void
// Lock the row to prevent concurrent execution
$pdo->beginTransaction();
- $lockStmt = $pdo->prepare("SELECT id FROM upgrade_history WHERE id = ? FOR UPDATE");
+ $lockStmt = $pdo->prepare("SELECT id FROM `" . t('upgrade_history') . "` WHERE id = ? FOR UPDATE");
$lockStmt->execute([$upgradeId]);
$lockStmt->closeCursor();
$pdo->commit();
@@ -899,7 +899,7 @@ function handle_upgrade_apply(PDO $pdo, array $admin_session, $json_input): void
$migVersion = (int)($mig['version'] ?? 0);
// Check if already applied
- $checkStmt = $pdo->prepare("SELECT COUNT(*) FROM schema_versions WHERE filename = ?");
+ $checkStmt = $pdo->prepare("SELECT COUNT(*) FROM `" . t('schema_versions') . "` WHERE filename = ?");
$checkStmt->execute([basename($migFile)]);
$alreadyApplied = (int)$checkStmt->fetchColumn() > 0;
$checkStmt->closeCursor();
@@ -945,7 +945,7 @@ function handle_upgrade_apply(PDO $pdo, array $admin_session, $json_input): void
// Record in schema_versions
$checksum = hash('sha256', $migContent);
- $svStmt = $pdo->prepare("INSERT INTO schema_versions (version, filename, checksum) VALUES (?, ?, ?)");
+ $svStmt = $pdo->prepare("INSERT INTO `" . t('schema_versions') . "` (version, filename, checksum) VALUES (?, ?, ?)");
$svStmt->execute([$migVersion, basename($migFile), $checksum]);
$svStmt->closeCursor();
@@ -1327,7 +1327,7 @@ function handle_upgrade_rollback(PDO $pdo, array $admin_session, $json_input): v
);
$freshPdo->prepare("
- INSERT INTO upgrade_history
+ INSERT INTO `" . t('upgrade_history') . "`
(from_version, to_version, status, error_message,
admin_id, admin_username, rolled_back_at)
VALUES (?, ?, 'rolled_back', ?, ?, ?, ?)
@@ -1363,7 +1363,7 @@ function handle_upgrade_history(PDO $pdo, array $admin_session, $json_input): vo
status, package_filename, error_message,
started_at, completed_at, rolled_back_at,
admin_id, admin_username, created_at
- FROM upgrade_history
+ FROM `" . t('upgrade_history') . "`
ORDER BY created_at DESC
LIMIT 50
");
diff --git a/FINAL_PRODUCTION_SYSTEM/controllers/admin/UsbDevicesController.php b/FINAL_PRODUCTION_SYSTEM/controllers/admin/UsbDevicesController.php
index 888e86e..ad824f1 100644
--- a/FINAL_PRODUCTION_SYSTEM/controllers/admin/UsbDevicesController.php
+++ b/FINAL_PRODUCTION_SYSTEM/controllers/admin/UsbDevicesController.php
@@ -43,7 +43,7 @@ function handle_list_usb_devices(PDO $pdo, array $admin_session): void {
// Get USB device statistics
$stmt = $pdo->query("
SELECT device_status, COUNT(*) as count
- FROM usb_devices
+ FROM `" . t('usb_devices') . "`
GROUP BY device_status
");
$statusCounts = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
@@ -79,7 +79,7 @@ function handle_register_usb_device(PDO $pdo, array $admin_session, ?array $json
}
// Check if technician exists and is active
- $stmt = $pdo->prepare("SELECT technician_id, is_active FROM technicians WHERE technician_id = ?");
+ $stmt = $pdo->prepare("SELECT technician_id, is_active FROM `" . t('technicians') . "` WHERE technician_id = ?");
$stmt->execute([$technicianId]);
$technician = $stmt->fetch(PDO::FETCH_ASSOC);
@@ -94,7 +94,7 @@ function handle_register_usb_device(PDO $pdo, array $admin_session, ?array $json
}
// Check if serial number already exists
- $stmt = $pdo->prepare("SELECT device_id, device_name FROM usb_devices WHERE device_serial_number = ?");
+ $stmt = $pdo->prepare("SELECT device_id, device_name FROM `" . t('usb_devices') . "` WHERE device_serial_number = ?");
$stmt->execute([$deviceSerial]);
$existingDevice = $stmt->fetch(PDO::FETCH_ASSOC);
@@ -109,7 +109,7 @@ function handle_register_usb_device(PDO $pdo, array $admin_session, ?array $json
// Check max devices per technician limit
$maxDevices = (int)getConfig('usb_auth_max_devices_per_tech');
if ($maxDevices > 0) {
- $stmt = $pdo->prepare("SELECT COUNT(*) FROM usb_devices WHERE technician_id = ? AND device_status = 'active'");
+ $stmt = $pdo->prepare("SELECT COUNT(*) FROM `" . t('usb_devices') . "` WHERE technician_id = ? AND device_status = 'active'");
$stmt->execute([$technicianId]);
$currentCount = $stmt->fetchColumn();
@@ -124,7 +124,7 @@ function handle_register_usb_device(PDO $pdo, array $admin_session, ?array $json
// Insert new USB device
$stmt = $pdo->prepare("
- INSERT INTO usb_devices (
+ INSERT INTO `" . t('usb_devices') . "` (
device_serial_number, device_name, technician_id,
device_manufacturer, device_model, device_capacity_gb,
device_description, registered_by_admin_id
@@ -166,7 +166,7 @@ function handle_update_usb_device_status(PDO $pdo, array $admin_session, ?array
}
// Get device info before update
- $stmt = $pdo->prepare("SELECT device_name, technician_id FROM usb_devices WHERE device_id = ?");
+ $stmt = $pdo->prepare("SELECT device_name, technician_id FROM `" . t('usb_devices') . "` WHERE device_id = ?");
$stmt->execute([$deviceId]);
$device = $stmt->fetch(PDO::FETCH_ASSOC);
@@ -178,7 +178,7 @@ function handle_update_usb_device_status(PDO $pdo, array $admin_session, ?array
// Update device status
if ($newStatus === 'active') {
$stmt = $pdo->prepare("
- UPDATE usb_devices
+ UPDATE `" . t('usb_devices') . "`
SET device_status = 'active',
disabled_date = NULL,
disabled_by_admin_id = NULL,
@@ -190,7 +190,7 @@ function handle_update_usb_device_status(PDO $pdo, array $admin_session, ?array
$disableReason = $json_input['reason'] ?? null;
$stmt = $pdo->prepare("
- UPDATE usb_devices
+ UPDATE `" . t('usb_devices') . "`
SET device_status = ?,
disabled_date = NOW(),
disabled_by_admin_id = ?,
@@ -218,7 +218,7 @@ function handle_delete_usb_device(PDO $pdo, array $admin_session, ?array $json_i
$deviceId = intval($json_input['device_id'] ?? 0);
- $stmt = $pdo->prepare("SELECT device_name, technician_id FROM usb_devices WHERE device_id = ?");
+ $stmt = $pdo->prepare("SELECT device_name, technician_id FROM `" . t('usb_devices') . "` WHERE device_id = ?");
$stmt->execute([$deviceId]);
$device = $stmt->fetch(PDO::FETCH_ASSOC);
@@ -227,7 +227,7 @@ function handle_delete_usb_device(PDO $pdo, array $admin_session, ?array $json_i
return;
}
- $stmt = $pdo->prepare("DELETE FROM usb_devices WHERE device_id = ?");
+ $stmt = $pdo->prepare("DELETE FROM `" . t('usb_devices') . "` WHERE device_id = ?");
$stmt->execute([$deviceId]);
logAdminActivity(
diff --git a/FINAL_PRODUCTION_SYSTEM/database/2fa_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/2fa_migration.sql
index 1f27a42..fc1fae9 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/2fa_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/2fa_migration.sql
@@ -6,7 +6,7 @@
-- Table: admin_totp_secrets
-- Stores TOTP secrets and backup codes for admin 2FA
-CREATE TABLE IF NOT EXISTS `admin_totp_secrets` (
+CREATE TABLE IF NOT EXISTS `#__admin_totp_secrets` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`admin_id` INT NOT NULL COMMENT 'Reference to admin_users.id',
`totp_secret` VARCHAR(255) NOT NULL COMMENT 'Base32 encoded TOTP secret',
@@ -18,12 +18,12 @@ CREATE TABLE IF NOT EXISTS `admin_totp_secrets` (
UNIQUE KEY `idx_admin_id` (`admin_id`),
INDEX `idx_enabled` (`totp_enabled`),
- FOREIGN KEY (`admin_id`) REFERENCES `admin_users`(`id`) ON DELETE CASCADE
+ FOREIGN KEY (`admin_id`) REFERENCES `#__admin_users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='TOTP 2FA secrets for admin accounts';
-- Table: trusted_networks
-- Defines network subnets that are trusted for security bypasses
-CREATE TABLE IF NOT EXISTS `trusted_networks` (
+CREATE TABLE IF NOT EXISTS `#__trusted_networks` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`network_name` VARCHAR(100) NOT NULL COMMENT 'Friendly name (e.g., "Office LAN")',
`ip_range` VARCHAR(45) NOT NULL COMMENT 'CIDR notation (e.g., 192.168.1.0/24)',
@@ -38,12 +38,12 @@ CREATE TABLE IF NOT EXISTS `trusted_networks` (
INDEX `idx_active` (`is_active`),
INDEX `idx_bypass_2fa` (`bypass_2fa`, `is_active`),
INDEX `idx_usb_auth` (`allow_usb_auth`, `is_active`),
- FOREIGN KEY (`created_by_admin_id`) REFERENCES `admin_users`(`id`) ON DELETE SET NULL
+ FOREIGN KEY (`created_by_admin_id`) REFERENCES `#__admin_users`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Trusted network subnets for security features';
-- Modify: admin_activity_log
-- Add columns to track 2FA usage and trusted network bypasses
-ALTER TABLE `admin_activity_log`
+ALTER TABLE `#__admin_activity_log`
ADD COLUMN IF NOT EXISTS `totp_verified` TINYINT(1) NULL COMMENT '1=2FA used, 0=bypassed, NULL=not applicable' AFTER `user_agent`,
ADD COLUMN IF NOT EXISTS `trusted_network_id` INT NULL COMMENT 'If bypassed, which network' AFTER `totp_verified`,
ADD INDEX IF NOT EXISTS `idx_totp_verified` (`totp_verified`);
@@ -55,8 +55,8 @@ SET @fk_exists = (SELECT COUNT(*) FROM information_schema.TABLE_CONSTRAINTS
AND CONSTRAINT_NAME = 'fk_admin_activity_log_trusted_network');
SET @sql = IF(@fk_exists = 0,
- 'ALTER TABLE `admin_activity_log` ADD CONSTRAINT `fk_admin_activity_log_trusted_network`
- FOREIGN KEY (`trusted_network_id`) REFERENCES `trusted_networks`(`id`) ON DELETE SET NULL',
+ 'ALTER TABLE `#__admin_activity_log` ADD CONSTRAINT `fk_admin_activity_log_trusted_network`
+ FOREIGN KEY (`trusted_network_id`) REFERENCES `#__trusted_networks`(`id`) ON DELETE SET NULL',
'SELECT "Foreign key already exists"');
PREPARE stmt FROM @sql;
@@ -64,7 +64,7 @@ EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- System configuration for 2FA features
-INSERT INTO `system_config` (`config_key`, `config_value`, `description`) VALUES
+INSERT INTO `#__system_config` (`config_key`, `config_value`, `description`) VALUES
('totp_2fa_available', '1', 'Enable TOTP 2FA feature (1=yes, 0=no)'),
('totp_issuer_name', 'OEM Activation System', 'TOTP issuer name shown in authenticator app'),
('totp_backup_codes_count', '10', 'Number of backup codes to generate per user'),
diff --git a/FINAL_PRODUCTION_SYSTEM/database/acl_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/acl_migration.sql
index 1390bf4..d5fd64f 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/acl_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/acl_migration.sql
@@ -10,7 +10,7 @@
-- ============================================================
-- Permission Categories (groups permissions for UI accordion)
-CREATE TABLE IF NOT EXISTS acl_permission_categories (
+CREATE TABLE IF NOT EXISTS `#__acl_permission_categories` (
id INT AUTO_INCREMENT PRIMARY KEY,
category_key VARCHAR(50) NOT NULL UNIQUE,
display_name VARCHAR(100) NOT NULL,
@@ -19,7 +19,7 @@ CREATE TABLE IF NOT EXISTS acl_permission_categories (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Granular Permissions
-CREATE TABLE IF NOT EXISTS acl_permissions (
+CREATE TABLE IF NOT EXISTS `#__acl_permissions` (
id INT AUTO_INCREMENT PRIMARY KEY,
permission_key VARCHAR(100) NOT NULL UNIQUE,
display_name VARCHAR(100) NOT NULL,
@@ -28,13 +28,13 @@ CREATE TABLE IF NOT EXISTS acl_permissions (
resource_type VARCHAR(50) NOT NULL,
action_type ENUM('view','create','edit','delete','manage','execute') NOT NULL,
is_dangerous TINYINT(1) DEFAULT 0 COMMENT 'Requires confirmation / shown with warning',
- FOREIGN KEY (category_id) REFERENCES acl_permission_categories(id),
+ FOREIGN KEY (category_id) REFERENCES `#__acl_permission_categories`(id),
INDEX idx_resource (resource_type),
INDEX idx_category (category_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Custom Roles
-CREATE TABLE IF NOT EXISTS acl_roles (
+CREATE TABLE IF NOT EXISTS `#__acl_roles` (
id INT AUTO_INCREMENT PRIMARY KEY,
role_name VARCHAR(50) NOT NULL UNIQUE,
display_name VARCHAR(100) NOT NULL,
@@ -51,19 +51,19 @@ CREATE TABLE IF NOT EXISTS acl_roles (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Role <-> Permission Junction
-CREATE TABLE IF NOT EXISTS acl_role_permissions (
+CREATE TABLE IF NOT EXISTS `#__acl_role_permissions` (
id INT AUTO_INCREMENT PRIMARY KEY,
role_id INT NOT NULL,
permission_id INT NOT NULL,
granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
granted_by INT NULL,
UNIQUE KEY unique_role_perm (role_id, permission_id),
- FOREIGN KEY (role_id) REFERENCES acl_roles(id) ON DELETE CASCADE,
- FOREIGN KEY (permission_id) REFERENCES acl_permissions(id) ON DELETE CASCADE
+ FOREIGN KEY (role_id) REFERENCES `#__acl_roles`(id) ON DELETE CASCADE,
+ FOREIGN KEY (permission_id) REFERENCES `#__acl_permissions`(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Per-User Permission Overrides
-CREATE TABLE IF NOT EXISTS acl_user_overrides (
+CREATE TABLE IF NOT EXISTS `#__acl_user_overrides` (
id INT AUTO_INCREMENT PRIMARY KEY,
user_type ENUM('admin','technician') NOT NULL,
user_id INT NOT NULL,
@@ -74,13 +74,13 @@ CREATE TABLE IF NOT EXISTS acl_user_overrides (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INT NULL,
UNIQUE KEY unique_user_perm (user_type, user_id, permission_id),
- FOREIGN KEY (permission_id) REFERENCES acl_permissions(id) ON DELETE CASCADE,
+ FOREIGN KEY (permission_id) REFERENCES `#__acl_permissions`(id) ON DELETE CASCADE,
INDEX idx_user (user_type, user_id),
INDEX idx_expires (expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ACL Change Audit Log
-CREATE TABLE IF NOT EXISTS acl_change_log (
+CREATE TABLE IF NOT EXISTS `#__acl_change_log` (
id INT AUTO_INCREMENT PRIMARY KEY,
actor_id INT NOT NULL,
actor_type ENUM('admin','system') DEFAULT 'admin',
@@ -102,15 +102,15 @@ CREATE TABLE IF NOT EXISTS acl_change_log (
-- ============================================================
-- Add custom_role_id to admin_users (links to acl_roles for new ACL)
-ALTER TABLE admin_users ADD COLUMN custom_role_id INT NULL AFTER role;
-ALTER TABLE admin_users ADD CONSTRAINT fk_admin_acl_role FOREIGN KEY (custom_role_id) REFERENCES acl_roles(id) ON DELETE SET NULL;
+ALTER TABLE `#__admin_users` ADD COLUMN custom_role_id INT NULL AFTER role;
+ALTER TABLE `#__admin_users` ADD CONSTRAINT fk_admin_acl_role FOREIGN KEY (custom_role_id) REFERENCES `#__acl_roles`(id) ON DELETE SET NULL;
-- Add role_id to technicians (links to acl_roles for technician roles)
-ALTER TABLE technicians ADD COLUMN role_id INT NULL AFTER is_active;
-ALTER TABLE technicians ADD CONSTRAINT fk_tech_acl_role FOREIGN KEY (role_id) REFERENCES acl_roles(id) ON DELETE SET NULL;
+ALTER TABLE `#__technicians` ADD COLUMN role_id INT NULL AFTER is_active;
+ALTER TABLE `#__technicians` ADD CONSTRAINT fk_tech_acl_role FOREIGN KEY (role_id) REFERENCES `#__acl_roles`(id) ON DELETE SET NULL;
-- Add acl_v2_enabled config flag (disabled by default for safety)
-INSERT INTO system_config (config_key, config_value, description)
+INSERT INTO `#__system_config` (config_key, config_value, description)
VALUES ('acl_v2_enabled', '0', 'Enable database-driven ACL system (0=legacy hardcoded, 1=new ACL)')
ON DUPLICATE KEY UPDATE config_key = config_key;
@@ -118,7 +118,7 @@ ON DUPLICATE KEY UPDATE config_key = config_key;
-- SEED: Permission Categories
-- ============================================================
-INSERT INTO acl_permission_categories (category_key, display_name, icon, sort_order) VALUES
+INSERT INTO `#__acl_permission_categories` (category_key, display_name, icon, sort_order) VALUES
('dashboard', 'Dashboard & Reports', NULL, 10),
('keys', 'OEM Key Management', NULL, 20),
('technicians', 'Technician Management', NULL, 30),
@@ -134,7 +134,7 @@ INSERT INTO acl_permission_categories (category_key, display_name, icon, sort_or
-- SEED: Granular Permissions (~38)
-- ============================================================
-INSERT INTO acl_permissions (permission_key, display_name, description, category_id, resource_type, action_type, is_dangerous) VALUES
+INSERT INTO `#__acl_permissions` (permission_key, display_name, description, category_id, resource_type, action_type, is_dangerous) VALUES
-- Dashboard (cat 1)
('view_dashboard', 'View Dashboard', 'Access the main dashboard with statistics', 1, 'dashboard', 'view', 0),
('view_reports', 'View Reports', 'Access activation and usage reports', 1, 'dashboard', 'view', 0),
@@ -199,7 +199,7 @@ INSERT INTO acl_permissions (permission_key, display_name, description, category
-- SEED: Roles (7 admin + 2 technician)
-- ============================================================
-INSERT INTO acl_roles (role_name, display_name, description, role_type, is_system_role, priority, color) VALUES
+INSERT INTO `#__acl_roles` (role_name, display_name, description, role_type, is_system_role, priority, color) VALUES
-- Admin roles
('super_admin', 'Super Administrator', 'Full system access including admin management, system settings, backups, and role management', 'admin', 1, 100, '#dc3545'),
('admin', 'Administrator', 'All data operations except delete, admin management, and system settings', 'admin', 1, 80, '#007bff'),
@@ -218,15 +218,15 @@ INSERT INTO acl_roles (role_name, display_name, description, role_type, is_syste
-- Helper: Get role and permission IDs for assignment
-- super_admin: ALL permissions
-INSERT INTO acl_role_permissions (role_id, permission_id)
+INSERT INTO `#__acl_role_permissions` (role_id, permission_id)
SELECT r.id, p.id
-FROM acl_roles r CROSS JOIN acl_permissions p
+FROM `#__acl_roles` r CROSS JOIN `#__acl_permissions` p
WHERE r.role_name = 'super_admin';
-- admin: All view + edit/create operations, NO delete, NO admin mgmt, NO system settings, NO role mgmt
-INSERT INTO acl_role_permissions (role_id, permission_id)
+INSERT INTO `#__acl_role_permissions` (role_id, permission_id)
SELECT r.id, p.id
-FROM acl_roles r, acl_permissions p
+FROM `#__acl_roles` r, acl_permissions p
WHERE r.role_name = 'admin' AND p.permission_key IN (
'view_dashboard', 'view_reports', 'export_data',
'view_keys', 'add_key', 'import_keys', 'edit_key', 'recycle_key',
@@ -239,9 +239,9 @@ WHERE r.role_name = 'admin' AND p.permission_key IN (
);
-- billing_manager: Dashboard, keys (view only), activations (view), reports, export, logs
-INSERT INTO acl_role_permissions (role_id, permission_id)
+INSERT INTO `#__acl_role_permissions` (role_id, permission_id)
SELECT r.id, p.id
-FROM acl_roles r, acl_permissions p
+FROM `#__acl_roles` r, acl_permissions p
WHERE r.role_name = 'billing_manager' AND p.permission_key IN (
'view_dashboard', 'view_reports', 'export_data',
'view_keys',
@@ -250,9 +250,9 @@ WHERE r.role_name = 'billing_manager' AND p.permission_key IN (
);
-- hr_manager: Dashboard, technician CRUD, password reset, role assignment
-INSERT INTO acl_role_permissions (role_id, permission_id)
+INSERT INTO `#__acl_role_permissions` (role_id, permission_id)
SELECT r.id, p.id
-FROM acl_roles r, acl_permissions p
+FROM `#__acl_roles` r, acl_permissions p
WHERE r.role_name = 'hr_manager' AND p.permission_key IN (
'view_dashboard',
'view_technicians', 'add_technician', 'edit_technician', 'reset_tech_password', 'assign_tech_role',
@@ -260,9 +260,9 @@ WHERE r.role_name = 'hr_manager' AND p.permission_key IN (
);
-- qc_inspector: View activations, hardware, add notes, export
-INSERT INTO acl_role_permissions (role_id, permission_id)
+INSERT INTO `#__acl_role_permissions` (role_id, permission_id)
SELECT r.id, p.id
-FROM acl_roles r, acl_permissions p
+FROM `#__acl_roles` r, acl_permissions p
WHERE r.role_name = 'qc_inspector' AND p.permission_key IN (
'view_dashboard', 'view_reports', 'export_data',
'view_activations', 'add_activation_note',
@@ -270,9 +270,9 @@ WHERE r.role_name = 'qc_inspector' AND p.permission_key IN (
);
-- dept_manager: View technicians, activations, hardware, dashboard, add notes
-INSERT INTO acl_role_permissions (role_id, permission_id)
+INSERT INTO `#__acl_role_permissions` (role_id, permission_id)
SELECT r.id, p.id
-FROM acl_roles r, acl_permissions p
+FROM `#__acl_roles` r, acl_permissions p
WHERE r.role_name = 'dept_manager' AND p.permission_key IN (
'view_dashboard', 'view_reports', 'export_data',
'view_technicians',
@@ -282,9 +282,9 @@ WHERE r.role_name = 'dept_manager' AND p.permission_key IN (
);
-- viewer: Read-only across the board
-INSERT INTO acl_role_permissions (role_id, permission_id)
+INSERT INTO `#__acl_role_permissions` (role_id, permission_id)
SELECT r.id, p.id
-FROM acl_roles r, acl_permissions p
+FROM `#__acl_roles` r, acl_permissions p
WHERE r.role_name = 'viewer' AND p.permission_key IN (
'view_dashboard', 'view_reports', 'export_data',
'view_keys',
@@ -297,17 +297,17 @@ WHERE r.role_name = 'viewer' AND p.permission_key IN (
);
-- technician_full: Can activate, submit hardware
-INSERT INTO acl_role_permissions (role_id, permission_id)
+INSERT INTO `#__acl_role_permissions` (role_id, permission_id)
SELECT r.id, p.id
-FROM acl_roles r, acl_permissions p
+FROM `#__acl_roles` r, acl_permissions p
WHERE r.role_name = 'technician_full' AND p.permission_key IN (
'view_keys', 'view_activations', 'view_hardware'
);
-- technician_limited: View only
-INSERT INTO acl_role_permissions (role_id, permission_id)
+INSERT INTO `#__acl_role_permissions` (role_id, permission_id)
SELECT r.id, p.id
-FROM acl_roles r, acl_permissions p
+FROM `#__acl_roles` r, acl_permissions p
WHERE r.role_name = 'technician_limited' AND p.permission_key IN (
'view_keys'
);
@@ -317,6 +317,6 @@ WHERE r.role_name = 'technician_limited' AND p.permission_key IN (
-- Map legacy role ENUM to new custom_role_id
-- ============================================================
-UPDATE admin_users au
-INNER JOIN acl_roles ar ON ar.role_name COLLATE utf8mb4_general_ci = au.role COLLATE utf8mb4_general_ci AND ar.role_type = 'admin'
+UPDATE `#__admin_users` au
+INNER JOIN `#__acl_roles` ar ON ar.role_name COLLATE utf8mb4_general_ci = au.role COLLATE utf8mb4_general_ci AND ar.role_type = 'admin'
SET au.custom_role_id = ar.id;
diff --git a/FINAL_PRODUCTION_SYSTEM/database/backup_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/backup_migration.sql
index 2efd0aa..183ec9a 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/backup_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/backup_migration.sql
@@ -5,7 +5,7 @@
-- Table: backup_history
-- Tracks all database backups (automated and manual)
-CREATE TABLE IF NOT EXISTS `backup_history` (
+CREATE TABLE IF NOT EXISTS `#__backup_history` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`backup_filename` VARCHAR(255) NOT NULL COMMENT 'Filename in backups directory',
`backup_size_mb` DECIMAL(10,2) NOT NULL COMMENT 'Backup file size in megabytes',
@@ -25,12 +25,12 @@ CREATE TABLE IF NOT EXISTS `backup_history` (
INDEX `idx_backup_type` (`backup_type`),
INDEX `idx_deleted_at` (`deleted_at`),
INDEX `idx_filename` (`backup_filename`),
- FOREIGN KEY (`created_by_admin_id`) REFERENCES `admin_users`(`id`) ON DELETE SET NULL
+ FOREIGN KEY (`created_by_admin_id`) REFERENCES `#__admin_users`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Database backup history and tracking';
-- Table: backup_restore_log
-- Tracks database restore operations for disaster recovery audit
-CREATE TABLE IF NOT EXISTS `backup_restore_log` (
+CREATE TABLE IF NOT EXISTS `#__backup_restore_log` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`backup_history_id` INT NULL COMMENT 'Which backup was restored',
`backup_filename` VARCHAR(255) NOT NULL COMMENT 'Backup file used for restore',
@@ -44,12 +44,12 @@ CREATE TABLE IF NOT EXISTS `backup_restore_log` (
INDEX `idx_restored_at` (`restored_at`),
INDEX `idx_restore_status` (`restore_status`),
- FOREIGN KEY (`backup_history_id`) REFERENCES `backup_history`(`id`) ON DELETE SET NULL,
- FOREIGN KEY (`restored_by_admin_id`) REFERENCES `admin_users`(`id`) ON DELETE SET NULL
+ FOREIGN KEY (`backup_history_id`) REFERENCES `#__backup_history`(`id`) ON DELETE SET NULL,
+ FOREIGN KEY (`restored_by_admin_id`) REFERENCES `#__admin_users`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Database restore operation audit log';
-- System configuration for automated backups
-INSERT INTO `system_config` (`config_key`, `config_value`, `description`) VALUES
+INSERT INTO `#__system_config` (`config_key`, `config_value`, `description`) VALUES
('backup_enabled', '1', 'Enable automated database backups (1=yes, 0=no)'),
('backup_retention_days', '30', 'Number of days to keep backups before deletion'),
('backup_schedule', '0 2 * * *', 'Backup cron schedule (default: daily at 2 AM UTC)'),
diff --git a/FINAL_PRODUCTION_SYSTEM/database/client_config_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/client_config_migration.sql
index 2cbda75..bd1b580 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/client_config_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/client_config_migration.sql
@@ -5,7 +5,7 @@
-- activation timing, and network diagnostics settings.
-- =============================================================
-INSERT INTO system_config (config_key, config_value, description) VALUES
+INSERT INTO `#__system_config` (config_key, config_value, description) VALUES
-- Pre-Activation Task Toggles
('client_task_wsus_cleanup', '1', 'Enable WSUS cleanup before activation'),
('client_task_security_hardening', '1', 'Enable SMB security hardening'),
diff --git a/FINAL_PRODUCTION_SYSTEM/database/client_resources_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/client_resources_migration.sql
index ca942ba..ad31235 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/client_resources_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/client_resources_migration.sql
@@ -2,7 +2,7 @@
-- Phase 9: PowerShell 7 Migration — Hosted MSI Installer
-- Run this migration to add the client_resources table and PS7 config entries
-CREATE TABLE IF NOT EXISTS client_resources (
+CREATE TABLE IF NOT EXISTS `#__client_resources` (
id INT AUTO_INCREMENT PRIMARY KEY,
resource_key VARCHAR(100) NOT NULL UNIQUE,
filename VARCHAR(255) NOT NULL,
@@ -14,5 +14,5 @@ CREATE TABLE IF NOT EXISTS client_resources (
uploaded_by INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (uploaded_by) REFERENCES admin_users(id) ON DELETE SET NULL
+ FOREIGN KEY (uploaded_by) REFERENCES `#__admin_users`(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
diff --git a/FINAL_PRODUCTION_SYSTEM/database/create_admin.php b/FINAL_PRODUCTION_SYSTEM/database/create_admin.php
index 455f123..94379d9 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/create_admin.php
+++ b/FINAL_PRODUCTION_SYSTEM/database/create_admin.php
@@ -2,8 +2,8 @@
require __DIR__ . '/../config.php';
$hash = password_hash("Admin2024!", PASSWORD_BCRYPT, ["cost" => 10]);
// Get super_admin role ID from acl_roles
-$roleId = $pdo->query("SELECT id FROM acl_roles WHERE role_name = 'super_admin' LIMIT 1")->fetchColumn() ?: null;
+$roleId = $pdo->query("SELECT id FROM `" . t('acl_roles') . "` WHERE role_name = 'super_admin' LIMIT 1")->fetchColumn() ?: null;
-$stmt = $pdo->prepare("INSERT INTO admin_users (username, password_hash, full_name, email, role, custom_role_id) VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE password_hash = VALUES(password_hash), custom_role_id = VALUES(custom_role_id), failed_login_attempts = 0, locked_until = NULL");
+$stmt = $pdo->prepare("INSERT INTO `" . t('admin_users') . "` (username, password_hash, full_name, email, role, custom_role_id) VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE password_hash = VALUES(password_hash), custom_role_id = VALUES(custom_role_id), failed_login_attempts = 0, locked_until = NULL");
$stmt->execute(["admin", $hash, "Administrator", "admin@localhost", "super_admin", $roleId]);
echo "Admin user created/reset\n";
diff --git a/FINAL_PRODUCTION_SYSTEM/database/database_admin_security.sql b/FINAL_PRODUCTION_SYSTEM/database/database_admin_security.sql
index 0dad74c..e628b04 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/database_admin_security.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/database_admin_security.sql
@@ -3,8 +3,8 @@
USE oem_activation;
--- Admin users table (separate from technicians)
-CREATE TABLE admin_users (
+-- Admin users table (separate from `#__technicians`)
+CREATE TABLE `#__admin_users` (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
full_name VARCHAR(100) NOT NULL,
@@ -22,11 +22,11 @@ CREATE TABLE admin_users (
created_by INT,
INDEX idx_username (username),
INDEX idx_is_active (is_active),
- FOREIGN KEY (created_by) REFERENCES admin_users(id)
+ FOREIGN KEY (created_by) REFERENCES `#__admin_users`(id)
);
-- Admin sessions table
-CREATE TABLE admin_sessions (
+CREATE TABLE `#__admin_sessions` (
id INT AUTO_INCREMENT PRIMARY KEY,
admin_id INT NOT NULL,
session_token VARCHAR(64) NOT NULL UNIQUE,
@@ -36,14 +36,14 @@ CREATE TABLE admin_sessions (
last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
- FOREIGN KEY (admin_id) REFERENCES admin_users(id),
+ FOREIGN KEY (admin_id) REFERENCES `#__admin_users`(id),
INDEX idx_session_token (session_token),
INDEX idx_admin_id (admin_id),
INDEX idx_expires_at (expires_at)
);
-- Admin activity log
-CREATE TABLE admin_activity_log (
+CREATE TABLE `#__admin_activity_log` (
id INT AUTO_INCREMENT PRIMARY KEY,
admin_id INT,
session_id INT,
@@ -52,15 +52,15 @@ CREATE TABLE admin_activity_log (
ip_address VARCHAR(45),
user_agent TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (admin_id) REFERENCES admin_users(id),
- FOREIGN KEY (session_id) REFERENCES admin_sessions(id),
+ FOREIGN KEY (admin_id) REFERENCES `#__admin_users`(id),
+ FOREIGN KEY (session_id) REFERENCES `#__admin_sessions`(id),
INDEX idx_admin_id (admin_id),
INDEX idx_created_at (created_at),
INDEX idx_action (action)
);
-- IP whitelist table
-CREATE TABLE admin_ip_whitelist (
+CREATE TABLE `#__admin_ip_whitelist` (
id INT AUTO_INCREMENT PRIMARY KEY,
ip_address VARCHAR(45) NOT NULL,
ip_range VARCHAR(45),
@@ -68,13 +68,13 @@ CREATE TABLE admin_ip_whitelist (
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INT,
- FOREIGN KEY (created_by) REFERENCES admin_users(id),
+ FOREIGN KEY (created_by) REFERENCES `#__admin_users`(id),
INDEX idx_ip_address (ip_address),
INDEX idx_is_active (is_active)
);
-- Add security configuration
-INSERT INTO system_config (config_key, config_value, description) VALUES
+INSERT INTO `#__system_config` (config_key, config_value, description) VALUES
('admin_session_timeout_minutes', '30', 'Admin session timeout in minutes'),
('admin_max_failed_logins', '3', 'Maximum failed admin login attempts'),
('admin_lockout_duration_minutes', '30', 'Admin account lockout duration'),
@@ -87,12 +87,12 @@ ON DUPLICATE KEY UPDATE config_value = VALUES(config_value);
-- Create initial super admin (password: SuperSecure2024!)
-- Password hash for: SuperSecure2024!
-INSERT INTO admin_users (username, full_name, email, password_hash, role, must_change_password, created_by)
+INSERT INTO `#__admin_users` (username, full_name, email, password_hash, role, must_change_password, created_by)
VALUES ('superadmin', 'Super Administrator', 'admin@yourcompany.com',
'$2y$12$LQv3c1yqBwlVHpPd7u/Dw.G2K2wjDUl9jhJxfTULt3lOAOWuTDBKG',
'super_admin', TRUE, NULL);
-- Add some safe IP addresses (update these for your environment)
--- INSERT INTO admin_ip_whitelist (ip_address, description, created_by) VALUES
+-- INSERT INTO `#__admin_ip_whitelist` (ip_address, description, created_by) VALUES
-- ('192.168.1.0/24', 'Local network', 1),
-- ('10.0.0.0/8', 'Internal network', 1);
\ No newline at end of file
diff --git a/FINAL_PRODUCTION_SYSTEM/database/database_concurrency_indexes.sql b/FINAL_PRODUCTION_SYSTEM/database/database_concurrency_indexes.sql
index 247e840..23c4945 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/database_concurrency_indexes.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/database_concurrency_indexes.sql
@@ -3,35 +3,35 @@
-- Run this after the main database installation
-- Optimize key selection queries (CRITICAL for allocateKeyAtomically)
-ALTER TABLE oem_keys
+ALTER TABLE `#__oem_keys`
ADD INDEX idx_status_fail_date (key_status, fail_counter, last_use_date, id);
-- Optimize session lookups for concurrent access
-ALTER TABLE active_sessions
+ALTER TABLE `#__active_sessions`
ADD INDEX idx_tech_active_expires (technician_id, is_active, expires_at);
-- Optimize activation attempts queries
-ALTER TABLE activation_attempts
+ALTER TABLE `#__activation_attempts`
ADD INDEX idx_key_tech_date (key_id, technician_id, attempted_at);
-- Optimize admin session queries
-ALTER TABLE admin_sessions
+ALTER TABLE `#__admin_sessions`
ADD INDEX idx_admin_active_expires (admin_id, is_active, expires_at);
-- Composite index for common technician queries
-ALTER TABLE technicians
+ALTER TABLE `#__technicians`
ADD INDEX idx_active_locked (is_active, locked_until);
-- Index for cleanup operations (expired sessions)
-ALTER TABLE active_sessions
+ALTER TABLE `#__active_sessions`
ADD INDEX idx_expires_active (expires_at, is_active);
-- Index for audit trail queries
-ALTER TABLE admin_activity_log
+ALTER TABLE `#__admin_activity_log`
ADD INDEX idx_admin_action_time (admin_id, action, created_at);
-- Index for key usage statistics
-ALTER TABLE oem_keys
+ALTER TABLE `#__oem_keys`
ADD INDEX idx_first_usage (first_usage_date, key_status);
-- Update table statistics for better query planning
diff --git a/FINAL_PRODUCTION_SYSTEM/database/database_setup.sql b/FINAL_PRODUCTION_SYSTEM/database/database_setup.sql
index 5d9c5d2..56caca0 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/database_setup.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/database_setup.sql
@@ -5,7 +5,7 @@ CREATE DATABASE IF NOT EXISTS oem_activation;
USE oem_activation;
-- Table to store OEM keys
-CREATE TABLE oem_keys (
+CREATE TABLE `#__oem_keys` (
id INT AUTO_INCREMENT PRIMARY KEY,
product_key VARCHAR(29) NOT NULL UNIQUE,
oem_identifier VARCHAR(20) NOT NULL,
@@ -21,7 +21,7 @@ CREATE TABLE oem_keys (
);
-- Table to track activation attempts
-CREATE TABLE activation_attempts (
+CREATE TABLE `#__activation_attempts` (
id INT AUTO_INCREMENT PRIMARY KEY,
key_id INT NOT NULL,
order_number VARCHAR(10) NOT NULL,
@@ -31,15 +31,15 @@ CREATE TABLE activation_attempts (
attempted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
client_ip VARCHAR(45),
notes TEXT,
- FOREIGN KEY (key_id) REFERENCES oem_keys(id),
- FOREIGN KEY (technician_id) REFERENCES technicians(technician_id),
+ FOREIGN KEY (key_id) REFERENCES `#__oem_keys`(id),
+ FOREIGN KEY (technician_id) REFERENCES `#__technicians`(technician_id),
INDEX idx_order_number (order_number),
INDEX idx_technician_id (technician_id),
INDEX idx_attempted_at (attempted_at)
);
-- Table to store active sessions/tokens
-CREATE TABLE active_sessions (
+CREATE TABLE `#__active_sessions` (
id INT AUTO_INCREMENT PRIMARY KEY,
technician_id VARCHAR(20) NOT NULL,
session_token VARCHAR(64) NOT NULL UNIQUE,
@@ -48,14 +48,14 @@ CREATE TABLE active_sessions (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
- FOREIGN KEY (key_id) REFERENCES oem_keys(id),
+ FOREIGN KEY (key_id) REFERENCES `#__oem_keys`(id),
INDEX idx_session_token (session_token),
INDEX idx_technician_id (technician_id),
INDEX idx_expires_at (expires_at)
);
-- Table for system configuration
-CREATE TABLE system_config (
+CREATE TABLE `#__system_config` (
config_key VARCHAR(50) PRIMARY KEY,
config_value TEXT NOT NULL,
description TEXT,
@@ -63,7 +63,7 @@ CREATE TABLE system_config (
);
-- Insert basic configuration
-INSERT INTO system_config (config_key, config_value, description) VALUES
+INSERT INTO `#__system_config` (config_key, config_value, description) VALUES
('smtp_server', 'smtp.zoho.com', 'SMTP server for notifications'),
('smtp_port', '587', 'SMTP port'),
('smtp_username', 'oem.activation@roo24.chesnotech.ru', 'SMTP username'),
diff --git a/FINAL_PRODUCTION_SYSTEM/database/database_setup_with_users.sql b/FINAL_PRODUCTION_SYSTEM/database/database_setup_with_users.sql
index aeb3e77..cee0f75 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/database_setup_with_users.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/database_setup_with_users.sql
@@ -5,7 +5,7 @@ CREATE DATABASE IF NOT EXISTS oem_activation;
USE oem_activation;
-- Table to store technician accounts
-CREATE TABLE technicians (
+CREATE TABLE `#__technicians` (
id INT AUTO_INCREMENT PRIMARY KEY,
technician_id VARCHAR(20) NOT NULL UNIQUE,
full_name VARCHAR(100) NOT NULL,
@@ -25,7 +25,7 @@ CREATE TABLE technicians (
);
-- Table to store OEM keys
-CREATE TABLE oem_keys (
+CREATE TABLE `#__oem_keys` (
id INT AUTO_INCREMENT PRIMARY KEY,
product_key VARCHAR(29) NOT NULL UNIQUE,
oem_identifier VARCHAR(20) NOT NULL,
@@ -41,7 +41,7 @@ CREATE TABLE oem_keys (
);
-- Table to track activation attempts
-CREATE TABLE activation_attempts (
+CREATE TABLE `#__activation_attempts` (
id INT AUTO_INCREMENT PRIMARY KEY,
key_id INT NOT NULL,
technician_id VARCHAR(20) NOT NULL,
@@ -51,15 +51,15 @@ CREATE TABLE activation_attempts (
attempted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
client_ip VARCHAR(45),
notes TEXT,
- FOREIGN KEY (key_id) REFERENCES oem_keys(id),
- FOREIGN KEY (technician_id) REFERENCES technicians(technician_id),
+ FOREIGN KEY (key_id) REFERENCES `#__oem_keys`(id),
+ FOREIGN KEY (technician_id) REFERENCES `#__technicians`(technician_id),
INDEX idx_order_number (order_number),
INDEX idx_technician_id (technician_id),
INDEX idx_attempted_at (attempted_at)
);
-- Table to store active sessions/tokens
-CREATE TABLE active_sessions (
+CREATE TABLE `#__active_sessions` (
id INT AUTO_INCREMENT PRIMARY KEY,
technician_id VARCHAR(20) NOT NULL,
session_token VARCHAR(64) NOT NULL UNIQUE,
@@ -68,15 +68,15 @@ CREATE TABLE active_sessions (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
- FOREIGN KEY (key_id) REFERENCES oem_keys(id),
- FOREIGN KEY (technician_id) REFERENCES technicians(technician_id),
+ FOREIGN KEY (key_id) REFERENCES `#__oem_keys`(id),
+ FOREIGN KEY (technician_id) REFERENCES `#__technicians`(technician_id),
INDEX idx_session_token (session_token),
INDEX idx_technician_id (technician_id),
INDEX idx_expires_at (expires_at)
);
-- Table for system configuration
-CREATE TABLE system_config (
+CREATE TABLE `#__system_config` (
config_key VARCHAR(50) PRIMARY KEY,
config_value TEXT NOT NULL,
description TEXT,
@@ -84,20 +84,20 @@ CREATE TABLE system_config (
);
-- Table for password reset tokens
-CREATE TABLE password_reset_tokens (
+CREATE TABLE `#__password_reset_tokens` (
id INT AUTO_INCREMENT PRIMARY KEY,
technician_id VARCHAR(20) NOT NULL,
reset_token VARCHAR(64) NOT NULL UNIQUE,
expires_at TIMESTAMP NOT NULL,
used_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (technician_id) REFERENCES technicians(technician_id),
+ FOREIGN KEY (technician_id) REFERENCES `#__technicians`(technician_id),
INDEX idx_reset_token (reset_token),
INDEX idx_expires_at (expires_at)
);
-- Insert basic configuration
-INSERT INTO system_config (config_key, config_value, description) VALUES
+INSERT INTO `#__system_config` (config_key, config_value, description) VALUES
('smtp_server', 'smtp.zoho.com', 'SMTP server for notifications'),
('smtp_port', '587', 'SMTP port'),
('smtp_username', 'oem.activation@roo24.chesnotech.ru', 'SMTP username'),
@@ -115,13 +115,13 @@ INSERT INTO system_config (config_key, config_value, description) VALUES
('show_full_keys_in_admin', '0', 'Show full product keys in admin panel (1=yes, 0=no - admin only)');
-- Create default admin account (password: admin123 - CHANGE THIS!)
-INSERT INTO technicians (technician_id, full_name, email, password_hash, must_change_password, created_by, notes)
+INSERT INTO `#__technicians` (technician_id, full_name, email, password_hash, must_change_password, created_by, notes)
VALUES ('admin', 'System Administrator', 'admin@yourcompany.com',
'$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
TRUE, 'system', 'Default admin account - change password immediately');
-- Create sample technician account (password: temp123)
-INSERT INTO technicians (technician_id, full_name, email, password_hash, temp_password, must_change_password, created_by, notes)
+INSERT INTO `#__technicians` (technician_id, full_name, email, password_hash, temp_password, must_change_password, created_by, notes)
VALUES ('tech001', 'John Technician', 'tech001@yourcompany.com',
'$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
'temp123', TRUE, 'admin', 'Sample technician account');
\ No newline at end of file
diff --git a/FINAL_PRODUCTION_SYSTEM/database/docker-init/00-init.sh b/FINAL_PRODUCTION_SYSTEM/database/docker-init/00-init.sh
index bac8e58..fb6bcc1 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/docker-init/00-init.sh
+++ b/FINAL_PRODUCTION_SYSTEM/database/docker-init/00-init.sh
@@ -15,16 +15,45 @@ DB="${MARIADB_DATABASE:-oem_activation}"
SQL_DIR="/docker-entrypoint-initdb.d/sql"
MYSQL_CMD="mysql -u root -p${MARIADB_ROOT_PASSWORD} ${DB}"
+# ── Table prefix support ─────────────────────────────────────
+# SQL files use the Joomla-style sentinel `#__` for table names.
+# At init time we substitute it with $KEYGATE_DB_PREFIX (default: empty
+# string → identical schema to pre-prefix releases).
+PREFIX="${KEYGATE_DB_PREFIX:-}"
+SV_TABLE="${PREFIX}schema_versions"
+
+# Validate prefix: must match ^[a-z][a-z0-9_]{0,9}$ or be empty.
+if [ -n "$PREFIX" ]; then
+ if ! echo "$PREFIX" | grep -qE '^[a-z][a-z0-9_]{0,9}$'; then
+ echo "[ERROR] Invalid KEYGATE_DB_PREFIX='$PREFIX'. Must match ^[a-z][a-z0-9_]{0,9}\$ or be empty."
+ exit 1
+ fi
+fi
+
echo "=== KeyGate: Database Initialization ==="
echo "Database: $DB"
echo "SQL directory: $SQL_DIR"
+echo "Table prefix: ${PREFIX:-}"
+
+# ── Substitute #__ → $PREFIX for every .sql file in $SQL_DIR ──
+# We copy to /tmp/keygate-sql/ so the original mounted volume stays
+# untouched (read-only on some setups).
+STAGING="/tmp/keygate-sql"
+mkdir -p "$STAGING"
+for f in "$SQL_DIR"/*.sql; do
+ [ -f "$f" ] || continue
+ base="$(basename "$f")"
+ # sed -i won't work on read-only mounts; pipe through to a fresh file.
+ sed "s/#__/${PREFIX}/g" "$f" > "$STAGING/$base"
+done
+SQL_DIR="$STAGING"
# ── Step 0: Ensure schema_versions table exists ──────────────
# This must run unconditionally so the tracking table is always present.
$MYSQL_CMD < "$SQL_DIR/schema_versions_migration.sql" 2>/dev/null || true
# ── Idempotent migration runner ──────────────────────────────
-# Checks schema_versions before running; records after success.
+# Checks ${PREFIX}schema_versions before running; records after success.
run_sql() {
local file="$SQL_DIR/$1"
local version="$2"
@@ -36,7 +65,7 @@ run_sql() {
# Check if already applied
local applied
- applied=$($MYSQL_CMD -N -e "SELECT COUNT(*) FROM schema_versions WHERE filename = '$1'" 2>/dev/null || echo "0")
+ applied=$($MYSQL_CMD -N -e "SELECT COUNT(*) FROM \`${SV_TABLE}\` WHERE filename = '$1'" 2>/dev/null || echo "0")
if [ "$applied" -gt 0 ]; then
echo "[SKIP] Already applied: $1"
@@ -48,11 +77,18 @@ run_sql() {
echo "[WARN] Non-fatal errors in $1 (continuing)"
fi
- # Compute checksum and record
+ # Compute checksum from the original (un-substituted) file so the
+ # checksum is stable regardless of prefix choice. Falls back to staged
+ # copy if the original isn't accessible.
+ local original="/docker-entrypoint-initdb.d/sql/$1"
local checksum
- checksum=$(sha256sum "$file" | cut -d' ' -f1)
+ if [ -f "$original" ]; then
+ checksum=$(sha256sum "$original" | cut -d' ' -f1)
+ else
+ checksum=$(sha256sum "$file" | cut -d' ' -f1)
+ fi
- $MYSQL_CMD -e "INSERT INTO schema_versions (version, filename, checksum) VALUES ($version, '$1', '$checksum')"
+ $MYSQL_CMD -e "INSERT INTO \`${SV_TABLE}\` (version, filename, checksum) VALUES ($version, '$1', '$checksum')"
echo "[DONE] Applied: $1"
}
diff --git a/FINAL_PRODUCTION_SYSTEM/database/downloads_acl_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/downloads_acl_migration.sql
index d5da7bd..e66a039 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/downloads_acl_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/downloads_acl_migration.sql
@@ -5,33 +5,33 @@
-- ============================================================
-- Add "Downloads" permission category
-INSERT INTO acl_permission_categories (category_key, display_name, icon, sort_order)
+INSERT INTO `#__acl_permission_categories` (category_key, display_name, icon, sort_order)
VALUES ('downloads', 'Client Downloads', NULL, 55)
ON DUPLICATE KEY UPDATE display_name = VALUES(display_name);
-- Add download permissions
-INSERT INTO acl_permissions (permission_key, display_name, description, category_id, resource_type, action_type, is_dangerous)
+INSERT INTO `#__acl_permissions` (permission_key, display_name, description, category_id, resource_type, action_type, is_dangerous)
SELECT 'view_downloads', 'View Downloads', 'View and download client tools (launcher, PS7 installer, extensions)',
c.id, 'downloads', 'view', 0
-FROM acl_permission_categories c WHERE c.category_key = 'downloads'
+FROM `#__acl_permission_categories` c WHERE c.category_key = 'downloads'
ON DUPLICATE KEY UPDATE display_name = VALUES(display_name);
-INSERT INTO acl_permissions (permission_key, display_name, description, category_id, resource_type, action_type, is_dangerous)
+INSERT INTO `#__acl_permissions` (permission_key, display_name, description, category_id, resource_type, action_type, is_dangerous)
SELECT 'manage_downloads', 'Manage Downloads', 'Upload, replace, and delete client resources',
c.id, 'downloads', 'manage', 1
-FROM acl_permission_categories c WHERE c.category_key = 'downloads'
+FROM `#__acl_permission_categories` c WHERE c.category_key = 'downloads'
ON DUPLICATE KEY UPDATE display_name = VALUES(display_name);
-- Grant both permissions to super_admin role
INSERT IGNORE INTO acl_role_permissions (role_id, permission_id)
SELECT r.id, p.id
-FROM acl_roles r CROSS JOIN acl_permissions p
+FROM `#__acl_roles` r CROSS JOIN `#__acl_permissions` p
WHERE r.role_name = 'super_admin'
AND p.permission_key IN ('view_downloads', 'manage_downloads');
-- Grant view_downloads to admin role
INSERT IGNORE INTO acl_role_permissions (role_id, permission_id)
SELECT r.id, p.id
-FROM acl_roles r, acl_permissions p
+FROM `#__acl_roles` r, acl_permissions p
WHERE r.role_name = 'admin'
AND p.permission_key = 'view_downloads';
diff --git a/FINAL_PRODUCTION_SYSTEM/database/hardware_info_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/hardware_info_migration.sql
index cee3e96..8dde0c3 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/hardware_info_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/hardware_info_migration.sql
@@ -3,12 +3,12 @@
-- Purpose: Make OEM ID and Roll Serial optional, add hardware tracking
-- Step 1: Make OEM ID and Roll Serial optional in oem_keys table
-ALTER TABLE oem_keys
+ALTER TABLE `#__oem_keys`
MODIFY COLUMN oem_identifier VARCHAR(20) NULL DEFAULT NULL,
MODIFY COLUMN roll_serial VARCHAR(20) NULL DEFAULT NULL;
-- Step 2: Create hardware_info table for tracking PC hardware details
-CREATE TABLE IF NOT EXISTS hardware_info (
+CREATE TABLE IF NOT EXISTS `#__hardware_info` (
id INT AUTO_INCREMENT PRIMARY KEY,
activation_id INT NOT NULL COMMENT 'Links to activation_attempts.id',
order_number VARCHAR(10) NOT NULL COMMENT 'Order number for easy reference',
@@ -60,11 +60,11 @@ CREATE TABLE IF NOT EXISTS hardware_info (
INDEX idx_order_number (order_number),
INDEX idx_collected_at (collected_at),
- FOREIGN KEY (activation_id) REFERENCES activation_attempts(id) ON DELETE CASCADE
+ FOREIGN KEY (activation_id) REFERENCES `#__activation_attempts`(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Hardware information collected during activation';
-- Step 3: Add hardware_collected flag to activation_attempts
-ALTER TABLE activation_attempts
+ALTER TABLE `#__activation_attempts`
ADD COLUMN hardware_collected TINYINT(1) DEFAULT 0 COMMENT 'Whether hardware info was collected for this activation';
-- Step 4: Create view for easy hardware lookup by order number
@@ -86,12 +86,12 @@ SELECT
h.ram_total_capacity_gb,
h.secure_boot_enabled,
h.collected_at AS hardware_collected_at
-FROM activation_attempts a
-LEFT JOIN technicians t ON a.technician_id = t.technician_id
-LEFT JOIN oem_keys k ON a.key_id = k.id
-LEFT JOIN hardware_info h ON h.activation_id = a.id
+FROM `#__activation_attempts` a
+LEFT JOIN `#__technicians` t ON a.technician_id = t.technician_id
+LEFT JOIN `#__oem_keys` k ON a.key_id = k.id
+LEFT JOIN `#__hardware_info` h ON h.activation_id = a.id
ORDER BY a.attempted_at DESC;
-- Step 5: (Optional) Add secure_boot_enabled column if table already exists without it
-- Run this only if you applied the migration before this column was added:
--- ALTER TABLE hardware_info ADD COLUMN secure_boot_enabled TINYINT(1) NULL COMMENT 'Whether Secure Boot is enabled (1=yes, 0=no, NULL=unknown)' AFTER os_architecture;
+-- ALTER TABLE `#__hardware_info` ADD COLUMN secure_boot_enabled TINYINT(1) NULL COMMENT 'Whether Secure Boot is enabled (1=yes, 0=no, NULL=unknown)' AFTER os_architecture;
diff --git a/FINAL_PRODUCTION_SYSTEM/database/hardware_info_v2_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/hardware_info_v2_migration.sql
index ae57497..aa65dbc 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/hardware_info_v2_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/hardware_info_v2_migration.sql
@@ -5,48 +5,48 @@
-- Add new columns to hardware_info table
-- Chassis / Enclosure Information
-ALTER TABLE hardware_info ADD COLUMN chassis_manufacturer VARCHAR(100) NULL AFTER computer_name;
-ALTER TABLE hardware_info ADD COLUMN chassis_serial VARCHAR(100) NULL AFTER chassis_manufacturer;
-ALTER TABLE hardware_info ADD COLUMN chassis_type VARCHAR(50) NULL AFTER chassis_serial;
+ALTER TABLE `#__hardware_info` ADD COLUMN chassis_manufacturer VARCHAR(100) NULL AFTER computer_name;
+ALTER TABLE `#__hardware_info` ADD COLUMN chassis_serial VARCHAR(100) NULL AFTER chassis_manufacturer;
+ALTER TABLE `#__hardware_info` ADD COLUMN chassis_type VARCHAR(50) NULL AFTER chassis_serial;
-- System Product Information (OEM)
-ALTER TABLE hardware_info ADD COLUMN system_manufacturer VARCHAR(100) NULL AFTER chassis_type;
-ALTER TABLE hardware_info ADD COLUMN system_product_name VARCHAR(200) NULL AFTER system_manufacturer;
-ALTER TABLE hardware_info ADD COLUMN system_serial VARCHAR(100) NULL AFTER system_product_name;
-ALTER TABLE hardware_info ADD COLUMN system_uuid VARCHAR(50) NULL AFTER system_serial;
+ALTER TABLE `#__hardware_info` ADD COLUMN system_manufacturer VARCHAR(100) NULL AFTER chassis_type;
+ALTER TABLE `#__hardware_info` ADD COLUMN system_product_name VARCHAR(200) NULL AFTER system_manufacturer;
+ALTER TABLE `#__hardware_info` ADD COLUMN system_serial VARCHAR(100) NULL AFTER system_product_name;
+ALTER TABLE `#__hardware_info` ADD COLUMN system_uuid VARCHAR(50) NULL AFTER system_serial;
-- TPM Information
-ALTER TABLE hardware_info ADD COLUMN tpm_present TINYINT(1) NULL AFTER system_uuid;
-ALTER TABLE hardware_info ADD COLUMN tpm_version VARCHAR(50) NULL AFTER tpm_present;
-ALTER TABLE hardware_info ADD COLUMN tpm_manufacturer VARCHAR(100) NULL AFTER tpm_version;
+ALTER TABLE `#__hardware_info` ADD COLUMN tpm_present TINYINT(1) NULL AFTER system_uuid;
+ALTER TABLE `#__hardware_info` ADD COLUMN tpm_version VARCHAR(50) NULL AFTER tpm_present;
+ALTER TABLE `#__hardware_info` ADD COLUMN tpm_manufacturer VARCHAR(100) NULL AFTER tpm_version;
-- CPU Serial (Processor ID)
-ALTER TABLE hardware_info ADD COLUMN cpu_serial VARCHAR(50) NULL AFTER cpu_max_clock_speed;
+ALTER TABLE `#__hardware_info` ADD COLUMN cpu_serial VARCHAR(50) NULL AFTER cpu_max_clock_speed;
-- Network Information
-ALTER TABLE hardware_info ADD COLUMN primary_mac_address VARCHAR(20) NULL AFTER computer_name;
-ALTER TABLE hardware_info ADD COLUMN local_ip VARCHAR(45) NULL AFTER primary_mac_address;
-ALTER TABLE hardware_info ADD COLUMN public_ip VARCHAR(45) NULL AFTER local_ip;
-ALTER TABLE hardware_info ADD COLUMN network_adapters JSON NULL COMMENT 'Array of network adapter details with MAC, IP, etc.' AFTER public_ip;
+ALTER TABLE `#__hardware_info` ADD COLUMN primary_mac_address VARCHAR(20) NULL AFTER computer_name;
+ALTER TABLE `#__hardware_info` ADD COLUMN local_ip VARCHAR(45) NULL AFTER primary_mac_address;
+ALTER TABLE `#__hardware_info` ADD COLUMN public_ip VARCHAR(45) NULL AFTER local_ip;
+ALTER TABLE `#__hardware_info` ADD COLUMN network_adapters JSON NULL COMMENT 'Array of network adapter details with MAC, IP, etc.' AFTER public_ip;
-- Audio Devices
-ALTER TABLE hardware_info ADD COLUMN audio_devices JSON NULL COMMENT 'Array of sound device details' AFTER network_adapters;
+ALTER TABLE `#__hardware_info` ADD COLUMN audio_devices JSON NULL COMMENT 'Array of sound device details' AFTER network_adapters;
-- Monitor Information
-ALTER TABLE hardware_info ADD COLUMN monitors JSON NULL COMMENT 'Array of connected monitor details with serials' AFTER audio_devices;
+ALTER TABLE `#__hardware_info` ADD COLUMN monitors JSON NULL COMMENT 'Array of connected monitor details with serials' AFTER audio_devices;
-- OS Extended Info
-ALTER TABLE hardware_info ADD COLUMN os_build_number VARCHAR(20) NULL AFTER os_architecture;
-ALTER TABLE hardware_info ADD COLUMN os_install_date VARCHAR(50) NULL AFTER os_build_number;
-ALTER TABLE hardware_info ADD COLUMN os_serial_number VARCHAR(100) NULL AFTER os_install_date;
+ALTER TABLE `#__hardware_info` ADD COLUMN os_build_number VARCHAR(20) NULL AFTER os_architecture;
+ALTER TABLE `#__hardware_info` ADD COLUMN os_install_date VARCHAR(50) NULL AFTER os_build_number;
+ALTER TABLE `#__hardware_info` ADD COLUMN os_serial_number VARCHAR(100) NULL AFTER os_install_date;
-- Device Fingerprint (composite unique identifier)
-ALTER TABLE hardware_info ADD COLUMN device_fingerprint VARCHAR(500) NULL COMMENT 'Composite hardware fingerprint for duplicate detection' AFTER collection_method;
+ALTER TABLE `#__hardware_info` ADD COLUMN device_fingerprint VARCHAR(500) NULL COMMENT 'Composite hardware fingerprint for duplicate detection' AFTER collection_method;
-- Index for device fingerprint lookups
-ALTER TABLE hardware_info ADD INDEX idx_device_fingerprint (device_fingerprint(255));
-ALTER TABLE hardware_info ADD INDEX idx_public_ip (public_ip);
-ALTER TABLE hardware_info ADD INDEX idx_primary_mac (primary_mac_address);
+ALTER TABLE `#__hardware_info` ADD INDEX idx_device_fingerprint (device_fingerprint(255));
+ALTER TABLE `#__hardware_info` ADD INDEX idx_public_ip (public_ip);
+ALTER TABLE `#__hardware_info` ADD INDEX idx_primary_mac (primary_mac_address);
-- Update the view to include new network fields
CREATE OR REPLACE VIEW v_activation_hardware AS
@@ -73,8 +73,8 @@ SELECT
h.system_uuid,
h.device_fingerprint,
h.collected_at AS hardware_collected_at
-FROM activation_attempts a
-LEFT JOIN technicians t ON a.technician_id = t.technician_id
-LEFT JOIN oem_keys k ON a.key_id = k.id
-LEFT JOIN hardware_info h ON h.activation_id = a.id
+FROM `#__activation_attempts` a
+LEFT JOIN `#__technicians` t ON a.technician_id = t.technician_id
+LEFT JOIN `#__oem_keys` k ON a.key_id = k.id
+LEFT JOIN `#__hardware_info` h ON h.activation_id = a.id
ORDER BY a.attempted_at DESC;
diff --git a/FINAL_PRODUCTION_SYSTEM/database/hash_temp_passwords.php b/FINAL_PRODUCTION_SYSTEM/database/hash_temp_passwords.php
index fb7e21d..322e7f5 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/hash_temp_passwords.php
+++ b/FINAL_PRODUCTION_SYSTEM/database/hash_temp_passwords.php
@@ -8,7 +8,7 @@
echo "Hashing plaintext temp_passwords...\n";
-$stmt = $pdo->query("SELECT id, temp_password FROM technicians WHERE temp_password IS NOT NULL AND temp_password != ''");
+$stmt = $pdo->query("SELECT id, temp_password FROM `" . t('technicians') . "` WHERE temp_password IS NOT NULL AND temp_password != ''");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$updated = 0;
@@ -20,7 +20,7 @@
}
$hashed = password_hash($row['temp_password'], PASSWORD_BCRYPT, ['cost' => BCRYPT_COST]);
- $update = $pdo->prepare("UPDATE technicians SET temp_password = ? WHERE id = ?");
+ $update = $pdo->prepare("UPDATE `" . t('technicians') . "` SET temp_password = ? WHERE id = ?");
$update->execute([$hashed, $row['id']]);
$updated++;
echo " Hashed temp_password for ID {$row['id']}\n";
diff --git a/FINAL_PRODUCTION_SYSTEM/database/i18n_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/i18n_migration.sql
index 1d3f112..a92be36 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/i18n_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/i18n_migration.sql
@@ -2,12 +2,12 @@
-- Run this migration to enable per-user language selection
-- Add preferred_language column to admin_users
-ALTER TABLE admin_users ADD COLUMN preferred_language VARCHAR(5) DEFAULT 'en' AFTER role;
+ALTER TABLE `#__admin_users` ADD COLUMN preferred_language VARCHAR(5) DEFAULT 'en' AFTER role;
-- Add preferred_language column to technicians
-ALTER TABLE technicians ADD COLUMN preferred_language VARCHAR(5) DEFAULT 'en' AFTER notes;
+ALTER TABLE `#__technicians` ADD COLUMN preferred_language VARCHAR(5) DEFAULT 'en' AFTER notes;
-- Add system default language setting
-INSERT INTO system_config (config_key, config_value, description)
+INSERT INTO `#__system_config` (config_key, config_value, description)
VALUES ('default_language', 'en', 'Default system language (en = English, ru = Russian)')
ON DUPLICATE KEY UPDATE config_value = config_value;
diff --git a/FINAL_PRODUCTION_SYSTEM/database/install.sql b/FINAL_PRODUCTION_SYSTEM/database/install.sql
index 0441b3a..88db432 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/install.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/install.sql
@@ -8,7 +8,7 @@ START TRANSACTION;
SET time_zone = "+00:00";
-- Create technicians table
-CREATE TABLE `technicians` (
+CREATE TABLE `#__technicians` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`technician_id` varchar(20) NOT NULL,
`full_name` varchar(100) NOT NULL,
@@ -31,7 +31,7 @@ CREATE TABLE `technicians` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Create oem_keys table
-CREATE TABLE `oem_keys` (
+CREATE TABLE `#__oem_keys` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`product_key` varchar(29) NOT NULL,
`oem_identifier` varchar(20) NOT NULL,
@@ -56,7 +56,7 @@ CREATE TABLE `oem_keys` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Create activation_attempts table
-CREATE TABLE `activation_attempts` (
+CREATE TABLE `#__activation_attempts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`key_id` int(11) NOT NULL,
`technician_id` varchar(20) NOT NULL,
@@ -76,12 +76,12 @@ CREATE TABLE `activation_attempts` (
KEY `idx_attempted_at` (`attempted_at`),
KEY `idx_attempted_date` (`attempted_date`),
KEY `idx_key_tech_date` (`key_id`, `technician_id`, `attempted_at`),
- CONSTRAINT `activation_attempts_ibfk_1` FOREIGN KEY (`key_id`) REFERENCES `oem_keys` (`id`),
- CONSTRAINT `activation_attempts_ibfk_2` FOREIGN KEY (`technician_id`) REFERENCES `technicians` (`technician_id`)
+ CONSTRAINT `activation_attempts_ibfk_1` FOREIGN KEY (`key_id`) REFERENCES `#__oem_keys` (`id`),
+ CONSTRAINT `activation_attempts_ibfk_2` FOREIGN KEY (`technician_id`) REFERENCES `#__technicians` (`technician_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Create active_sessions table
-CREATE TABLE `active_sessions` (
+CREATE TABLE `#__active_sessions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`technician_id` varchar(20) NOT NULL,
`session_token` varchar(64) NOT NULL,
@@ -99,12 +99,12 @@ CREATE TABLE `active_sessions` (
KEY `idx_expires_at` (`expires_at`),
KEY `idx_tech_active_expires` (`technician_id`, `is_active`, `expires_at`),
KEY `idx_expires_active` (`expires_at`, `is_active`),
- CONSTRAINT `active_sessions_ibfk_1` FOREIGN KEY (`key_id`) REFERENCES `oem_keys` (`id`),
- CONSTRAINT `active_sessions_ibfk_2` FOREIGN KEY (`technician_id`) REFERENCES `technicians` (`technician_id`)
+ CONSTRAINT `active_sessions_ibfk_1` FOREIGN KEY (`key_id`) REFERENCES `#__oem_keys` (`id`),
+ CONSTRAINT `active_sessions_ibfk_2` FOREIGN KEY (`technician_id`) REFERENCES `#__technicians` (`technician_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Create admin_users table
-CREATE TABLE `admin_users` (
+CREATE TABLE `#__admin_users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`full_name` varchar(100) NOT NULL,
@@ -125,11 +125,11 @@ CREATE TABLE `admin_users` (
KEY `created_by` (`created_by`),
KEY `idx_username` (`username`),
KEY `idx_is_active` (`is_active`),
- CONSTRAINT `admin_users_ibfk_1` FOREIGN KEY (`created_by`) REFERENCES `admin_users` (`id`)
+ CONSTRAINT `admin_users_ibfk_1` FOREIGN KEY (`created_by`) REFERENCES `#__admin_users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Create admin_sessions table
-CREATE TABLE `admin_sessions` (
+CREATE TABLE `#__admin_sessions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`admin_id` int(11) NOT NULL,
`session_token` varchar(64) NOT NULL,
@@ -145,11 +145,11 @@ CREATE TABLE `admin_sessions` (
KEY `idx_session_token` (`session_token`),
KEY `idx_admin_id` (`admin_id`),
KEY `idx_expires_at` (`expires_at`),
- CONSTRAINT `admin_sessions_ibfk_1` FOREIGN KEY (`admin_id`) REFERENCES `admin_users` (`id`)
+ CONSTRAINT `admin_sessions_ibfk_1` FOREIGN KEY (`admin_id`) REFERENCES `#__admin_users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Create admin_activity_log table
-CREATE TABLE `admin_activity_log` (
+CREATE TABLE `#__admin_activity_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`admin_id` int(11) DEFAULT NULL,
`session_id` int(11) DEFAULT NULL,
@@ -164,12 +164,12 @@ CREATE TABLE `admin_activity_log` (
KEY `idx_admin_id` (`admin_id`),
KEY `idx_created_at` (`created_at`),
KEY `idx_action` (`action`),
- CONSTRAINT `admin_activity_log_ibfk_1` FOREIGN KEY (`admin_id`) REFERENCES `admin_users` (`id`),
- CONSTRAINT `admin_activity_log_ibfk_2` FOREIGN KEY (`session_id`) REFERENCES `admin_sessions` (`id`)
+ CONSTRAINT `admin_activity_log_ibfk_1` FOREIGN KEY (`admin_id`) REFERENCES `#__admin_users` (`id`),
+ CONSTRAINT `admin_activity_log_ibfk_2` FOREIGN KEY (`session_id`) REFERENCES `#__admin_sessions` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Create admin_ip_whitelist table
-CREATE TABLE `admin_ip_whitelist` (
+CREATE TABLE `#__admin_ip_whitelist` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ip_address` varchar(45) NOT NULL,
`ip_range` varchar(45) DEFAULT NULL,
@@ -181,11 +181,11 @@ CREATE TABLE `admin_ip_whitelist` (
KEY `created_by` (`created_by`),
KEY `idx_ip_address` (`ip_address`),
KEY `idx_is_active` (`is_active`),
- CONSTRAINT `admin_ip_whitelist_ibfk_1` FOREIGN KEY (`created_by`) REFERENCES `admin_users` (`id`)
+ CONSTRAINT `admin_ip_whitelist_ibfk_1` FOREIGN KEY (`created_by`) REFERENCES `#__admin_users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Create system_config table
-CREATE TABLE `system_config` (
+CREATE TABLE `#__system_config` (
`config_key` varchar(50) NOT NULL,
`config_value` text NOT NULL,
`description` text DEFAULT NULL,
@@ -194,7 +194,7 @@ CREATE TABLE `system_config` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Create password_reset_tokens table
-CREATE TABLE `password_reset_tokens` (
+CREATE TABLE `#__password_reset_tokens` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`technician_id` varchar(20) NOT NULL,
`reset_token` varchar(64) NOT NULL,
@@ -206,11 +206,11 @@ CREATE TABLE `password_reset_tokens` (
KEY `technician_id` (`technician_id`),
KEY `idx_reset_token` (`reset_token`),
KEY `idx_expires_at` (`expires_at`),
- CONSTRAINT `password_reset_tokens_ibfk_1` FOREIGN KEY (`technician_id`) REFERENCES `technicians` (`technician_id`)
+ CONSTRAINT `password_reset_tokens_ibfk_1` FOREIGN KEY (`technician_id`) REFERENCES `#__technicians` (`technician_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Insert default system configuration
-INSERT INTO `system_config` (`config_key`, `config_value`, `description`) VALUES
+INSERT INTO `#__system_config` (`config_key`, `config_value`, `description`) VALUES
('smtp_server', 'smtp.zoho.com', 'SMTP server for notifications'),
('smtp_port', '587', 'SMTP port'),
('smtp_username', '', 'SMTP username'),
@@ -236,7 +236,7 @@ INSERT INTO `system_config` (`config_key`, `config_value`, `description`) VALUES
('admin_log_retention_days', '365', 'Keep admin activity logs for N days');
-- Create sample technician account (for testing)
-INSERT INTO `technicians` (`technician_id`, `full_name`, `email`, `password_hash`, `temp_password`, `must_change_password`, `created_by`, `notes`) VALUES
+INSERT INTO `#__technicians` (`technician_id`, `full_name`, `email`, `password_hash`, `temp_password`, `must_change_password`, `created_by`, `notes`) VALUES
('demo', 'Demo Technician', 'demo@example.com', '$2y$12$LQv3c1yqBwlVHpPd7u/Dw.G2K2wjDUl9jhJxfTULt3lOAOWuTDBKG', 'demo123', 1, 'system', 'Demo account for testing - Password: demo123');
COMMIT;
\ No newline at end of file
diff --git a/FINAL_PRODUCTION_SYSTEM/database/integrations_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/integrations_migration.sql
index 4fa2bf3..f60e111 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/integrations_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/integrations_migration.sql
@@ -3,7 +3,7 @@
-- Supports osTicket, 1C ERP, and future integrations
-- =============================================================
-CREATE TABLE IF NOT EXISTS `integrations` (
+CREATE TABLE IF NOT EXISTS `#__integrations` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`integration_key` VARCHAR(50) NOT NULL UNIQUE,
`display_name` VARCHAR(100) NOT NULL,
@@ -18,7 +18,7 @@ CREATE TABLE IF NOT EXISTS `integrations` (
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-CREATE TABLE IF NOT EXISTS `integration_events` (
+CREATE TABLE IF NOT EXISTS `#__integration_events` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`integration_id` INT UNSIGNED NOT NULL,
`event_type` VARCHAR(50) NOT NULL,
@@ -29,14 +29,14 @@ CREATE TABLE IF NOT EXISTS `integration_events` (
`error_message` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`processed_at` TIMESTAMP NULL DEFAULT NULL,
- FOREIGN KEY (`integration_id`) REFERENCES `integrations`(`id`) ON DELETE CASCADE,
+ FOREIGN KEY (`integration_id`) REFERENCES `#__integrations`(`id`) ON DELETE CASCADE,
INDEX `idx_ie_status` (`status`),
INDEX `idx_ie_event_type` (`event_type`),
INDEX `idx_ie_created` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Seed default integrations
-INSERT INTO `integrations` (`integration_key`, `display_name`, `description`, `integration_type`, `enabled`, `config`) VALUES
+INSERT INTO `#__integrations` (`integration_key`, `display_name`, `description`, `integration_type`, `enabled`, `config`) VALUES
('osticket', 'osTicket', 'Track PC build orders as support tickets in osTicket', 'api_sync', 0,
JSON_OBJECT(
'base_url', '',
diff --git a/FINAL_PRODUCTION_SYSTEM/database/license_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/license_migration.sql
index 49e8cb4..8dbc5dd 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/license_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/license_migration.sql
@@ -4,7 +4,7 @@
-- Tracks instance license, tier limits, and validation history.
-- =============================================================
-CREATE TABLE IF NOT EXISTS license_info (
+CREATE TABLE IF NOT EXISTS `#__license_info` (
id INT AUTO_INCREMENT PRIMARY KEY,
license_key VARCHAR(2048) NOT NULL COMMENT 'JWT license token',
instance_id VARCHAR(128) NOT NULL COMMENT 'SHA256 fingerprint of this installation',
@@ -26,10 +26,10 @@ CREATE TABLE IF NOT EXISTS license_info (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Default community license limits stored in system_config
-INSERT INTO system_config (config_key, config_value, description) VALUES
+INSERT INTO `#__system_config` (config_key, config_value, description) VALUES
('license_tier', 'community', 'Current license tier')
ON DUPLICATE KEY UPDATE config_key = config_key;
-INSERT INTO system_config (config_key, config_value, description) VALUES
+INSERT INTO `#__system_config` (config_key, config_value, description) VALUES
('license_instance_id', '', 'Unique instance fingerprint (auto-generated)')
ON DUPLICATE KEY UPDATE config_key = config_key;
diff --git a/FINAL_PRODUCTION_SYSTEM/database/missing_drivers_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/missing_drivers_migration.sql
index 0cdd5a0..61a4e83 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/missing_drivers_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/missing_drivers_migration.sql
@@ -2,21 +2,21 @@
-- Adds driver status tracking and per-check enforcement to product lines
-- 1. Add driver columns to hardware_info
-ALTER TABLE hardware_info
+ALTER TABLE `#__hardware_info`
ADD COLUMN IF NOT EXISTS missing_drivers JSON NULL COMMENT 'Array of devices with missing/problematic drivers',
ADD COLUMN IF NOT EXISTS missing_drivers_count INT NULL DEFAULT NULL COMMENT 'Number of missing/error drivers detected';
CREATE INDEX IF NOT EXISTS idx_hw_missing_drivers_count ON hardware_info (missing_drivers_count);
-- 2. Add enforcement columns to QC tables
-ALTER TABLE qc_motherboard_registry
+ALTER TABLE `#__qc_motherboard_registry`
ADD COLUMN IF NOT EXISTS missing_drivers_enforcement TINYINT(1) NULL COMMENT 'Override: 0=disabled, 1=info, 2=warning, 3=blocking';
-ALTER TABLE qc_manufacturer_defaults
+ALTER TABLE `#__qc_manufacturer_defaults`
ADD COLUMN IF NOT EXISTS missing_drivers_enforcement TINYINT(1) DEFAULT NULL COMMENT 'Override: 0=disabled, 1=info, 2=warning, 3=blocking';
-- 3. Add per-check enforcement columns to product_lines
-ALTER TABLE product_lines
+ALTER TABLE `#__product_lines`
ADD COLUMN IF NOT EXISTS secure_boot_enforcement TINYINT(1) DEFAULT NULL,
ADD COLUMN IF NOT EXISTS bios_enforcement TINYINT(1) DEFAULT NULL,
ADD COLUMN IF NOT EXISTS hackbgrt_enforcement TINYINT(1) DEFAULT NULL,
@@ -31,5 +31,5 @@ INSERT IGNORE INTO qc_global_settings (setting_key, setting_value, description)
VALUES ('default_partition_enforcement', '2', 'Default enforcement for partition layout check (0=disabled, 1=info, 2=warning, 3=blocking)');
-- 5. Widen check_type from ENUM to VARCHAR for extensibility
-ALTER TABLE qc_compliance_results
+ALTER TABLE `#__qc_compliance_results`
MODIFY COLUMN check_type VARCHAR(50) NOT NULL COMMENT 'Check type identifier';
diff --git a/FINAL_PRODUCTION_SYSTEM/database/order_field_config_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/order_field_config_migration.sql
index 4d7a821..1d218e1 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/order_field_config_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/order_field_config_migration.sql
@@ -5,19 +5,19 @@
-- 1. Widen order_number columns from VARCHAR(10) to VARCHAR(50)
-- Preserve original NULL/NOT NULL constraints per table
-ALTER TABLE activation_attempts MODIFY order_number VARCHAR(50) NOT NULL;
-ALTER TABLE active_sessions MODIFY order_number VARCHAR(50) NULL;
-ALTER TABLE hardware_info MODIFY order_number VARCHAR(50) NOT NULL;
-ALTER TABLE qc_compliance_results MODIFY order_number VARCHAR(50) NOT NULL;
+ALTER TABLE `#__activation_attempts` MODIFY order_number VARCHAR(50) NOT NULL;
+ALTER TABLE `#__active_sessions` MODIFY order_number VARCHAR(50) NULL;
+ALTER TABLE `#__hardware_info` MODIFY order_number VARCHAR(50) NOT NULL;
+ALTER TABLE `#__qc_compliance_results` MODIFY order_number VARCHAR(50) NOT NULL;
-- hardware_collection_log: only alter if table exists (not present in all installations)
SET @tbl_exists = (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'hardware_collection_log');
-SET @sql = IF(@tbl_exists > 0, 'ALTER TABLE hardware_collection_log MODIFY order_number VARCHAR(50) NOT NULL', 'SELECT 1');
+SET @sql = IF(@tbl_exists > 0, 'ALTER TABLE `#__hardware_collection_log` MODIFY order_number VARCHAR(50) NOT NULL', 'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 2. Seed system_config with order field configuration defaults
-INSERT INTO system_config (config_key, config_value, description, updated_at)
+INSERT INTO `#__system_config` (config_key, config_value, description, updated_at)
VALUES
('order_field_label_en', 'Order Number', 'Display label for the order field (English)', NOW()),
('order_field_label_ru', 'Номер заказа', 'Display label for the order field (Russian)', NOW()),
diff --git a/FINAL_PRODUCTION_SYSTEM/database/product_variants_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/product_variants_migration.sql
index da6ec04..42f324b 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/product_variants_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/product_variants_migration.sql
@@ -2,7 +2,7 @@
-- Adds partition layout QC checking via product lines → variants → partition templates
-- ── Product Lines (linked to order number patterns) ──────────────
-CREATE TABLE IF NOT EXISTS product_lines (
+CREATE TABLE IF NOT EXISTS `#__product_lines` (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
order_pattern VARCHAR(50) NOT NULL COMMENT 'Order number prefix to match, e.g. ЭЛ00-',
@@ -16,7 +16,7 @@ CREATE TABLE IF NOT EXISTS product_lines (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ── Product Variants (disk size ranges within a product line) ────
-CREATE TABLE IF NOT EXISTS product_variants (
+CREATE TABLE IF NOT EXISTS `#__product_variants` (
id INT AUTO_INCREMENT PRIMARY KEY,
line_id INT NOT NULL,
name VARCHAR(100) NOT NULL COMMENT 'e.g. RTX 1TB, RTX 512GB',
@@ -25,12 +25,12 @@ CREATE TABLE IF NOT EXISTS product_variants (
is_active TINYINT(1) NOT NULL DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (line_id) REFERENCES product_lines(id) ON DELETE CASCADE,
+ FOREIGN KEY (line_id) REFERENCES `#__product_lines`(id) ON DELETE CASCADE,
UNIQUE KEY uk_line_name (line_id, name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ── Partition Templates (expected layout per variant) ────────────
-CREATE TABLE IF NOT EXISTS product_variant_partitions (
+CREATE TABLE IF NOT EXISTS `#__product_variant_partitions` (
id INT AUTO_INCREMENT PRIMARY KEY,
variant_id INT NOT NULL,
partition_order INT NOT NULL COMMENT 'Expected position on disk: 1, 2, 3...',
@@ -39,16 +39,16 @@ CREATE TABLE IF NOT EXISTS product_variant_partitions (
expected_size_mb INT NOT NULL,
tolerance_percent DECIMAL(5,2) NOT NULL DEFAULT 1.00 COMMENT 'Allowed deviation % per partition',
is_flexible TINYINT(1) NOT NULL DEFAULT 0 COMMENT '1 = absorbs remaining space (e.g. Data partition)',
- FOREIGN KEY (variant_id) REFERENCES product_variants(id) ON DELETE CASCADE,
+ FOREIGN KEY (variant_id) REFERENCES `#__product_variants`(id) ON DELETE CASCADE,
UNIQUE KEY uk_variant_order (variant_id, partition_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ── Extend QC compliance results to include partition_layout ─────
-ALTER TABLE qc_compliance_results
+ALTER TABLE `#__qc_compliance_results`
MODIFY COLUMN check_type ENUM('bios_version','secure_boot','hackbgrt_boot_priority','partition_layout') NOT NULL;
-- ── Add detected variant tracking to hardware_info ───────────────
-ALTER TABLE hardware_info
+ALTER TABLE `#__hardware_info`
ADD COLUMN IF NOT EXISTS detected_variant_id INT DEFAULT NULL,
ADD COLUMN IF NOT EXISTS detected_variant_name VARCHAR(100) DEFAULT NULL,
ADD COLUMN IF NOT EXISTS detected_line_name VARCHAR(100) DEFAULT NULL;
diff --git a/FINAL_PRODUCTION_SYSTEM/database/production_tracking_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/production_tracking_migration.sql
index 8f247aa..8d4da60 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/production_tracking_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/production_tracking_migration.sql
@@ -7,7 +7,7 @@
-- ── 1. Computer Build Reports (CBR) ────────────────────────
-- Structured per-machine reports for auditing and compliance.
-CREATE TABLE IF NOT EXISTS computer_build_reports (
+CREATE TABLE IF NOT EXISTS `#__computer_build_reports` (
id INT AUTO_INCREMENT PRIMARY KEY,
report_uuid VARCHAR(36) NOT NULL UNIQUE COMMENT 'UUID v4 for external reference',
activation_attempt_id INT DEFAULT NULL,
@@ -80,7 +80,7 @@ CREATE TABLE IF NOT EXISTS computer_build_reports (
-- ── 2. Key Pool Management ─────────────────────────────────
-- Alert thresholds and replenishment tracking per product edition.
-CREATE TABLE IF NOT EXISTS key_pool_config (
+CREATE TABLE IF NOT EXISTS `#__key_pool_config` (
id INT AUTO_INCREMENT PRIMARY KEY,
product_edition VARCHAR(100) NOT NULL UNIQUE COMMENT 'e.g. Windows 11 Pro, Windows 11 Home',
low_threshold INT NOT NULL DEFAULT 10 COMMENT 'Alert when unused keys drop below this',
@@ -96,7 +96,7 @@ CREATE TABLE IF NOT EXISTS key_pool_config (
-- ── 3. Hardware Binding Registry ───────────────────────────
-- Tracks which keys have been used on which hardware.
-CREATE TABLE IF NOT EXISTS hardware_key_bindings (
+CREATE TABLE IF NOT EXISTS `#__hardware_key_bindings` (
id INT AUTO_INCREMENT PRIMARY KEY,
product_key_id INT NOT NULL COMMENT 'FK to oem_keys',
device_fingerprint VARCHAR(500) NOT NULL,
@@ -118,7 +118,7 @@ CREATE TABLE IF NOT EXISTS hardware_key_bindings (
-- ── 4. DPK Import Batches ──────────────────────────────────
-- Tracks bulk key imports from Microsoft OEM deliveries.
-CREATE TABLE IF NOT EXISTS dpk_import_batches (
+CREATE TABLE IF NOT EXISTS `#__dpk_import_batches` (
id INT AUTO_INCREMENT PRIMARY KEY,
batch_name VARCHAR(255) NOT NULL COMMENT 'e.g. "Microsoft Order #12345"',
import_source VARCHAR(100) NOT NULL DEFAULT 'manual' COMMENT 'manual, microsoft_csv, microsoft_xml',
@@ -142,7 +142,7 @@ CREATE TABLE IF NOT EXISTS dpk_import_batches (
-- ── 5. Work Orders (Production Line Tracking) ──────────────
-- Links builds to customer orders, batch production runs.
-CREATE TABLE IF NOT EXISTS work_orders (
+CREATE TABLE IF NOT EXISTS `#__work_orders` (
id INT AUTO_INCREMENT PRIMARY KEY,
work_order_number VARCHAR(50) NOT NULL UNIQUE COMMENT 'Auto-generated or manual',
batch_number VARCHAR(100) DEFAULT NULL COMMENT 'Production batch grouping',
diff --git a/FINAL_PRODUCTION_SYSTEM/database/push_notifications_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/push_notifications_migration.sql
index 5fee47b..061e04e 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/push_notifications_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/push_notifications_migration.sql
@@ -5,7 +5,7 @@
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
-- Push subscriptions: stores browser push endpoints per admin
-CREATE TABLE IF NOT EXISTS `push_subscriptions` (
+CREATE TABLE IF NOT EXISTS `#__push_subscriptions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`admin_id` int(11) NOT NULL,
`endpoint` text NOT NULL,
@@ -18,11 +18,11 @@ CREATE TABLE IF NOT EXISTS `push_subscriptions` (
PRIMARY KEY (`id`),
UNIQUE KEY `uq_admin_endpoint` (`admin_id`, `endpoint`(500)),
KEY `idx_admin_active` (`admin_id`, `is_active`),
- CONSTRAINT `fk_push_sub_admin` FOREIGN KEY (`admin_id`) REFERENCES `admin_users` (`id`) ON DELETE CASCADE
+ CONSTRAINT `fk_push_sub_admin` FOREIGN KEY (`admin_id`) REFERENCES `#__admin_users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Push preferences: per-admin category toggles
-CREATE TABLE IF NOT EXISTS `push_preferences` (
+CREATE TABLE IF NOT EXISTS `#__push_preferences` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`admin_id` int(11) NOT NULL,
`category` varchar(50) NOT NULL,
@@ -30,11 +30,11 @@ CREATE TABLE IF NOT EXISTS `push_preferences` (
PRIMARY KEY (`id`),
UNIQUE KEY `uq_admin_category` (`admin_id`, `category`),
KEY `idx_admin_id` (`admin_id`),
- CONSTRAINT `fk_push_pref_admin` FOREIGN KEY (`admin_id`) REFERENCES `admin_users` (`id`) ON DELETE CASCADE
+ CONSTRAINT `fk_push_pref_admin` FOREIGN KEY (`admin_id`) REFERENCES `#__admin_users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Notifications: bell dropdown history
-CREATE TABLE IF NOT EXISTS `notifications` (
+CREATE TABLE IF NOT EXISTS `#__notifications` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`admin_id` int(11) NOT NULL,
`category` varchar(50) NOT NULL,
@@ -47,11 +47,11 @@ CREATE TABLE IF NOT EXISTS `notifications` (
PRIMARY KEY (`id`),
KEY `idx_admin_read` (`admin_id`, `is_read`),
KEY `idx_admin_created` (`admin_id`, `created_at` DESC),
- CONSTRAINT `fk_notif_admin` FOREIGN KEY (`admin_id`) REFERENCES `admin_users` (`id`) ON DELETE CASCADE
+ CONSTRAINT `fk_notif_admin` FOREIGN KEY (`admin_id`) REFERENCES `#__admin_users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- VAPID keys and push settings in system_config
-INSERT INTO `system_config` (`config_key`, `config_value`, `description`)
+INSERT INTO `#__system_config` (`config_key`, `config_value`, `description`)
VALUES
('vapid_public_key', '', 'VAPID public key for Web Push (auto-generated)'),
('vapid_private_key', '', 'VAPID private key for Web Push (auto-generated)'),
diff --git a/FINAL_PRODUCTION_SYSTEM/database/qc_compliance_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/qc_compliance_migration.sql
index c289c28..2a0a27f 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/qc_compliance_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/qc_compliance_migration.sql
@@ -9,15 +9,15 @@
-- 1. New columns on hardware_info
-- ============================================================
-ALTER TABLE hardware_info ADD COLUMN boot_order JSON NULL COMMENT 'Ordered list of UEFI boot entries from bcdedit' AFTER device_fingerprint;
-ALTER TABLE hardware_info ADD COLUMN hackbgrt_installed TINYINT(1) NULL COMMENT '1=HackBGRT traces found on EFI partition' AFTER boot_order;
-ALTER TABLE hardware_info ADD COLUMN hackbgrt_first_boot TINYINT(1) NULL COMMENT '1=HackBGRT is first boot entry' AFTER hackbgrt_installed;
+ALTER TABLE `#__hardware_info` ADD COLUMN boot_order JSON NULL COMMENT 'Ordered list of UEFI boot entries from bcdedit' AFTER device_fingerprint;
+ALTER TABLE `#__hardware_info` ADD COLUMN hackbgrt_installed TINYINT(1) NULL COMMENT '1=HackBGRT traces found on EFI partition' AFTER boot_order;
+ALTER TABLE `#__hardware_info` ADD COLUMN hackbgrt_first_boot TINYINT(1) NULL COMMENT '1=HackBGRT is first boot entry' AFTER hackbgrt_installed;
-- ============================================================
-- 2. QC Global Settings (key-value)
-- ============================================================
-CREATE TABLE IF NOT EXISTS qc_global_settings (
+CREATE TABLE IF NOT EXISTS `#__qc_global_settings` (
id INT AUTO_INCREMENT PRIMARY KEY,
setting_key VARCHAR(100) NOT NULL UNIQUE,
setting_value TEXT NOT NULL,
@@ -26,7 +26,7 @@ CREATE TABLE IF NOT EXISTS qc_global_settings (
updated_by INT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-INSERT INTO qc_global_settings (setting_key, setting_value, description) VALUES
+INSERT INTO `#__qc_global_settings` (setting_key, setting_value, description) VALUES
('qc_enabled', '0', 'Enable hardware compliance checking during activation (0=off, 1=on)'),
('default_bios_enforcement', '1', 'Default BIOS version check enforcement (0=disabled, 1=info, 2=warning, 3=blocking)'),
('default_secure_boot_enforcement', '1', 'Default Secure Boot check enforcement (0=disabled, 1=info, 2=warning, 3=blocking)'),
@@ -37,7 +37,7 @@ INSERT INTO qc_global_settings (setting_key, setting_value, description) VALUES
-- 3. Manufacturer Defaults
-- ============================================================
-CREATE TABLE IF NOT EXISTS qc_manufacturer_defaults (
+CREATE TABLE IF NOT EXISTS `#__qc_manufacturer_defaults` (
id INT AUTO_INCREMENT PRIMARY KEY,
manufacturer VARCHAR(100) NOT NULL UNIQUE,
secure_boot_required TINYINT(1) DEFAULT 1 COMMENT '1=Secure Boot must be ON',
@@ -54,10 +54,10 @@ CREATE TABLE IF NOT EXISTS qc_manufacturer_defaults (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ============================================================
--- 4. Motherboard Registry (auto-populated from hardware_info)
+-- 4. Motherboard Registry (auto-populated from `#__hardware_info`)
-- ============================================================
-CREATE TABLE IF NOT EXISTS qc_motherboard_registry (
+CREATE TABLE IF NOT EXISTS `#__qc_motherboard_registry` (
id INT AUTO_INCREMENT PRIMARY KEY,
manufacturer VARCHAR(100) NOT NULL,
product VARCHAR(100) NOT NULL,
@@ -85,7 +85,7 @@ CREATE TABLE IF NOT EXISTS qc_motherboard_registry (
-- 5. Compliance Results (per check, per hardware submission)
-- ============================================================
-CREATE TABLE IF NOT EXISTS qc_compliance_results (
+CREATE TABLE IF NOT EXISTS `#__qc_compliance_results` (
id INT AUTO_INCREMENT PRIMARY KEY,
hardware_info_id INT NOT NULL,
order_number VARCHAR(10) NOT NULL,
@@ -99,7 +99,7 @@ CREATE TABLE IF NOT EXISTS qc_compliance_results (
motherboard_registry_id INT NULL,
is_retroactive TINYINT(1) DEFAULT 0 COMMENT '1=created by retroactive recheck',
checked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (hardware_info_id) REFERENCES hardware_info(id) ON DELETE CASCADE,
+ FOREIGN KEY (hardware_info_id) REFERENCES `#__hardware_info`(id) ON DELETE CASCADE,
INDEX idx_hardware_id (hardware_info_id),
INDEX idx_order_number (order_number),
INDEX idx_check_result (check_result),
@@ -110,12 +110,12 @@ CREATE TABLE IF NOT EXISTS qc_compliance_results (
-- 6. ACL: Quality Control permission category + permissions
-- ============================================================
-INSERT INTO acl_permission_categories (category_key, display_name, icon, sort_order) VALUES
+INSERT INTO `#__acl_permission_categories` (category_key, display_name, icon, sort_order) VALUES
('quality_control', 'Quality Control', NULL, 46);
-SET @qc_cat_id = (SELECT id FROM acl_permission_categories WHERE category_key = 'quality_control');
+SET @qc_cat_id = (SELECT id FROM `#__acl_permission_categories` WHERE category_key = 'quality_control');
-INSERT INTO acl_permissions (permission_key, display_name, description, category_id, resource_type, action_type, is_dangerous) VALUES
+INSERT INTO `#__acl_permissions` (permission_key, display_name, description, category_id, resource_type, action_type, is_dangerous) VALUES
('view_compliance', 'View Compliance Results', 'View QC compliance check results and statistics', @qc_cat_id, 'compliance', 'view', 0),
('manage_compliance_rules', 'Manage Compliance Rules', 'Configure motherboard registry and compliance enforcement rules', @qc_cat_id, 'compliance', 'manage', 0),
('manage_compliance', 'Manage QC System', 'Toggle QC feature, trigger retroactive checks, modify global settings', @qc_cat_id, 'compliance', 'manage', 1);
@@ -123,23 +123,23 @@ INSERT INTO acl_permissions (permission_key, display_name, description, category
-- Grant to super_admin (already gets everything via CROSS JOIN, but explicit for clarity on new installs)
INSERT IGNORE INTO acl_role_permissions (role_id, permission_id)
SELECT r.id, p.id
-FROM acl_roles r CROSS JOIN acl_permissions p
+FROM `#__acl_roles` r CROSS JOIN `#__acl_permissions` p
WHERE r.role_name = 'super_admin' AND p.permission_key IN ('view_compliance', 'manage_compliance_rules', 'manage_compliance');
-- Grant to admin: view + manage rules
INSERT IGNORE INTO acl_role_permissions (role_id, permission_id)
SELECT r.id, p.id
-FROM acl_roles r, acl_permissions p
+FROM `#__acl_roles` r, acl_permissions p
WHERE r.role_name = 'admin' AND p.permission_key IN ('view_compliance', 'manage_compliance_rules');
-- Grant to qc_inspector: view only
INSERT IGNORE INTO acl_role_permissions (role_id, permission_id)
SELECT r.id, p.id
-FROM acl_roles r, acl_permissions p
+FROM `#__acl_roles` r, acl_permissions p
WHERE r.role_name = 'qc_inspector' AND p.permission_key IN ('view_compliance');
-- Grant to dept_manager: view only
INSERT IGNORE INTO acl_role_permissions (role_id, permission_id)
SELECT r.id, p.id
-FROM acl_roles r, acl_permissions p
+FROM `#__acl_roles` r, acl_permissions p
WHERE r.role_name = 'dept_manager' AND p.permission_key IN ('view_compliance');
diff --git a/FINAL_PRODUCTION_SYSTEM/database/rate_limiting_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/rate_limiting_migration.sql
index e037a26..bc4293f 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/rate_limiting_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/rate_limiting_migration.sql
@@ -6,7 +6,7 @@
-- Table: rate_limit_violations
-- Logs when API rate limits are exceeded for security monitoring
-CREATE TABLE IF NOT EXISTS `rate_limit_violations` (
+CREATE TABLE IF NOT EXISTS `#__rate_limit_violations` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`identifier` VARCHAR(100) NOT NULL COMMENT 'IP address or user ID',
`action` VARCHAR(50) NOT NULL COMMENT 'Endpoint action (login, get-key, etc.)',
@@ -26,7 +26,7 @@ CREATE TABLE IF NOT EXISTS `rate_limit_violations` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Rate limit violation log for security monitoring';
-- System configuration for rate limiting
-INSERT INTO `system_config` (`config_key`, `config_value`, `description`) VALUES
+INSERT INTO `#__system_config` (`config_key`, `config_value`, `description`) VALUES
('rate_limit_enabled', '1', 'Enable API rate limiting (1=yes, 0=no)'),
('rate_limit_global_per_minute', '100', 'Max requests per minute per IP (all endpoints)'),
('rate_limit_login_per_hour', '20', 'Max login attempts per hour per IP'),
diff --git a/FINAL_PRODUCTION_SYSTEM/database/rbac_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/rbac_migration.sql
index 0c50dc4..25f5888 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/rbac_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/rbac_migration.sql
@@ -13,7 +13,7 @@ SET @role_column_exists = (SELECT COUNT(*) FROM information_schema.COLUMNS
-- If role column doesn't exist, create it
SET @sql = IF(@role_column_exists = 0,
- 'ALTER TABLE `admin_users` ADD COLUMN `role` ENUM(''super_admin'', ''admin'', ''viewer'') DEFAULT ''admin'' COMMENT ''Admin role for RBAC''',
+ 'ALTER TABLE `#__admin_users` ADD COLUMN `role` ENUM(''super_admin'', ''admin'', ''viewer'') DEFAULT ''admin'' COMMENT ''Admin role for RBAC''',
'SELECT "Role column already exists"');
PREPARE stmt FROM @sql;
@@ -21,12 +21,12 @@ EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- Add role tracking to admin_activity_log
-ALTER TABLE `admin_activity_log`
+ALTER TABLE `#__admin_activity_log`
ADD COLUMN IF NOT EXISTS `admin_role` ENUM('super_admin', 'admin', 'viewer') NULL COMMENT 'Role of admin at time of action' AFTER `user_agent`,
ADD INDEX IF NOT EXISTS `idx_admin_role` (`admin_role`);
--- Create table for permission audit trail
-CREATE TABLE IF NOT EXISTS `rbac_permission_denials` (
+-- Create table `#__for` permission audit trail
+CREATE TABLE IF NOT EXISTS `#__rbac_permission_denials` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`admin_id` INT NOT NULL COMMENT 'Admin who was denied',
`session_id` VARCHAR(64) NULL COMMENT 'Session ID if available',
@@ -41,24 +41,24 @@ CREATE TABLE IF NOT EXISTS `rbac_permission_denials` (
INDEX `idx_admin_role` (`admin_role`),
INDEX `idx_requested_action` (`requested_action`),
INDEX `idx_denied_at` (`denied_at`),
- FOREIGN KEY (`admin_id`) REFERENCES `admin_users`(`id`) ON DELETE CASCADE
+ FOREIGN KEY (`admin_id`) REFERENCES `#__admin_users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='RBAC permission denial audit log';
-- System configuration for RBAC
-INSERT INTO `system_config` (`config_key`, `config_value`, `description`) VALUES
+INSERT INTO `#__system_config` (`config_key`, `config_value`, `description`) VALUES
('rbac_enabled', '1', 'Enable role-based access control (1=yes, 0=no)'),
('rbac_log_denials', '1', 'Log permission denials to rbac_permission_denials table'),
('rbac_strict_mode', '1', 'Deny access to undefined permissions (1=yes, 0=allow)')
ON DUPLICATE KEY UPDATE `config_value` = VALUES(`config_value`);
-- Verify we have at least one super_admin account
-SET @super_admin_count = (SELECT COUNT(*) FROM `admin_users` WHERE `role` = 'super_admin');
+SET @super_admin_count = (SELECT COUNT(*) FROM `#__admin_users` WHERE `role` = 'super_admin');
-- If no super_admin exists, promote the first admin to super_admin
-UPDATE `admin_users` SET `role` = 'super_admin'
-WHERE `id` = (SELECT `id` FROM (SELECT `id` FROM `admin_users` ORDER BY `id` ASC LIMIT 1) AS temp)
+UPDATE `#__admin_users` SET `role` = 'super_admin'
+WHERE `id` = (SELECT `id` FROM (SELECT `id` FROM `#__admin_users` ORDER BY `id` ASC LIMIT 1) AS temp)
AND @super_admin_count = 0;
-- Migration complete
SELECT 'Migration: RBAC tables and permissions configured successfully' AS status;
-SELECT CONCAT('Super admins: ', COUNT(*)) AS super_admin_count FROM `admin_users` WHERE `role` = 'super_admin';
+SELECT CONCAT('Super admins: ', COUNT(*)) AS super_admin_count FROM `#__admin_users` WHERE `role` = 'super_admin';
diff --git a/FINAL_PRODUCTION_SYSTEM/database/schema_versions_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/schema_versions_migration.sql
index 7c8c3e3..18b66e3 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/schema_versions_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/schema_versions_migration.sql
@@ -1,7 +1,7 @@
-- Schema Version Tracking
-- Tracks which migration files have been applied to prevent re-running.
-CREATE TABLE IF NOT EXISTS schema_versions (
+CREATE TABLE IF NOT EXISTS `#__schema_versions` (
id INT AUTO_INCREMENT PRIMARY KEY,
version INT NOT NULL,
filename VARCHAR(255) NOT NULL,
diff --git a/FINAL_PRODUCTION_SYSTEM/database/seed_demo_compliance.sql b/FINAL_PRODUCTION_SYSTEM/database/seed_demo_compliance.sql
index fcc4699..42f6de5 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/seed_demo_compliance.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/seed_demo_compliance.sql
@@ -8,49 +8,49 @@
-- 256GB: OS 120000 MB = 117.1875 GB, Data 121736 MB = 118.8828125 GB
-- 1. Delete old compliance results to start fresh
-DELETE FROM qc_compliance_results;
+DELETE FROM `#__qc_compliance_results`;
-- 2. Update existing hardware_info order numbers to follow product line patterns
-- Also inject complete_disk_layout JSON for partition checking
-- PASS: Good 512 GB layout (ЭЛ00-100001)
-UPDATE hardware_info SET order_number = 'ЭЛ00-100001',
+UPDATE `#__hardware_info` SET order_number = 'ЭЛ00-100001',
complete_disk_layout = '[{"disk_number":0,"disk_model":"Samsung SSD 970 EVO 500GB","disk_size_gb":465.76,"partition_style":"GPT","partitions":[{"partition_number":0,"size_gb":0.25390625,"partition_purpose":"EFI","file_system":"FAT32"},{"partition_number":1,"size_gb":0.015625,"partition_purpose":"MSR","file_system":null},{"partition_number":2,"size_gb":195.3125,"partition_purpose":"OS","file_system":"NTFS","drive_letter":"C:"},{"partition_number":3,"size_gb":1.46484375,"partition_purpose":"Recovery","file_system":"NTFS"},{"partition_number":4,"size_gb":276.7578125,"partition_purpose":"Data","file_system":"NTFS","drive_letter":"D:"},{"partition_number":5,"size_gb":0.1953125,"partition_purpose":"Other","file_system":"FAT32","volume_name":"BIOS","drive_letter":"E:"}]}]'
-WHERE id = (SELECT id FROM (SELECT MIN(id) AS id FROM hardware_info) t);
+WHERE id = (SELECT id FROM (SELECT MIN(id) AS id FROM `#__hardware_info`) t);
-- PASS: Good 512 GB layout (ЭЛ00-100002)
-UPDATE hardware_info SET order_number = 'ЭЛ00-100002',
+UPDATE `#__hardware_info` SET order_number = 'ЭЛ00-100002',
complete_disk_layout = '[{"disk_number":0,"disk_model":"WD Blue SN570 500GB","disk_size_gb":465.76,"partition_style":"GPT","partitions":[{"partition_number":0,"size_gb":0.25390625,"partition_purpose":"EFI","file_system":"FAT32"},{"partition_number":1,"size_gb":0.015625,"partition_purpose":"MSR","file_system":null},{"partition_number":2,"size_gb":195.3125,"partition_purpose":"OS","file_system":"NTFS","drive_letter":"C:"},{"partition_number":3,"size_gb":1.46484375,"partition_purpose":"Recovery","file_system":"NTFS"},{"partition_number":4,"size_gb":276.7578125,"partition_purpose":"Data","file_system":"NTFS","drive_letter":"D:"},{"partition_number":5,"size_gb":0.1953125,"partition_purpose":"Other","file_system":"FAT32","volume_name":"BIOS","drive_letter":"E:"}]}]'
-WHERE id = (SELECT id FROM (SELECT id FROM hardware_info ORDER BY id LIMIT 1 OFFSET 1) t);
+WHERE id = (SELECT id FROM (SELECT id FROM `#__hardware_info` ORDER BY id LIMIT 1 OFFSET 1) t);
-- PASS: Good 1 TB layout (ЛЕ00-200001)
-UPDATE hardware_info SET order_number = 'ЛЕ00-200001',
+UPDATE `#__hardware_info` SET order_number = 'ЛЕ00-200001',
complete_disk_layout = '[{"disk_number":0,"disk_model":"Kingston A2000 1TB","disk_size_gb":931.51,"partition_style":"GPT","partitions":[{"partition_number":0,"size_gb":0.25390625,"partition_purpose":"EFI","file_system":"FAT32"},{"partition_number":1,"size_gb":0.015625,"partition_purpose":"MSR","file_system":null},{"partition_number":2,"size_gb":390.625,"partition_purpose":"OS","file_system":"NTFS","drive_letter":"C:"},{"partition_number":3,"size_gb":1.46484375,"partition_purpose":"Recovery","file_system":"NTFS"},{"partition_number":4,"size_gb":540.0,"partition_purpose":"Data","file_system":"NTFS","drive_letter":"D:"},{"partition_number":5,"size_gb":0.1953125,"partition_purpose":"Other","file_system":"FAT32","volume_name":"BIOS","drive_letter":"E:"}]}]'
-WHERE id = (SELECT id FROM (SELECT id FROM hardware_info ORDER BY id LIMIT 1 OFFSET 2) t);
+WHERE id = (SELECT id FROM (SELECT id FROM `#__hardware_info` ORDER BY id LIMIT 1 OFFSET 2) t);
-- WARNING: 256 GB layout with Data partition slightly too small (ЛЕ00-200002)
-- Data is 113.77 GB = 116500 MB but template requires 121736 MB minimum
-UPDATE hardware_info SET order_number = 'ЛЕ00-200002',
+UPDATE `#__hardware_info` SET order_number = 'ЛЕ00-200002',
complete_disk_layout = '[{"disk_number":0,"disk_model":"Samsung SSD 860 EVO 250GB","disk_size_gb":232.89,"partition_style":"GPT","partitions":[{"partition_number":0,"size_gb":0.25390625,"partition_purpose":"EFI","file_system":"FAT32"},{"partition_number":1,"size_gb":0.015625,"partition_purpose":"MSR","file_system":null},{"partition_number":2,"size_gb":117.1875,"partition_purpose":"OS","file_system":"NTFS","drive_letter":"C:"},{"partition_number":3,"size_gb":1.46484375,"partition_purpose":"Recovery","file_system":"NTFS"},{"partition_number":4,"size_gb":113.77,"partition_purpose":"Data","file_system":"NTFS","drive_letter":"D:"},{"partition_number":5,"size_gb":0.1953125,"partition_purpose":"Other","file_system":"FAT32","volume_name":"BIOS","drive_letter":"E:"}]}]'
-WHERE id = (SELECT id FROM (SELECT id FROM hardware_info ORDER BY id LIMIT 1 OFFSET 3) t);
+WHERE id = (SELECT id FROM (SELECT id FROM `#__hardware_info` ORDER BY id LIMIT 1 OFFSET 3) t);
-- PASS: Good 1 TB layout (ИП00-300001)
-UPDATE hardware_info SET order_number = 'ИП00-300001',
+UPDATE `#__hardware_info` SET order_number = 'ИП00-300001',
complete_disk_layout = '[{"disk_number":0,"disk_model":"Crucial P3 1TB","disk_size_gb":931.51,"partition_style":"GPT","partitions":[{"partition_number":0,"size_gb":0.25390625,"partition_purpose":"EFI","file_system":"FAT32"},{"partition_number":1,"size_gb":0.015625,"partition_purpose":"MSR","file_system":null},{"partition_number":2,"size_gb":390.625,"partition_purpose":"OS","file_system":"NTFS","drive_letter":"C:"},{"partition_number":3,"size_gb":1.46484375,"partition_purpose":"Recovery","file_system":"NTFS"},{"partition_number":4,"size_gb":540.0,"partition_purpose":"Data","file_system":"NTFS","drive_letter":"D:"},{"partition_number":5,"size_gb":0.1953125,"partition_purpose":"Other","file_system":"FAT32","volume_name":"BIOS","drive_letter":"E:"}]}]'
-WHERE id = (SELECT id FROM (SELECT id FROM hardware_info ORDER BY id LIMIT 1 OFFSET 4) t);
+WHERE id = (SELECT id FROM (SELECT id FROM `#__hardware_info` ORDER BY id LIMIT 1 OFFSET 4) t);
-- FAIL: Bad 512 GB layout — wrong OS size (150 GB instead of ~195 GB)
-UPDATE hardware_info SET order_number = 'ИП00-300002',
+UPDATE `#__hardware_info` SET order_number = 'ИП00-300002',
complete_disk_layout = '[{"disk_number":0,"disk_model":"WD Blue SN550 500GB","disk_size_gb":465.76,"partition_style":"GPT","partitions":[{"partition_number":0,"size_gb":0.25390625,"partition_purpose":"EFI","file_system":"FAT32"},{"partition_number":1,"size_gb":0.015625,"partition_purpose":"MSR","file_system":null},{"partition_number":2,"size_gb":146.484375,"partition_purpose":"OS","file_system":"NTFS","drive_letter":"C:"},{"partition_number":3,"size_gb":1.46484375,"partition_purpose":"Recovery","file_system":"NTFS"},{"partition_number":4,"size_gb":317.35,"partition_purpose":"Data","file_system":"NTFS","drive_letter":"D:"},{"partition_number":5,"size_gb":0.1953125,"partition_purpose":"Other","file_system":"FAT32","volume_name":"BIOS","drive_letter":"E:"}]}]'
-WHERE id = (SELECT id FROM (SELECT id FROM hardware_info ORDER BY id LIMIT 1 OFFSET 5) t);
+WHERE id = (SELECT id FROM (SELECT id FROM `#__hardware_info` ORDER BY id LIMIT 1 OFFSET 5) t);
-- No disk layout data (ЭЛ00-100003) — will show "no data" for partition check
-UPDATE hardware_info SET order_number = 'ЭЛ00-100003'
+UPDATE `#__hardware_info` SET order_number = 'ЭЛ00-100003'
WHERE order_number NOT LIKE 'ЭЛ00-%' AND order_number NOT LIKE 'ЛЕ00-%' AND order_number NOT LIKE 'ИП00-%'
- AND id = (SELECT id FROM (SELECT id FROM hardware_info WHERE order_number NOT LIKE 'ЭЛ00-%' AND order_number NOT LIKE 'ЛЕ00-%' AND order_number NOT LIKE 'ИП00-%' ORDER BY id LIMIT 1) t);
+ AND id = (SELECT id FROM (SELECT id FROM `#__hardware_info` WHERE order_number NOT LIKE 'ЭЛ00-%' AND order_number NOT LIKE 'ЛЕ00-%' AND order_number NOT LIKE 'ИП00-%' ORDER BY id LIMIT 1) t);
-UPDATE hardware_info SET order_number = 'ЛЕ00-200003'
+UPDATE `#__hardware_info` SET order_number = 'ЛЕ00-200003'
WHERE order_number NOT LIKE 'ЭЛ00-%' AND order_number NOT LIKE 'ЛЕ00-%' AND order_number NOT LIKE 'ИП00-%'
- AND id = (SELECT id FROM (SELECT id FROM hardware_info WHERE order_number NOT LIKE 'ЭЛ00-%' AND order_number NOT LIKE 'ЛЕ00-%' AND order_number NOT LIKE 'ИП00-%' ORDER BY id LIMIT 1) t);
+ AND id = (SELECT id FROM (SELECT id FROM `#__hardware_info` WHERE order_number NOT LIKE 'ЭЛ00-%' AND order_number NOT LIKE 'ЛЕ00-%' AND order_number NOT LIKE 'ИП00-%' ORDER BY id LIMIT 1) t);
-- Done. Now trigger "Recheck Historical" from the admin UI to regenerate all compliance results.
diff --git a/FINAL_PRODUCTION_SYSTEM/database/seed_variants.sql b/FINAL_PRODUCTION_SYSTEM/database/seed_variants.sql
index 3684d8d..7878914 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/seed_variants.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/seed_variants.sql
@@ -2,9 +2,9 @@
-- Disk sizes: 256 GB (~238 GB = 243712 MB), 512 GB (~474 GB = 485376 MB), 1 TB (~931 GB = 953344 MB), 2 TB (~1862 GB = 1907200 MB)
-- Get product line IDs
-SET @pl1 = (SELECT id FROM product_lines WHERE order_pattern = 'ЭЛ00-######');
-SET @pl2 = (SELECT id FROM product_lines WHERE order_pattern = 'ЛЕ00-######');
-SET @pl3 = (SELECT id FROM product_lines WHERE order_pattern = 'ИП00-######');
+SET @pl1 = (SELECT id FROM `#__product_lines` WHERE order_pattern = 'ЭЛ00-######');
+SET @pl2 = (SELECT id FROM `#__product_lines` WHERE order_pattern = 'ЛЕ00-######');
+SET @pl3 = (SELECT id FROM `#__product_lines` WHERE order_pattern = 'ИП00-######');
-- ── Variants for Marketplace 1 ──────────────────────────────────
INSERT IGNORE INTO product_variants (line_id, name, disk_size_min_mb, disk_size_max_mb) VALUES
@@ -39,9 +39,9 @@ INSERT IGNORE INTO product_variants (line_id, name, disk_size_min_mb, disk_size_
-- 2 TB ~ 1907200 MB total: EFI 260 + MSR 16 + OS 500000 + Recovery 1500 + Data 1405224 + BIOS 200
-- ── 256 GB variants (all 3 product lines) ────────────────────────
-INSERT INTO product_variant_partitions (variant_id, partition_order, partition_name, partition_type, expected_size_mb, tolerance_percent, is_flexible)
+INSERT INTO `#__product_variant_partitions` (variant_id, partition_order, partition_name, partition_type, expected_size_mb, tolerance_percent, is_flexible)
SELECT v.id, p.partition_order, p.partition_name, p.partition_type, p.expected_size_mb, p.tolerance_percent, p.is_flexible
-FROM product_variants v
+FROM `#__product_variants` v
CROSS JOIN (
SELECT 1 AS partition_order, 'EFI' AS partition_name, 'EFI System' AS partition_type, 260 AS expected_size_mb, 1.00 AS tolerance_percent, 0 AS is_flexible UNION ALL
SELECT 2, 'MSR', 'Microsoft Reserved', 16, 1.00, 0 UNION ALL
@@ -54,9 +54,9 @@ WHERE v.name = '256 GB'
ON DUPLICATE KEY UPDATE expected_size_mb = VALUES(expected_size_mb), partition_type = VALUES(partition_type), is_flexible = VALUES(is_flexible);
-- ── 512 GB variants (all 3 product lines) ────────────────────────
-INSERT INTO product_variant_partitions (variant_id, partition_order, partition_name, partition_type, expected_size_mb, tolerance_percent, is_flexible)
+INSERT INTO `#__product_variant_partitions` (variant_id, partition_order, partition_name, partition_type, expected_size_mb, tolerance_percent, is_flexible)
SELECT v.id, p.partition_order, p.partition_name, p.partition_type, p.expected_size_mb, p.tolerance_percent, p.is_flexible
-FROM product_variants v
+FROM `#__product_variants` v
CROSS JOIN (
SELECT 1 AS partition_order, 'EFI' AS partition_name, 'EFI System' AS partition_type, 260 AS expected_size_mb, 1.00 AS tolerance_percent, 0 AS is_flexible UNION ALL
SELECT 2, 'MSR', 'Microsoft Reserved', 16, 1.00, 0 UNION ALL
@@ -69,9 +69,9 @@ WHERE v.name = '512 GB'
ON DUPLICATE KEY UPDATE expected_size_mb = VALUES(expected_size_mb), partition_type = VALUES(partition_type), is_flexible = VALUES(is_flexible);
-- ── 1 TB variants (all 3 product lines) ──────────────────────────
-INSERT INTO product_variant_partitions (variant_id, partition_order, partition_name, partition_type, expected_size_mb, tolerance_percent, is_flexible)
+INSERT INTO `#__product_variant_partitions` (variant_id, partition_order, partition_name, partition_type, expected_size_mb, tolerance_percent, is_flexible)
SELECT v.id, p.partition_order, p.partition_name, p.partition_type, p.expected_size_mb, p.tolerance_percent, p.is_flexible
-FROM product_variants v
+FROM `#__product_variants` v
CROSS JOIN (
SELECT 1 AS partition_order, 'EFI' AS partition_name, 'EFI System' AS partition_type, 260 AS expected_size_mb, 1.00 AS tolerance_percent, 0 AS is_flexible UNION ALL
SELECT 2, 'MSR', 'Microsoft Reserved', 16, 1.00, 0 UNION ALL
@@ -84,9 +84,9 @@ WHERE v.name = '1 TB'
ON DUPLICATE KEY UPDATE expected_size_mb = VALUES(expected_size_mb), partition_type = VALUES(partition_type), is_flexible = VALUES(is_flexible);
-- ── 2 TB variants (all 3 product lines) ──────────────────────────
-INSERT INTO product_variant_partitions (variant_id, partition_order, partition_name, partition_type, expected_size_mb, tolerance_percent, is_flexible)
+INSERT INTO `#__product_variant_partitions` (variant_id, partition_order, partition_name, partition_type, expected_size_mb, tolerance_percent, is_flexible)
SELECT v.id, p.partition_order, p.partition_name, p.partition_type, p.expected_size_mb, p.tolerance_percent, p.is_flexible
-FROM product_variants v
+FROM `#__product_variants` v
CROSS JOIN (
SELECT 1 AS partition_order, 'EFI' AS partition_name, 'EFI System' AS partition_type, 260 AS expected_size_mb, 1.00 AS tolerance_percent, 0 AS is_flexible UNION ALL
SELECT 2, 'MSR', 'Microsoft Reserved', 16, 1.00, 0 UNION ALL
diff --git a/FINAL_PRODUCTION_SYSTEM/database/task_pipeline_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/task_pipeline_migration.sql
index 5ad75bf..3ec8067 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/task_pipeline_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/task_pipeline_migration.sql
@@ -7,7 +7,7 @@
-- =============================================================
-- Global task template library (reusable across product lines)
-CREATE TABLE IF NOT EXISTS task_templates (
+CREATE TABLE IF NOT EXISTS `#__task_templates` (
id INT AUTO_INCREMENT PRIMARY KEY,
task_key VARCHAR(50) NOT NULL UNIQUE COMMENT 'Internal identifier (e.g. hardware_collection)',
task_name VARCHAR(100) NOT NULL COMMENT 'Display name',
@@ -23,7 +23,7 @@ CREATE TABLE IF NOT EXISTS task_templates (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Per-product-line task assignments (which tasks run, in what order, with overrides)
-CREATE TABLE IF NOT EXISTS product_line_tasks (
+CREATE TABLE IF NOT EXISTS `#__product_line_tasks` (
id INT AUTO_INCREMENT PRIMARY KEY,
product_line_id INT NOT NULL COMMENT 'FK to product_lines table',
task_template_id INT NOT NULL COMMENT 'FK to task_templates',
@@ -40,7 +40,7 @@ CREATE TABLE IF NOT EXISTS product_line_tasks (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Task execution log (tracks what ran during each activation)
-CREATE TABLE IF NOT EXISTS task_execution_log (
+CREATE TABLE IF NOT EXISTS `#__task_execution_log` (
id INT AUTO_INCREMENT PRIMARY KEY,
activation_attempt_id INT DEFAULT NULL COMMENT 'FK to activation_attempts',
product_line_id INT DEFAULT NULL,
@@ -62,7 +62,7 @@ CREATE TABLE IF NOT EXISTS task_execution_log (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Seed built-in task templates
-INSERT INTO task_templates (task_key, task_name, task_type, description, default_timeout_seconds, default_on_failure, is_system, icon) VALUES
+INSERT INTO `#__task_templates` (task_key, task_name, task_type, description, default_timeout_seconds, default_on_failure, is_system, icon) VALUES
('hardware_collection', 'Hardware Collection', 'built_in', 'Collect full hardware inventory (MB, CPU, RAM, GPU, disks, network)', 60, 'stop', 1, 'Cpu'),
('qc_compliance', 'QC Compliance Check', 'built_in', 'Run quality control checks (Secure Boot, BIOS version, HackBGRT)', 30, 'stop', 1, 'ShieldCheck'),
('oem_activation', 'OEM Key Activation', 'built_in', 'Request OEM key from server, install and activate Windows', 180, 'stop', 1, 'Key'),
@@ -73,7 +73,7 @@ INSERT INTO task_templates (task_key, task_name, task_type, description, default
ON DUPLICATE KEY UPDATE task_name = VALUES(task_name);
-- Seed example custom tasks (disabled by default, admin can enable per product line)
-INSERT INTO task_templates (task_key, task_name, task_type, description, default_code, default_timeout_seconds, default_on_failure, is_system, icon) VALUES
+INSERT INTO `#__task_templates` (task_key, task_name, task_type, description, default_code, default_timeout_seconds, default_on_failure, is_system, icon) VALUES
('set_power_plan', 'Set Power Plan', 'custom', 'Configure Windows power plan (High Performance, Balanced, etc.)',
'powercfg /setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c # High Performance', 15, 'skip', 0, 'Zap'),
('disable_sleep', 'Disable Sleep Mode', 'custom', 'Prevent the PC from going to sleep',
diff --git a/FINAL_PRODUCTION_SYSTEM/database/temp_password_hash_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/temp_password_hash_migration.sql
index 7188269..e2b4cef 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/temp_password_hash_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/temp_password_hash_migration.sql
@@ -3,7 +3,7 @@
-- and must be followed by running the PHP migration script below.
-- Step 1: Widen column to hold bcrypt hashes (60 chars)
-ALTER TABLE technicians MODIFY COLUMN temp_password VARCHAR(255) DEFAULT NULL;
+ALTER TABLE `#__technicians` MODIFY COLUMN temp_password VARCHAR(255) DEFAULT NULL;
-- Step 2: The existing plaintext temp passwords must be hashed via PHP
-- because SQL cannot generate bcrypt hashes natively.
diff --git a/FINAL_PRODUCTION_SYSTEM/database/unallocated_space_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/unallocated_space_migration.sql
index 1827a70..606a568 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/unallocated_space_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/unallocated_space_migration.sql
@@ -2,7 +2,7 @@
-- Adds configurable max unallocated space limit for partition QC
-- 1. Add per-variant unallocated space limit
-ALTER TABLE product_variants
+ALTER TABLE `#__product_variants`
ADD COLUMN IF NOT EXISTS max_unallocated_mb INT DEFAULT NULL
COMMENT 'Max allowed unallocated disk space in MB (NULL = use global setting)';
diff --git a/FINAL_PRODUCTION_SYSTEM/database/upgrade_system_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/upgrade_system_migration.sql
index d1ee47a..7037a12 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/upgrade_system_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/upgrade_system_migration.sql
@@ -5,7 +5,7 @@
-- verify, rollback. Provides full audit trail.
-- =============================================================
-CREATE TABLE IF NOT EXISTS upgrade_history (
+CREATE TABLE IF NOT EXISTS `#__upgrade_history` (
id INT AUTO_INCREMENT PRIMARY KEY,
from_version VARCHAR(20) NOT NULL,
to_version VARCHAR(20) NOT NULL,
diff --git a/FINAL_PRODUCTION_SYSTEM/database/usb_devices_migration.sql b/FINAL_PRODUCTION_SYSTEM/database/usb_devices_migration.sql
index ca10291..335bdea 100644
--- a/FINAL_PRODUCTION_SYSTEM/database/usb_devices_migration.sql
+++ b/FINAL_PRODUCTION_SYSTEM/database/usb_devices_migration.sql
@@ -4,7 +4,7 @@
-- Tracks registered USB devices for passwordless technician auth.
-- =============================================================
-CREATE TABLE IF NOT EXISTS usb_devices (
+CREATE TABLE IF NOT EXISTS `#__usb_devices` (
device_id INT AUTO_INCREMENT PRIMARY KEY,
device_serial_number VARCHAR(255) NOT NULL UNIQUE,
device_name VARCHAR(255) NOT NULL,
diff --git a/FINAL_PRODUCTION_SYSTEM/functions/acl.php b/FINAL_PRODUCTION_SYSTEM/functions/acl.php
index 8b602f1..8411927 100644
--- a/FINAL_PRODUCTION_SYSTEM/functions/acl.php
+++ b/FINAL_PRODUCTION_SYSTEM/functions/acl.php
@@ -110,7 +110,7 @@ function aclLogDenial($userId, $userType, $permissionKey, $session) {
try {
$stmt = $pdo->prepare("
- INSERT INTO rbac_permission_denials (
+ INSERT INTO `" . t('rbac_permission_denials') . "` (
admin_id, session_id, admin_role, requested_action,
endpoint, ip_address, user_agent
) VALUES (?, ?, ?, ?, ?, ?, ?)
@@ -150,7 +150,7 @@ function aclGetEffectivePermissions($userType, $userId) {
// Check if super_admin
$isSuperAdmin = false;
if ($roleId) {
- $stmt = $pdo->prepare("SELECT role_name FROM acl_roles WHERE id = ?");
+ $stmt = $pdo->prepare("SELECT role_name FROM `" . t('acl_roles') . "` WHERE id = ?");
$stmt->execute([$roleId]);
$role = $stmt->fetch(PDO::FETCH_ASSOC);
if ($role && $role['role_name'] === 'super_admin') {
@@ -159,7 +159,7 @@ function aclGetEffectivePermissions($userType, $userId) {
}
// Get all permissions
- $stmt = $pdo->query("SELECT id, permission_key, display_name, category_id, is_dangerous FROM acl_permissions ORDER BY id");
+ $stmt = $pdo->query("SELECT id, permission_key, display_name, category_id, is_dangerous FROM `" . t('acl_permissions') . "` ORDER BY id");
$allPerms = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Get role permissions
@@ -167,8 +167,8 @@ function aclGetEffectivePermissions($userType, $userId) {
if ($roleId) {
$stmt = $pdo->prepare("
SELECT p.permission_key
- FROM acl_role_permissions rp
- INNER JOIN acl_permissions p ON rp.permission_id = p.id
+ FROM `" . t('acl_role_permissions') . "` rp
+ INNER JOIN `" . t('acl_permissions') . "` p ON rp.permission_id = p.id
WHERE rp.role_id = ?
");
$stmt->execute([$roleId]);
@@ -179,8 +179,8 @@ function aclGetEffectivePermissions($userType, $userId) {
$overrides = [];
$stmt = $pdo->prepare("
SELECT p.permission_key, uo.is_granted, uo.reason, uo.expires_at
- FROM acl_user_overrides uo
- INNER JOIN acl_permissions p ON uo.permission_id = p.id
+ FROM `" . t('acl_user_overrides') . "` uo
+ INNER JOIN `" . t('acl_permissions') . "` p ON uo.permission_id = p.id
WHERE uo.user_type = ? AND uo.user_id = ?
AND (uo.expires_at IS NULL OR uo.expires_at > NOW())
");
@@ -232,12 +232,12 @@ function aclGetEffectivePermissions($userType, $userId) {
function aclGetRoleById($roleId) {
global $pdo;
try {
- $stmt = $pdo->prepare("SELECT * FROM acl_roles WHERE id = ?");
+ $stmt = $pdo->prepare("SELECT * FROM `" . t('acl_roles') . "` WHERE id = ?");
$stmt->execute([$roleId]);
$role = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$role) return null;
- $stmt = $pdo->prepare("SELECT permission_id FROM acl_role_permissions WHERE role_id = ?");
+ $stmt = $pdo->prepare("SELECT permission_id FROM `" . t('acl_role_permissions') . "` WHERE role_id = ?");
$stmt->execute([$roleId]);
$role['permission_ids'] = $stmt->fetchAll(PDO::FETCH_COLUMN);
@@ -255,8 +255,8 @@ function aclListRoles($roleType = null) {
global $pdo;
try {
$sql = "SELECT r.*,
- (SELECT COUNT(*) FROM acl_role_permissions WHERE role_id = r.id) as permission_count
- FROM acl_roles r WHERE 1=1";
+ (SELECT COUNT(*) FROM `" . t('acl_role_permissions') . "` WHERE role_id = r.id) as permission_count
+ FROM `" . t('acl_roles') . "` r WHERE 1=1";
$params = [];
if ($roleType) {
$sql .= " AND r.role_type = ?";
@@ -291,7 +291,7 @@ function aclCreateRole($name, $displayName, $description, $roleType, $color, $pe
$pdo->beginTransaction();
$stmt = $pdo->prepare("
- INSERT INTO acl_roles (role_name, display_name, description, role_type, color, is_system_role, priority, created_by)
+ INSERT INTO `" . t('acl_roles') . "` (role_name, display_name, description, role_type, color, is_system_role, priority, created_by)
VALUES (?, ?, ?, ?, ?, 0, 0, ?)
");
$stmt->execute([$name, $displayName, $description, $roleType, $color, $actorId]);
@@ -299,7 +299,7 @@ function aclCreateRole($name, $displayName, $description, $roleType, $color, $pe
// Assign permissions
if (!empty($permissionIds)) {
- $stmt = $pdo->prepare("INSERT INTO acl_role_permissions (role_id, permission_id, granted_by) VALUES (?, ?, ?)");
+ $stmt = $pdo->prepare("INSERT INTO `" . t('acl_role_permissions') . "` (role_id, permission_id, granted_by) VALUES (?, ?, ?)");
foreach ($permissionIds as $permId) {
$stmt->execute([$roleId, $permId, $actorId]);
}
@@ -329,7 +329,7 @@ function aclUpdateRole($roleId, $data, $actorId) {
// Optimistic locking: reject if role was modified since the client loaded it
if (!empty($data['expected_updated_at'])) {
- $stmt = $pdo->prepare("SELECT updated_at FROM acl_roles WHERE id = ?");
+ $stmt = $pdo->prepare("SELECT updated_at FROM `" . t('acl_roles') . "` WHERE id = ?");
$stmt->execute([$roleId]);
$currentUpdatedAt = $stmt->fetchColumn();
if ($currentUpdatedAt && $currentUpdatedAt !== $data['expected_updated_at']) {
@@ -372,7 +372,7 @@ function aclUpdateRole($roleId, $data, $actorId) {
if (!empty($fields)) {
$params[] = $roleId;
- $stmt = $pdo->prepare("UPDATE acl_roles SET " . implode(', ', $fields) . " WHERE id = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('acl_roles') . "` SET " . implode(', ', $fields) . " WHERE id = ?");
$stmt->execute($params);
}
@@ -381,11 +381,11 @@ function aclUpdateRole($roleId, $data, $actorId) {
$oldPermIds = $role['permission_ids'];
// Clear existing
- $stmt = $pdo->prepare("DELETE FROM acl_role_permissions WHERE role_id = ?");
+ $stmt = $pdo->prepare("DELETE FROM `" . t('acl_role_permissions') . "` WHERE role_id = ?");
$stmt->execute([$roleId]);
// Insert new
- $stmt = $pdo->prepare("INSERT INTO acl_role_permissions (role_id, permission_id, granted_by) VALUES (?, ?, ?)");
+ $stmt = $pdo->prepare("INSERT INTO `" . t('acl_role_permissions') . "` (role_id, permission_id, granted_by) VALUES (?, ?, ?)");
foreach ($data['permission_ids'] as $permId) {
$stmt->execute([$roleId, $permId, $actorId]);
}
@@ -428,7 +428,7 @@ function aclDeleteRole($roleId, $actorId) {
$pdo->beginTransaction();
// role_permissions will cascade delete
- $stmt = $pdo->prepare("DELETE FROM acl_roles WHERE id = ? AND is_system_role = 0");
+ $stmt = $pdo->prepare("DELETE FROM `" . t('acl_roles') . "` WHERE id = ? AND is_system_role = 0");
$stmt->execute([$roleId]);
aclLogChange($actorId, 'delete_role', 'role', $roleId, $role['display_name'],
@@ -480,10 +480,10 @@ function aclAssignPermissions($roleId, $permissionIds, $actorId) {
$oldPermIds = $role['permission_ids'];
- $stmt = $pdo->prepare("DELETE FROM acl_role_permissions WHERE role_id = ?");
+ $stmt = $pdo->prepare("DELETE FROM `" . t('acl_role_permissions') . "` WHERE role_id = ?");
$stmt->execute([$roleId]);
- $stmt = $pdo->prepare("INSERT INTO acl_role_permissions (role_id, permission_id, granted_by) VALUES (?, ?, ?)");
+ $stmt = $pdo->prepare("INSERT INTO `" . t('acl_role_permissions') . "` (role_id, permission_id, granted_by) VALUES (?, ?, ?)");
foreach ($permissionIds as $permId) {
$stmt->execute([$roleId, $permId, $actorId]);
}
@@ -511,7 +511,7 @@ function aclSetUserOverride($userType, $userId, $permissionId, $isGranted, $reas
global $pdo;
try {
$stmt = $pdo->prepare("
- INSERT INTO acl_user_overrides (user_type, user_id, permission_id, is_granted, reason, expires_at, created_by)
+ INSERT INTO `" . t('acl_user_overrides') . "` (user_type, user_id, permission_id, is_granted, reason, expires_at, created_by)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE is_granted = VALUES(is_granted), reason = VALUES(reason),
expires_at = VALUES(expires_at), created_by = VALUES(created_by),
@@ -520,7 +520,7 @@ function aclSetUserOverride($userType, $userId, $permissionId, $isGranted, $reas
$stmt->execute([$userType, $userId, $permissionId, $isGranted ? 1 : 0, $reason, $expiresAt, $actorId]);
// Get permission key for logging
- $stmt2 = $pdo->prepare("SELECT permission_key FROM acl_permissions WHERE id = ?");
+ $stmt2 = $pdo->prepare("SELECT permission_key FROM `" . t('acl_permissions') . "` WHERE id = ?");
$stmt2->execute([$permissionId]);
$permKey = $stmt2->fetchColumn();
@@ -544,14 +544,14 @@ function aclRemoveUserOverride($userType, $userId, $permissionId, $actorId) {
// Get current override for logging
$stmt = $pdo->prepare("
SELECT uo.*, p.permission_key
- FROM acl_user_overrides uo
- INNER JOIN acl_permissions p ON uo.permission_id = p.id
+ FROM `" . t('acl_user_overrides') . "` uo
+ INNER JOIN `" . t('acl_permissions') . "` p ON uo.permission_id = p.id
WHERE uo.user_type = ? AND uo.user_id = ? AND uo.permission_id = ?
");
$stmt->execute([$userType, $userId, $permissionId]);
$old = $stmt->fetch(PDO::FETCH_ASSOC);
- $stmt = $pdo->prepare("DELETE FROM acl_user_overrides WHERE user_type = ? AND user_id = ? AND permission_id = ?");
+ $stmt = $pdo->prepare("DELETE FROM `" . t('acl_user_overrides') . "` WHERE user_type = ? AND user_id = ? AND permission_id = ?");
$stmt->execute([$userType, $userId, $permissionId]);
if ($old) {
@@ -575,8 +575,8 @@ function aclGetUserOverrides($userType, $userId) {
try {
$stmt = $pdo->prepare("
SELECT uo.*, p.permission_key, p.display_name as permission_name, p.category_id
- FROM acl_user_overrides uo
- INNER JOIN acl_permissions p ON uo.permission_id = p.id
+ FROM `" . t('acl_user_overrides') . "` uo
+ INNER JOIN `" . t('acl_permissions') . "` p ON uo.permission_id = p.id
WHERE uo.user_type = ? AND uo.user_id = ?
ORDER BY p.id
");
@@ -599,11 +599,11 @@ function aclListPermissions($categoryId = null) {
global $pdo;
try {
// Get categories
- $stmt = $pdo->query("SELECT * FROM acl_permission_categories ORDER BY sort_order");
+ $stmt = $pdo->query("SELECT * FROM `" . t('acl_permission_categories') . "` ORDER BY sort_order");
$categories = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Get permissions
- $sql = "SELECT * FROM acl_permissions";
+ $sql = "SELECT * FROM `" . t('acl_permissions') . "`";
$params = [];
if ($categoryId) {
$sql .= " WHERE category_id = ?";
@@ -646,11 +646,11 @@ function aclGetUserRoleId($userType, $userId) {
global $pdo;
try {
if ($userType === 'admin') {
- $stmt = $pdo->prepare("SELECT custom_role_id FROM admin_users WHERE id = ?");
+ $stmt = $pdo->prepare("SELECT custom_role_id FROM `" . t('admin_users') . "` WHERE id = ?");
$stmt->execute([$userId]);
return $stmt->fetchColumn() ?: null;
} elseif ($userType === 'technician') {
- $stmt = $pdo->prepare("SELECT role_id FROM technicians WHERE id = ?");
+ $stmt = $pdo->prepare("SELECT role_id FROM `" . t('technicians') . "` WHERE id = ?");
$stmt->execute([$userId]);
return $stmt->fetchColumn() ?: null;
}
@@ -668,8 +668,8 @@ function aclGetRoleUserCount($roleId) {
try {
$stmt = $pdo->prepare("
SELECT
- (SELECT COUNT(*) FROM admin_users WHERE custom_role_id = ?) +
- (SELECT COUNT(*) FROM technicians WHERE role_id = ?) as total
+ (SELECT COUNT(*) FROM `" . t('admin_users') . "` WHERE custom_role_id = ?) +
+ (SELECT COUNT(*) FROM `" . t('technicians') . "` WHERE role_id = ?) as total
");
$stmt->execute([$roleId, $roleId]);
return (int)$stmt->fetchColumn();
@@ -686,7 +686,7 @@ function aclLogChange($actorId, $action, $targetType, $targetId, $targetName, $o
global $pdo;
try {
$stmt = $pdo->prepare("
- INSERT INTO acl_change_log (actor_id, action, target_type, target_id, target_name, old_value, new_value, ip_address)
+ INSERT INTO `" . t('acl_change_log') . "` (actor_id, action, target_type, target_id, target_name, old_value, new_value, ip_address)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([
diff --git a/FINAL_PRODUCTION_SYSTEM/functions/admin-helpers.php b/FINAL_PRODUCTION_SYSTEM/functions/admin-helpers.php
index 8e3855e..51bc3d0 100644
--- a/FINAL_PRODUCTION_SYSTEM/functions/admin-helpers.php
+++ b/FINAL_PRODUCTION_SYSTEM/functions/admin-helpers.php
@@ -27,8 +27,8 @@ function validateAdminSession() {
s.id, s.admin_id, s.expires_at, s.last_activity,
u.username, u.full_name, u.role, u.is_active, u.preferred_language,
u.password_changed_at, u.must_change_password
- FROM admin_sessions s
- JOIN admin_users u ON s.admin_id = u.id
+ FROM `" . t('admin_sessions') . "` s
+ JOIN `" . t('admin_users') . "` u ON s.admin_id = u.id
WHERE s.session_token = ? AND s.is_active = 1
");
$stmt->execute([$_SESSION['admin_token']]);
@@ -41,7 +41,7 @@ function validateAdminSession() {
// Check if session expired (hard expiry set at creation time)
if (strtotime($session['expires_at']) < time()) {
- $stmt = $pdo->prepare("UPDATE admin_sessions SET is_active = 0 WHERE id = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('admin_sessions') . "` SET is_active = 0 WHERE id = ?");
$stmt->execute([$session['id']]);
return false;
}
@@ -50,7 +50,7 @@ function validateAdminSession() {
$timeoutMinutes = (int) getConfigWithDefault('admin_session_timeout_minutes', DEFAULT_ADMIN_SESSION_TIMEOUT_MINUTES);
$timeoutSeconds = $timeoutMinutes * 60;
if (strtotime($session['last_activity']) < (time() - $timeoutSeconds)) {
- $stmt = $pdo->prepare("UPDATE admin_sessions SET is_active = 0 WHERE id = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('admin_sessions') . "` SET is_active = 0 WHERE id = ?");
$stmt->execute([$session['id']]);
return false;
}
@@ -71,7 +71,7 @@ function validateAdminSession() {
}
// Update last activity
- $stmt = $pdo->prepare("UPDATE admin_sessions SET last_activity = NOW() WHERE id = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('admin_sessions') . "` SET last_activity = NOW() WHERE id = ?");
$stmt->execute([$session['id']]);
return $session;
@@ -93,7 +93,7 @@ function logAdminActivity($admin_id, $session_id, $action, $description = '') {
global $pdo;
try {
$stmt = $pdo->prepare("
- INSERT INTO admin_activity_log (admin_id, session_id, action, description, ip_address, user_agent)
+ INSERT INTO `" . t('admin_activity_log') . "` (admin_id, session_id, action, description, ip_address, user_agent)
VALUES (?, ?, ?, ?, ?, ?)
");
$stmt->execute([
@@ -125,7 +125,7 @@ function authenticateAdmin($username, $password) {
$lockoutMinutes = (int) getConfigWithDefault('admin_lockout_duration_minutes', DEFAULT_ADMIN_LOCKOUT_MINUTES);
$sessionTimeout = (int) getConfigWithDefault('admin_session_timeout_minutes', DEFAULT_ADMIN_SESSION_TIMEOUT_MINUTES);
- $stmt = $pdo->prepare("SELECT * FROM admin_users WHERE username = ? AND is_active = 1");
+ $stmt = $pdo->prepare("SELECT * FROM `" . t('admin_users') . "` WHERE username = ? AND is_active = 1");
$stmt->execute([$username]);
$admin = $stmt->fetch();
@@ -153,7 +153,7 @@ function authenticateAdmin($username, $password) {
}
$stmt = $pdo->prepare("
- UPDATE admin_users
+ UPDATE `" . t('admin_users') . "`
SET failed_login_attempts = ?, locked_until = ?
WHERE id = ?
");
@@ -172,7 +172,7 @@ function authenticateAdmin($username, $password) {
$expires_at = date('Y-m-d H:i:s', time() + ($sessionTimeout * 60));
$stmt = $pdo->prepare("
- INSERT INTO admin_sessions (admin_id, session_token, ip_address, user_agent, expires_at)
+ INSERT INTO `" . t('admin_sessions') . "` (admin_id, session_token, ip_address, user_agent, expires_at)
VALUES (?, ?, ?, ?, ?)
");
$stmt->execute([
@@ -182,7 +182,7 @@ function authenticateAdmin($username, $password) {
// Reset failed attempts
$stmt = $pdo->prepare("
- UPDATE admin_users
+ UPDATE `" . t('admin_users') . "`
SET failed_login_attempts = 0, locked_until = NULL, last_login = NOW(), last_login_ip = ?
WHERE id = ?
");
@@ -217,7 +217,7 @@ function getUploadErrorMessage(int $errorCode): string {
*/
function saveConfigBatch(PDO $pdo, array $configs, array $descriptions = []): void {
$stmt = $pdo->prepare("
- INSERT INTO system_config (config_key, config_value, description, updated_at)
+ INSERT INTO `" . t('system_config') . "` (config_key, config_value, description, updated_at)
VALUES (?, ?, ?, NOW())
ON DUPLICATE KEY UPDATE config_value = ?, updated_at = NOW()
");
@@ -251,8 +251,8 @@ function isActorSuperAdmin($adminId) {
global $pdo;
try {
$stmt = $pdo->prepare("
- SELECT r.role_name FROM acl_roles r
- INNER JOIN admin_users u ON u.custom_role_id = r.id
+ SELECT r.role_name FROM `" . t('acl_roles') . "` r
+ INNER JOIN `" . t('admin_users') . "` u ON u.custom_role_id = r.id
WHERE u.id = ? AND r.role_name = 'super_admin'
");
$stmt->execute([$adminId]);
diff --git a/FINAL_PRODUCTION_SYSTEM/functions/csv-import.php b/FINAL_PRODUCTION_SYSTEM/functions/csv-import.php
index 11ee83b..91b7cb5 100644
--- a/FINAL_PRODUCTION_SYSTEM/functions/csv-import.php
+++ b/FINAL_PRODUCTION_SYSTEM/functions/csv-import.php
@@ -180,14 +180,14 @@ function importComprehensiveKeyRow($row, $format) {
try {
// Check if key already exists
- $stmt = $pdo->prepare("SELECT id, key_status FROM oem_keys WHERE product_key = ?");
+ $stmt = $pdo->prepare("SELECT id, key_status FROM `" . t('oem_keys') . "` WHERE product_key = ?");
$stmt->execute([$product_key]);
$existing = $stmt->fetch(PDO::FETCH_ASSOC);
if ($existing) {
// Update existing key
$stmt = $pdo->prepare("
- UPDATE oem_keys
+ UPDATE `" . t('oem_keys') . "`
SET key_status = ?, oem_identifier = ?, roll_serial = ?, fail_counter = ?,
last_use_date = ?, last_use_time = ?, first_usage_date = ?, first_usage_time = ?,
updated_at = NOW()
@@ -200,7 +200,7 @@ function importComprehensiveKeyRow($row, $format) {
} else {
// Insert new key
$stmt = $pdo->prepare("
- INSERT INTO oem_keys (product_key, oem_identifier, roll_serial, key_status, fail_counter,
+ INSERT INTO `" . t('oem_keys') . "` (product_key, oem_identifier, roll_serial, key_status, fail_counter,
last_use_date, last_use_time, first_usage_date, first_usage_time, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())
");
@@ -242,7 +242,7 @@ function importStandardKeyRow($row, $format) {
try {
// Check if key already exists
- $stmt = $pdo->prepare("SELECT id, key_status FROM oem_keys WHERE product_key = ?");
+ $stmt = $pdo->prepare("SELECT id, key_status FROM `" . t('oem_keys') . "` WHERE product_key = ?");
$stmt->execute([$product_key]);
$existing = $stmt->fetch(PDO::FETCH_ASSOC);
@@ -250,7 +250,7 @@ function importStandardKeyRow($row, $format) {
// Update existing key if status changed
if ($existing['key_status'] !== $key_status) {
$stmt = $pdo->prepare("
- UPDATE oem_keys
+ UPDATE `" . t('oem_keys') . "`
SET key_status = ?, oem_identifier = ?, barcode = ?, updated_at = NOW()
WHERE product_key = ?
");
@@ -262,7 +262,7 @@ function importStandardKeyRow($row, $format) {
} else {
// Insert new key
$stmt = $pdo->prepare("
- INSERT INTO oem_keys (product_key, oem_identifier, barcode, key_status, roll_serial, created_at)
+ INSERT INTO `" . t('oem_keys') . "` (product_key, oem_identifier, barcode, key_status, roll_serial, created_at)
VALUES (?, ?, ?, ?, 'imported', NOW())
");
$stmt->execute([$product_key, $oem_identifier, $barcode, $key_status]);
@@ -321,7 +321,7 @@ function importActivationAttempts($key_id, $row, $format) {
if (!empty($attempt['attempted_date']) && !empty($attempt['technician_id'])) {
try {
$stmt = $pdo->prepare("
- INSERT INTO activation_attempts
+ INSERT INTO `" . t('activation_attempts') . "`
(key_id, technician_id, order_number, attempt_number, attempt_result,
attempted_date, attempted_time, attempted_at)
VALUES (?, ?, ?, ?, ?, ?, ?, NOW())
diff --git a/FINAL_PRODUCTION_SYSTEM/functions/db-helpers.php b/FINAL_PRODUCTION_SYSTEM/functions/db-helpers.php
new file mode 100644
index 0000000..30e6976
--- /dev/null
+++ b/FINAL_PRODUCTION_SYSTEM/functions/db-helpers.php
@@ -0,0 +1,37 @@
+prepare("SELECT * FROM integrations WHERE integration_key = ?");
+ $stmt = $pdo->prepare("SELECT * FROM `" . t('integrations') . "` WHERE integration_key = ?");
$stmt->execute([$key]);
$row = $stmt->fetch();
@@ -51,10 +51,10 @@ function updateIntegrationStatus(int $id, string $status, ?string $error = null)
try {
if ($error !== null) {
- $stmt = $pdo->prepare("UPDATE integrations SET status = ?, last_error = ?, updated_at = NOW() WHERE id = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('integrations') . "` SET status = ?, last_error = ?, updated_at = NOW() WHERE id = ?");
$stmt->execute([$status, $error, $id]);
} else {
- $stmt = $pdo->prepare("UPDATE integrations SET status = ?, updated_at = NOW() WHERE id = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('integrations') . "` SET status = ?, updated_at = NOW() WHERE id = ?");
$stmt->execute([$status, $id]);
}
@@ -81,7 +81,7 @@ function dispatchIntegrationEvent(string $integrationKey, string $eventType, arr
try {
$stmt = $pdo->prepare("
- INSERT INTO integration_events (integration_id, event_type, payload, status, created_at)
+ INSERT INTO `" . t('integration_events') . "` (integration_id, event_type, payload, status, created_at)
VALUES (?, ?, ?, 'pending', NOW())
");
$stmt->execute([
@@ -106,7 +106,7 @@ function dispatchEventToAll(string $eventType, array $payload): void {
global $pdo;
try {
- $stmt = $pdo->query("SELECT integration_key FROM integrations WHERE enabled = 1");
+ $stmt = $pdo->query("SELECT integration_key FROM `" . t('integrations') . "` WHERE enabled = 1");
$keys = $stmt->fetchAll(PDO::FETCH_COLUMN);
foreach ($keys as $key) {
@@ -124,7 +124,7 @@ function deliverIntegrationEvent(int $eventId, array $integration): void {
global $pdo;
try {
- $stmt = $pdo->prepare("SELECT * FROM integration_events WHERE id = ?");
+ $stmt = $pdo->prepare("SELECT * FROM `" . t('integration_events') . "` WHERE id = ?");
$stmt->execute([$eventId]);
$event = $stmt->fetch();
if (!$event) return;
@@ -137,7 +137,7 @@ function deliverIntegrationEvent(int $eventId, array $integration): void {
$handlerFile = dirname(__DIR__) . "/functions/integrations/{$key}-handler.php";
if (!file_exists($handlerFile)) {
// No handler yet — mark as skipped
- $stmt = $pdo->prepare("UPDATE integration_events SET status = 'skipped', processed_at = NOW(), error_message = 'No handler file' WHERE id = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('integration_events') . "` SET status = 'skipped', processed_at = NOW(), error_message = 'No handler file' WHERE id = ?");
$stmt->execute([$eventId]);
return;
}
@@ -146,7 +146,7 @@ function deliverIntegrationEvent(int $eventId, array $integration): void {
$handlerFunc = 'handle_' . str_replace('-', '_', $key) . '_event';
if (!function_exists($handlerFunc)) {
- $stmt = $pdo->prepare("UPDATE integration_events SET status = 'skipped', processed_at = NOW(), error_message = 'Handler function not found' WHERE id = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('integration_events') . "` SET status = 'skipped', processed_at = NOW(), error_message = 'Handler function not found' WHERE id = ?");
$stmt->execute([$eventId]);
return;
}
@@ -157,7 +157,7 @@ function deliverIntegrationEvent(int $eventId, array $integration): void {
// Update event with result
$status = ($result['success'] ?? false) ? 'sent' : 'failed';
$stmt = $pdo->prepare("
- UPDATE integration_events
+ UPDATE `" . t('integration_events') . "`
SET status = ?, response_code = ?, response_body = ?, error_message = ?, processed_at = NOW()
WHERE id = ?
");
@@ -172,7 +172,7 @@ function deliverIntegrationEvent(int $eventId, array $integration): void {
// Update integration status
if ($status === 'sent') {
updateIntegrationStatus($integration['id'], 'connected');
- $pdo->prepare("UPDATE integrations SET last_sync_at = NOW() WHERE id = ?")->execute([$integration['id']]);
+ $pdo->prepare("UPDATE `" . t('integrations') . "` SET last_sync_at = NOW() WHERE id = ?")->execute([$integration['id']]);
} else {
updateIntegrationStatus($integration['id'], 'error', $result['error'] ?? 'Delivery failed');
}
@@ -180,7 +180,7 @@ function deliverIntegrationEvent(int $eventId, array $integration): void {
} catch (Exception $e) {
error_log("deliverIntegrationEvent($eventId) failed: " . $e->getMessage());
try {
- $stmt = $pdo->prepare("UPDATE integration_events SET status = 'failed', processed_at = NOW(), error_message = ? WHERE id = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('integration_events') . "` SET status = 'failed', processed_at = NOW(), error_message = ? WHERE id = ?");
$stmt->execute([$e->getMessage(), $eventId]);
} catch (PDOException $ex) {
// Ignore
@@ -201,7 +201,7 @@ function retryFailedEvents(string $integrationKey, int $limit = 50): array {
try {
$stmt = $pdo->prepare("
- SELECT id FROM integration_events
+ SELECT id FROM `" . t('integration_events') . "`
WHERE integration_id = ? AND status IN ('failed', 'pending')
ORDER BY created_at ASC
LIMIT ?
@@ -216,7 +216,7 @@ function retryFailedEvents(string $integrationKey, int $limit = 50): array {
$retried++;
// Check if it succeeded
- $check = $pdo->prepare("SELECT status FROM integration_events WHERE id = ?");
+ $check = $pdo->prepare("SELECT status FROM `" . t('integration_events') . "` WHERE id = ?");
$check->execute([$eid]);
if ($check->fetchColumn() === 'sent') {
$succeeded++;
diff --git a/FINAL_PRODUCTION_SYSTEM/functions/key-helpers.php b/FINAL_PRODUCTION_SYSTEM/functions/key-helpers.php
index ba37328..63d385f 100644
--- a/FINAL_PRODUCTION_SYSTEM/functions/key-helpers.php
+++ b/FINAL_PRODUCTION_SYSTEM/functions/key-helpers.php
@@ -39,7 +39,7 @@ function allocateKeyAtomically($pdo, $technician_id, $order_number) {
}
$stmt = $pdo->prepare("
- SELECT * FROM oem_keys
+ SELECT * FROM `" . t('oem_keys') . "`
WHERE key_status IN ('unused', 'retry')
AND (fail_counter < " . MAX_KEY_FAIL_COUNTER . " OR key_status = 'unused')
ORDER BY
@@ -55,7 +55,7 @@ function allocateKeyAtomically($pdo, $technician_id, $order_number) {
if ($key) {
$stmt = $pdo->prepare("
- UPDATE oem_keys
+ UPDATE `" . t('oem_keys') . "`
SET key_status = 'allocated',
last_use_date = CURDATE(),
last_use_time = CURTIME(),
diff --git a/FINAL_PRODUCTION_SYSTEM/functions/license-helpers.php b/FINAL_PRODUCTION_SYSTEM/functions/license-helpers.php
index 44c904f..9aa8877 100644
--- a/FINAL_PRODUCTION_SYSTEM/functions/license-helpers.php
+++ b/FINAL_PRODUCTION_SYSTEM/functions/license-helpers.php
@@ -84,7 +84,7 @@ function generateInstanceId(PDO $pdo): string {
// Get database creation timestamp (stable across restarts)
try {
- $stmt = $pdo->query("SELECT MIN(created_at) AS first_record FROM admin_users");
+ $stmt = $pdo->query("SELECT MIN(created_at) AS first_record FROM `" . t('admin_users') . "`");
$row = $stmt->fetch();
$dbSeed = $row['first_record'] ?? date('Y-m-d');
} catch (Exception $e) {
@@ -171,7 +171,7 @@ function createLicenseJwt(array $payload, string $secret = 'keygate-community-ve
*/
function getCurrentLicense(PDO $pdo): ?array {
try {
- $stmt = $pdo->query("SELECT * FROM license_info WHERE is_active = 1 ORDER BY id DESC LIMIT 1");
+ $stmt = $pdo->query("SELECT * FROM `" . t('license_info') . "` WHERE is_active = 1 ORDER BY id DESC LIMIT 1");
return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
} catch (Exception $e) {
// Table may not exist yet (pre-migration)
@@ -258,11 +258,11 @@ function registerLicense(PDO $pdo, string $licenseKey): array {
$tierDef = LICENSE_TIERS[$tier];
// Deactivate any existing license
- $pdo->exec("UPDATE license_info SET is_active = 0");
+ $pdo->exec("UPDATE `" . t('license_info') . "` SET is_active = 0");
// Insert new license
$stmt = $pdo->prepare("
- INSERT INTO license_info
+ INSERT INTO `" . t('license_info') . "`
(license_key, instance_id, tier, licensed_to_email, licensed_to_name,
max_technicians, max_keys, features,
issued_at, expires_at, last_validated_at, validation_status, is_active)
@@ -307,7 +307,7 @@ function isFeatureAvailable(PDO $pdo, string $feature): bool {
*/
function canAddTechnician(PDO $pdo): array {
$license = getEffectiveLicense($pdo);
- $stmt = $pdo->query("SELECT COUNT(*) FROM technicians WHERE status = 'active'");
+ $stmt = $pdo->query("SELECT COUNT(*) FROM `" . t('technicians') . "` WHERE status = 'active'");
$currentCount = (int)$stmt->fetchColumn();
if ($currentCount >= $license['max_technicians']) {
@@ -328,7 +328,7 @@ function canAddTechnician(PDO $pdo): array {
*/
function canAddKeys(PDO $pdo, int $count = 1): array {
$license = getEffectiveLicense($pdo);
- $stmt = $pdo->query("SELECT COUNT(*) FROM oem_keys");
+ $stmt = $pdo->query("SELECT COUNT(*) FROM `" . t('oem_keys') . "`");
$currentCount = (int)$stmt->fetchColumn();
if (($currentCount + $count) > $license['max_keys']) {
diff --git a/FINAL_PRODUCTION_SYSTEM/functions/network-utils.php b/FINAL_PRODUCTION_SYSTEM/functions/network-utils.php
index ec3e6bf..fe2e8f6 100644
--- a/FINAL_PRODUCTION_SYSTEM/functions/network-utils.php
+++ b/FINAL_PRODUCTION_SYSTEM/functions/network-utils.php
@@ -21,14 +21,14 @@ function checkTrustedNetwork($ip, $checkUSBAuth = false) {
// Check if network allows USB authentication
$stmt = $pdo->prepare("
SELECT id, network_name, ip_range, bypass_2fa, allow_usb_auth
- FROM trusted_networks
+ FROM `" . t('trusted_networks') . "`
WHERE is_active = 1 AND allow_usb_auth = 1
");
} else {
// Check if network allows 2FA bypass
$stmt = $pdo->prepare("
SELECT id, network_name, ip_range, bypass_2fa, allow_usb_auth
- FROM trusted_networks
+ FROM `" . t('trusted_networks') . "`
WHERE is_active = 1 AND bypass_2fa = 1
");
}
@@ -122,7 +122,7 @@ function logNetworkSecurityEvent($event_type, $ip_address, $network_id = null, $
try {
$stmt = $pdo->prepare("
- INSERT INTO admin_activity_log (
+ INSERT INTO `" . t('admin_activity_log') . "` (
admin_id, session_id, action, description,
ip_address, user_agent, trusted_network_id
) VALUES (
diff --git a/FINAL_PRODUCTION_SYSTEM/functions/push-helpers.php b/FINAL_PRODUCTION_SYSTEM/functions/push-helpers.php
index bcf23cd..2f2304f 100644
--- a/FINAL_PRODUCTION_SYSTEM/functions/push-helpers.php
+++ b/FINAL_PRODUCTION_SYSTEM/functions/push-helpers.php
@@ -87,7 +87,7 @@ function getVapidKeys(): ?array {
$keys = VAPID::createVapidKeys();
$stmt = $pdo->prepare("
- INSERT INTO system_config (config_key, config_value, description)
+ INSERT INTO `" . t('system_config') . "` (config_key, config_value, description)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE config_value = VALUES(config_value)
");
@@ -129,11 +129,11 @@ function dispatchNotification(string $action, string $description, int $actorAdm
// Get all admin IDs except the actor, who have this category enabled (or no preference row = default ON)
$stmt = $pdo->prepare("
SELECT DISTINCT au.id, au.preferred_language
- FROM admin_users au
+ FROM `" . t('admin_users') . "` au
WHERE au.id != ?
AND au.is_active = 1
AND NOT EXISTS (
- SELECT 1 FROM push_preferences pp
+ SELECT 1 FROM `" . t('push_preferences') . "` pp
WHERE pp.admin_id = au.id AND pp.category = ? AND pp.enabled = 0
)
");
@@ -148,7 +148,7 @@ function dispatchNotification(string $action, string $description, int $actorAdm
// Insert bell notifications for all recipients
$insertStmt = $pdo->prepare("
- INSERT INTO notifications (admin_id, category, title_key, body, action_url)
+ INSERT INTO `" . t('notifications') . "` (admin_id, category, title_key, body, action_url)
VALUES (?, ?, ?, ?, ?)
");
foreach ($recipientIds as $adminId) {
@@ -212,7 +212,7 @@ function dispatchNotification(string $action, string $description, int $actorAdm
if ($report->isSubscriptionExpired()) {
// Deactivate expired subscriptions
$expiredEndpoint = $report->getRequest()->getUri()->__toString();
- $deactivateStmt = $pdo->prepare("UPDATE push_subscriptions SET is_active = 0 WHERE endpoint = ?");
+ $deactivateStmt = $pdo->prepare("UPDATE `" . t('push_subscriptions') . "` SET is_active = 0 WHERE endpoint = ?");
$deactivateStmt->execute([$expiredEndpoint]);
}
}
diff --git a/FINAL_PRODUCTION_SYSTEM/functions/qc-compliance.php b/FINAL_PRODUCTION_SYSTEM/functions/qc-compliance.php
index 63f2d6a..eb339f5 100644
--- a/FINAL_PRODUCTION_SYSTEM/functions/qc-compliance.php
+++ b/FINAL_PRODUCTION_SYSTEM/functions/qc-compliance.php
@@ -8,7 +8,7 @@
* Check if QC compliance system is enabled
*/
function qcIsEnabled(PDO $pdo): bool {
- $stmt = $pdo->prepare("SELECT setting_value FROM qc_global_settings WHERE setting_key = 'qc_enabled' LIMIT 1");
+ $stmt = $pdo->prepare("SELECT setting_value FROM `" . t('qc_global_settings') . "` WHERE setting_key = 'qc_enabled' LIMIT 1");
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row && $row['setting_value'] === '1';
@@ -18,7 +18,7 @@ function qcIsEnabled(PDO $pdo): bool {
* Return all global QC settings as key-value array
*/
function qcGetGlobalSettings(PDO $pdo): array {
- $stmt = $pdo->query("SELECT setting_key, setting_value FROM qc_global_settings");
+ $stmt = $pdo->query("SELECT setting_key, setting_value FROM `" . t('qc_global_settings') . "`");
$settings = [];
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$settings[$row['setting_key']] = $row['setting_value'];
@@ -36,7 +36,7 @@ function qcAutoRegisterMotherboard(PDO $pdo, ?string $manufacturer, ?string $pro
}
// Check if already exists
- $stmt = $pdo->prepare("SELECT id, known_bios_versions FROM qc_motherboard_registry WHERE manufacturer = ? AND product = ? LIMIT 1");
+ $stmt = $pdo->prepare("SELECT id, known_bios_versions FROM `" . t('qc_motherboard_registry') . "` WHERE manufacturer = ? AND product = ? LIMIT 1");
$stmt->execute([$manufacturer, $product]);
$existing = $stmt->fetch(PDO::FETCH_ASSOC);
@@ -48,7 +48,7 @@ function qcAutoRegisterMotherboard(PDO $pdo, ?string $manufacturer, ?string $pro
}
$stmt = $pdo->prepare("
- UPDATE qc_motherboard_registry
+ UPDATE `" . t('qc_motherboard_registry') . "`
SET times_seen = times_seen + 1,
last_seen_at = NOW(),
known_bios_versions = ?
@@ -61,7 +61,7 @@ function qcAutoRegisterMotherboard(PDO $pdo, ?string $manufacturer, ?string $pro
// Insert new motherboard
$knownVersions = !empty($biosVersion) ? json_encode([$biosVersion]) : '[]';
$stmt = $pdo->prepare("
- INSERT INTO qc_motherboard_registry (manufacturer, product, known_bios_versions, first_seen_at, last_seen_at, times_seen)
+ INSERT INTO `" . t('qc_motherboard_registry') . "` (manufacturer, product, known_bios_versions, first_seen_at, last_seen_at, times_seen)
VALUES (?, ?, ?, NOW(), NOW(), 1)
");
$stmt->execute([$manufacturer, $product, $knownVersions]);
@@ -89,7 +89,7 @@ function qcGetEffectiveRules(PDO $pdo, ?string $manufacturer, ?string $product,
// Overlay product line enforcement (matched by order number pattern)
if (!empty($orderNumber) && mb_strlen($orderNumber) <= 50) {
- $stmt = $pdo->query("SELECT id, name, order_pattern, secure_boot_enforcement, bios_enforcement, hackbgrt_enforcement, partition_enforcement, missing_drivers_enforcement FROM product_lines WHERE is_active = 1 ORDER BY LENGTH(order_pattern) DESC");
+ $stmt = $pdo->query("SELECT id, name, order_pattern, secure_boot_enforcement, bios_enforcement, hackbgrt_enforcement, partition_enforcement, missing_drivers_enforcement FROM `" . t('product_lines') . "` WHERE is_active = 1 ORDER BY LENGTH(order_pattern) DESC");
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $line) {
$pattern = $line['order_pattern'] ?? '';
if (!preg_match('/^[\p{L}\p{N}#*\-\s]+$/u', $pattern) || mb_strlen($pattern) > 50) continue;
@@ -114,7 +114,7 @@ function qcGetEffectiveRules(PDO $pdo, ?string $manufacturer, ?string $product,
// Overlay manufacturer defaults
if (!empty($manufacturer)) {
- $stmt = $pdo->prepare("SELECT * FROM qc_manufacturer_defaults WHERE manufacturer = ? LIMIT 1");
+ $stmt = $pdo->prepare("SELECT * FROM `" . t('qc_manufacturer_defaults') . "` WHERE manufacturer = ? LIMIT 1");
$stmt->execute([$manufacturer]);
$mfr = $stmt->fetch(PDO::FETCH_ASSOC);
if ($mfr) {
@@ -129,7 +129,7 @@ function qcGetEffectiveRules(PDO $pdo, ?string $manufacturer, ?string $product,
// Overlay model-specific overrides (non-NULL only)
if (!empty($manufacturer) && !empty($product)) {
- $stmt = $pdo->prepare("SELECT * FROM qc_motherboard_registry WHERE manufacturer = ? AND product = ? LIMIT 1");
+ $stmt = $pdo->prepare("SELECT * FROM `" . t('qc_motherboard_registry') . "` WHERE manufacturer = ? AND product = ? LIMIT 1");
$stmt->execute([$manufacturer, $product]);
$model = $stmt->fetch(PDO::FETCH_ASSOC);
if ($model) {
@@ -444,7 +444,7 @@ function qcCheckPartitionLayout(PDO $pdo, int $hardwareInfoId, string $orderNumb
if (mb_strlen($orderNumber) > 50) {
return null;
}
- $stmt = $pdo->query("SELECT id, name, order_pattern, enforcement_level FROM product_lines WHERE is_active = 1 ORDER BY LENGTH(order_pattern) DESC");
+ $stmt = $pdo->query("SELECT id, name, order_pattern, enforcement_level FROM `" . t('product_lines') . "` WHERE is_active = 1 ORDER BY LENGTH(order_pattern) DESC");
$matchedLine = null;
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $line) {
$pattern = $line['order_pattern'] ?? '';
@@ -537,7 +537,7 @@ function qcCheckPartitionLayout(PDO $pdo, int $hardwareInfoId, string $orderNumb
}
$stmt = $pdo->prepare("
SELECT id, name, disk_size_min_mb, disk_size_max_mb
- FROM product_variants
+ FROM `" . t('product_variants') . "`
WHERE line_id = ? AND is_active = 1
AND disk_size_min_mb <= ? AND disk_size_max_mb >= ?
ORDER BY disk_size_min_mb DESC
@@ -562,7 +562,7 @@ function qcCheckPartitionLayout(PDO $pdo, int $hardwareInfoId, string $orderNumb
// 6. Store detected variant in hardware_info
$stmt = $pdo->prepare("
- UPDATE hardware_info
+ UPDATE `" . t('hardware_info') . "`
SET detected_variant_id = ?, detected_variant_name = ?, detected_line_name = ?
WHERE id = ?
");
@@ -570,7 +570,7 @@ function qcCheckPartitionLayout(PDO $pdo, int $hardwareInfoId, string $orderNumb
// 7. Load expected partitions
$stmt = $pdo->prepare("
- SELECT * FROM product_variant_partitions
+ SELECT * FROM `" . t('product_variant_partitions') . "`
WHERE variant_id = ?
ORDER BY partition_order
");
@@ -659,7 +659,7 @@ function qcCheckPartitionLayout(PDO $pdo, int $hardwareInfoId, string $orderNumb
} else {
// Check global setting
try {
- $gStmt = $pdo->prepare("SELECT setting_value FROM qc_global_settings WHERE setting_key = 'max_unallocated_mb'");
+ $gStmt = $pdo->prepare("SELECT setting_value FROM `" . t('qc_global_settings') . "` WHERE setting_key = 'max_unallocated_mb'");
$gStmt->execute();
$gVal = $gStmt->fetchColumn();
if ($gVal !== false && $gVal !== null && $gVal !== '') {
@@ -714,7 +714,7 @@ function qcCheckPartitionLayout(PDO $pdo, int $hardwareInfoId, string $orderNumb
*/
function qcInsertResult(PDO $pdo, int $hardwareInfoId, string $orderNumber, array $check, ?int $registryId, bool $retroactive = false): void {
$stmt = $pdo->prepare("
- INSERT INTO qc_compliance_results (
+ INSERT INTO `" . t('qc_compliance_results') . "` (
hardware_info_id, order_number, check_type, check_result,
enforcement_level, expected_value, actual_value, message,
rule_source, motherboard_registry_id, is_retroactive, checked_at
@@ -741,7 +741,7 @@ function qcInsertResult(PDO $pdo, int $hardwareInfoId, string $orderNumber, arra
function qcHasBlockingIssues(PDO $pdo, int $hardwareInfoId): bool {
$stmt = $pdo->prepare("
SELECT COUNT(*) as cnt
- FROM qc_compliance_results
+ FROM `" . t('qc_compliance_results') . "`
WHERE hardware_info_id = ?
AND enforcement_level = 3
AND check_result = 'fail'
@@ -809,7 +809,7 @@ function qcRecheckHistorical(PDO $pdo, ?string $manufacturer = null, ?string $pr
$lastId = $hwId;
// Delete existing compliance results for this record
- $stmt2 = $pdo->prepare("DELETE FROM qc_compliance_results WHERE hardware_info_id = ?");
+ $stmt2 = $pdo->prepare("DELETE FROM `" . t('qc_compliance_results') . "` WHERE hardware_info_id = ?");
$stmt2->execute([$hwId]);
// Re-run checks
@@ -853,7 +853,7 @@ function qcRunChecksRetroactive(PDO $pdo, int $hardwareInfoId, array $hw): array
$orderNumber = $hw['order_number'] ?? '';
// Get registry ID (don't auto-register for retroactive)
- $stmt = $pdo->prepare("SELECT id FROM qc_motherboard_registry WHERE manufacturer = ? AND product = ? LIMIT 1");
+ $stmt = $pdo->prepare("SELECT id FROM `" . t('qc_motherboard_registry') . "` WHERE manufacturer = ? AND product = ? LIMIT 1");
$stmt->execute([$manufacturer, $product]);
$reg = $stmt->fetch(PDO::FETCH_ASSOC);
$registryId = $reg ? (int) $reg['id'] : null;
diff --git a/FINAL_PRODUCTION_SYSTEM/functions/session-helpers.php b/FINAL_PRODUCTION_SYSTEM/functions/session-helpers.php
index 8b382ed..9a1521f 100644
--- a/FINAL_PRODUCTION_SYSTEM/functions/session-helpers.php
+++ b/FINAL_PRODUCTION_SYSTEM/functions/session-helpers.php
@@ -32,9 +32,9 @@ function validateSession($token) {
try {
$stmt = $pdo->prepare("
SELECT s.*, k.product_key, k.key_status, t.is_active as tech_active
- FROM active_sessions s
- LEFT JOIN oem_keys k ON s.key_id = k.id
- LEFT JOIN technicians t ON s.technician_id = t.technician_id
+ FROM `" . t('active_sessions') . "` s
+ LEFT JOIN `" . t('oem_keys') . "` k ON s.key_id = k.id
+ LEFT JOIN `" . t('technicians') . "` t ON s.technician_id = t.technician_id
WHERE s.session_token = ?
AND s.is_active = 1
AND s.expires_at > NOW()
@@ -54,7 +54,7 @@ function validateSession($token) {
function cleanupExpiredSessions($pdo) {
try {
$stmt = $pdo->prepare("
- UPDATE active_sessions
+ UPDATE `" . t('active_sessions') . "`
SET is_active = 0
WHERE expires_at < NOW() AND is_active = 1
LIMIT " . SESSION_CLEANUP_BATCH . "
@@ -82,8 +82,8 @@ function getActiveSession($pdo, $technician_id) {
$stmt = $pdo->prepare("
SELECT s.*, k.product_key, k.oem_identifier, k.key_status, k.fail_counter
- FROM active_sessions s
- LEFT JOIN oem_keys k ON s.key_id = k.id
+ FROM `" . t('active_sessions') . "` s
+ LEFT JOIN `" . t('oem_keys') . "` k ON s.key_id = k.id
WHERE s.technician_id = ?
AND s.is_active = 1
AND s.expires_at > NOW()
diff --git a/FINAL_PRODUCTION_SYSTEM/functions/totp-helpers.php b/FINAL_PRODUCTION_SYSTEM/functions/totp-helpers.php
index 8a6cd21..32bf5b9 100644
--- a/FINAL_PRODUCTION_SYSTEM/functions/totp-helpers.php
+++ b/FINAL_PRODUCTION_SYSTEM/functions/totp-helpers.php
@@ -39,7 +39,7 @@ function validateAdminApiSession(): array {
function fetchTotpData(PDO $pdo, int $adminId): ?array {
$stmt = $pdo->prepare("
SELECT id, totp_secret, totp_enabled, backup_codes
- FROM admin_totp_secrets
+ FROM `" . t('admin_totp_secrets') . "`
WHERE admin_id = ?
");
$stmt->execute([$adminId]);
@@ -78,7 +78,7 @@ function verifyTotpCode(PDO $pdo, int $adminId, string $code, array $totpData, b
$backupCodes = array_values($backupCodes);
$stmt = $pdo->prepare("
- UPDATE admin_totp_secrets
+ UPDATE `" . t('admin_totp_secrets') . "`
SET backup_codes = ?, last_used_at = NOW()
WHERE admin_id = ?
");
@@ -92,7 +92,7 @@ function verifyTotpCode(PDO $pdo, int $adminId, string $code, array $totpData, b
} else {
$totp = TOTP::createFromSecret($totpData['totp_secret']);
- $stmt = $pdo->query("SELECT config_value FROM system_config WHERE config_key = 'totp_window'");
+ $stmt = $pdo->query("SELECT config_value FROM `" . t('system_config') . "` WHERE config_key = 'totp_window'");
$windowResult = $stmt->fetch(PDO::FETCH_ASSOC);
$window = $windowResult ? (int)$windowResult['config_value'] : 1;
@@ -100,7 +100,7 @@ function verifyTotpCode(PDO $pdo, int $adminId, string $code, array $totpData, b
if ($result['verified']) {
$stmt = $pdo->prepare("
- UPDATE admin_totp_secrets
+ UPDATE `" . t('admin_totp_secrets') . "`
SET last_used_at = NOW()
WHERE admin_id = ?
");
@@ -117,7 +117,7 @@ function verifyTotpCode(PDO $pdo, int $adminId, string $code, array $totpData, b
* @return array ['plain' => string[], 'hashed' => string[]]
*/
function generateBackupCodes(PDO $pdo): array {
- $stmt = $pdo->query("SELECT config_value FROM system_config WHERE config_key = 'totp_backup_codes_count'");
+ $stmt = $pdo->query("SELECT config_value FROM `" . t('system_config') . "` WHERE config_key = 'totp_backup_codes_count'");
$backupCountResult = $stmt->fetch(PDO::FETCH_ASSOC);
$backupCodeCount = $backupCountResult ? (int)$backupCountResult['config_value'] : 10;
diff --git a/FINAL_PRODUCTION_SYSTEM/install/ajax.php b/FINAL_PRODUCTION_SYSTEM/install/ajax.php
index fedc2a5..a9cbf4c 100644
--- a/FINAL_PRODUCTION_SYSTEM/install/ajax.php
+++ b/FINAL_PRODUCTION_SYSTEM/install/ajax.php
@@ -469,13 +469,13 @@ function handleInstallDbInit() {
}
// Bootstrap schema_versions first (the tracking table for all later migrations).
+ // Run through installerRunSqlFile so the `#__` prefix substitution kicks in.
$schemaFile = $sqlDir . '/schema_versions_migration.sql';
if (file_exists($schemaFile)) {
- try {
- $pdo->exec(file_get_contents($schemaFile));
- } catch (PDOException $e) { /* probably already exists */ }
+ installerRunSqlFile($pdo, file_get_contents($schemaFile)); // Best-effort; ignore errors.
}
+ $svTable = '`' . installerT('schema_versions') . '`';
$list = [];
foreach (installerMigrationList() as [$file, $version]) {
$filePath = $sqlDir . '/' . $file;
@@ -484,7 +484,7 @@ function handleInstallDbInit() {
if ($exists) {
try {
- $stmt = $pdo->prepare("SELECT COUNT(*) AS c FROM schema_versions WHERE filename = ?");
+ $stmt = $pdo->prepare("SELECT COUNT(*) AS c FROM {$svTable} WHERE filename = ?");
$stmt->execute([$file]);
$applied = ((int)$stmt->fetchColumn()) > 0;
} catch (PDOException $e) { /* table missing → not applied */ }
@@ -532,9 +532,11 @@ function handleInstallDbStep() {
return;
}
+ $svTable = '`' . installerT('schema_versions') . '`';
+
// Skip if already applied
try {
- $stmt = $pdo->prepare("SELECT COUNT(*) AS c FROM schema_versions WHERE filename = ?");
+ $stmt = $pdo->prepare("SELECT COUNT(*) AS c FROM {$svTable} WHERE filename = ?");
$stmt->execute([$file]);
if ((int)$stmt->fetchColumn() > 0) {
echo json_encode(['file' => $file, 'success' => true, 'status' => 'skipped', 'message' => 'Already applied']);
@@ -548,7 +550,7 @@ function handleInstallDbStep() {
if ($result['ok']) {
try {
$checksum = hash('sha256', $sql);
- $stmt = $pdo->prepare("INSERT IGNORE INTO schema_versions (version, filename, checksum) VALUES (?, ?, ?)");
+ $stmt = $pdo->prepare("INSERT IGNORE INTO {$svTable} (version, filename, checksum) VALUES (?, ?, ?)");
$stmt->execute([$version, $file, $checksum]);
} catch (PDOException $e) { /* ignore */ }
@@ -566,7 +568,7 @@ function handleInstallDbStep() {
if (preg_match('/Duplicate|already exists|1060|1061|1050|1062/i', $result['error'])) {
try {
$checksum = hash('sha256', $sql);
- $stmt = $pdo->prepare("INSERT IGNORE INTO schema_versions (version, filename, checksum) VALUES (?, ?, ?)");
+ $stmt = $pdo->prepare("INSERT IGNORE INTO {$svTable} (version, filename, checksum) VALUES (?, ?, ?)");
$stmt->execute([$version, $file, $checksum]);
} catch (PDOException $e2) { /* ignore */ }
@@ -602,12 +604,13 @@ function handleInstallDbAll() {
return;
}
- // Bootstrap schema_versions
+ // Bootstrap schema_versions (run through installerRunSqlFile so prefix substitution applies)
$schemaFile = $sqlDir . '/schema_versions_migration.sql';
if (file_exists($schemaFile)) {
- try { $pdo->exec(file_get_contents($schemaFile)); } catch (PDOException $e) { /* ok */ }
+ installerRunSqlFile($pdo, file_get_contents($schemaFile));
}
+ $svTable = '`' . installerT('schema_versions') . '`';
$results = [];
foreach (installerMigrationList() as $i => [$file, $version]) {
if ($i === 0) {
@@ -622,7 +625,7 @@ function handleInstallDbAll() {
}
try {
- $stmt = $pdo->prepare("SELECT COUNT(*) AS c FROM schema_versions WHERE filename = ?");
+ $stmt = $pdo->prepare("SELECT COUNT(*) AS c FROM {$svTable} WHERE filename = ?");
$stmt->execute([$file]);
if ((int)$stmt->fetchColumn() > 0) {
$results[] = ['file' => $file, 'status' => 'skipped', 'message' => 'Already applied'];
@@ -636,14 +639,14 @@ function handleInstallDbAll() {
if ($r['ok']) {
try {
$checksum = hash('sha256', $sql);
- $stmt = $pdo->prepare("INSERT IGNORE INTO schema_versions (version, filename, checksum) VALUES (?, ?, ?)");
+ $stmt = $pdo->prepare("INSERT IGNORE INTO {$svTable} (version, filename, checksum) VALUES (?, ?, ?)");
$stmt->execute([$version, $file, $checksum]);
} catch (PDOException $e) { /* ignore */ }
$results[] = ['file' => $file, 'status' => 'ok', 'message' => 'Applied (' . $r['stmts_run'] . ' statements)'];
} elseif (preg_match('/Duplicate|already exists|1060|1061|1050|1062/i', $r['error'])) {
try {
$checksum = hash('sha256', $sql);
- $stmt = $pdo->prepare("INSERT IGNORE INTO schema_versions (version, filename, checksum) VALUES (?, ?, ?)");
+ $stmt = $pdo->prepare("INSERT IGNORE INTO {$svTable} (version, filename, checksum) VALUES (?, ?, ?)");
$stmt->execute([$version, $file, $checksum]);
} catch (PDOException $e) { /* ignore */ }
$results[] = ['file' => $file, 'status' => 'ok', 'message' => 'Applied (some objects already existed)'];
@@ -675,6 +678,17 @@ function installerRunSqlFile(PDO $pdo, string $sql): array {
$sql = preg_replace('/^\s*(START\s+TRANSACTION|BEGIN)\s*;\s*$/im', '', $sql);
$sql = preg_replace('/^\s*COMMIT\s*;\s*$/im', '', $sql);
+ // Substitute table prefix sentinel `#__` with the configured prefix.
+ // Empty prefix → identical to pre-prefix schema.
+ $prefix = (string)($_SESSION['install_db']['prefix'] ?? '');
+ $sql = str_replace('#__', $prefix, $sql);
+
+ // Defense-in-depth: if substitution somehow missed and `#__` remains,
+ // abort loudly rather than send invalid SQL.
+ if (strpos($sql, '#__') !== false) {
+ return ['ok' => false, 'stmts_run' => 0, 'error' => 'Internal error: unsubstituted `#__` sentinel still present after prefix replacement.'];
+ }
+
$stmts = installerSplitSql($sql);
$count = 0;
foreach ($stmts as $stmt) {
@@ -690,6 +704,16 @@ function installerRunSqlFile(PDO $pdo, string $sql): array {
return ['ok' => true, 'stmts_run' => $count, 'error' => ''];
}
+/**
+ * Resolve the prefixed name for a logical table during installation.
+ * Mirrors functions/db-helpers.php t() but reads from $_SESSION since
+ * the installer runs before config.php exists.
+ */
+function installerT(string $name): string {
+ $prefix = (string)($_SESSION['install_db']['prefix'] ?? '');
+ return $prefix . $name;
+}
+
/**
* Split a multi-statement SQL string into individual statements.
* Respects backticks, single/double-quoted strings, line comments (-- ...),
@@ -815,30 +839,33 @@ function handleCreateAdmin() {
return;
}
+ $adminTable = '`' . installerT('admin_users') . '`';
+ $aclTable = '`' . installerT('acl_roles') . '`';
+
try {
// Check if admin already exists
- $stmt = $pdo->prepare("SELECT id FROM admin_users WHERE username = ?");
+ $stmt = $pdo->prepare("SELECT id FROM {$adminTable} WHERE username = ?");
$stmt->execute([$username]);
if ($stmt->fetch()) {
// Update existing
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
- $stmt = $pdo->prepare("UPDATE admin_users SET password_hash = ?, full_name = ?, email = ?, role = 'super_admin', is_active = 1, must_change_password = 0, failed_login_attempts = 0, locked_until = NULL WHERE username = ?");
+ $stmt = $pdo->prepare("UPDATE {$adminTable} SET password_hash = ?, full_name = ?, email = ?, role = 'super_admin', is_active = 1, must_change_password = 0, failed_login_attempts = 0, locked_until = NULL WHERE username = ?");
$stmt->execute([$hash, $fullName, $email, $username]);
echo json_encode(['success' => true, 'message' => "Admin account '{$username}' updated."]);
} else {
// Create new
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
- $stmt = $pdo->prepare("INSERT INTO admin_users (username, full_name, email, password_hash, role, is_active, must_change_password) VALUES (?, ?, ?, ?, 'super_admin', 1, 0)");
+ $stmt = $pdo->prepare("INSERT INTO {$adminTable} (username, full_name, email, password_hash, role, is_active, must_change_password) VALUES (?, ?, ?, ?, 'super_admin', 1, 0)");
$stmt->execute([$username, $fullName, $email, $hash]);
// Try to assign ACL role if table exists
try {
$adminId = $pdo->lastInsertId();
- $roleStmt = $pdo->prepare("SELECT id FROM acl_roles WHERE name = 'super_admin' LIMIT 1");
+ $roleStmt = $pdo->prepare("SELECT id FROM {$aclTable} WHERE name = 'super_admin' LIMIT 1");
$roleStmt->execute();
$role = $roleStmt->fetch(PDO::FETCH_ASSOC);
if ($role) {
- $pdo->prepare("UPDATE admin_users SET custom_role_id = ? WHERE id = ?")->execute([$role['id'], $adminId]);
+ $pdo->prepare("UPDATE {$adminTable} SET custom_role_id = ? WHERE id = ?")->execute([$role['id'], $adminId]);
}
} catch (PDOException $e) {
// ACL tables might not exist — that's fine
@@ -873,7 +900,9 @@ function handleFinalize() {
}
// ── Write config.php ──
- $configContent = generateConfig($host, $port, $user, $pass, $name, $timezone);
+ $prefix = (string)($_SESSION['install_db']['prefix'] ?? '');
+ $charset = (string)($_SESSION['install_db']['charset'] ?? 'utf8mb4');
+ $configContent = generateConfig($host, $port, $user, $pass, $name, $timezone, $prefix, $charset);
$configPath = realpath(__DIR__ . '/..') . '/config.php';
if (file_put_contents($configPath, $configContent) === false) {
@@ -888,6 +917,12 @@ function handleFinalize() {
try {
$pdo = getInstallerPdo();
if ($pdo) {
+ $tConfig = '`' . installerT('system_config') . '`';
+ $tTech = '`' . installerT('technicians') . '`';
+ $tAdmin = '`' . installerT('admin_users') . '`';
+ $tTrustedN = '`' . installerT('trusted_networks') . '`';
+ $tAdminIp = '`' . installerT('admin_ip_whitelist') . '`';
+
$settings = [
'system_name' => $systemName,
'server_url' => $serverUrl,
@@ -895,13 +930,13 @@ function handleFinalize() {
'timezone' => $timezone,
];
foreach ($settings as $key => $value) {
- $stmt = $pdo->prepare("INSERT INTO system_config (config_key, config_value, description) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE config_value = VALUES(config_value)");
+ $stmt = $pdo->prepare("INSERT INTO {$tConfig} (config_key, config_value, description) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE config_value = VALUES(config_value)");
$stmt->execute([$key, $value, "Set by installer"]);
}
// Delete demo technician if it exists
try {
- $pdo->exec("DELETE FROM technicians WHERE technician_id = 'demo' AND notes LIKE '%Demo account%'");
+ $pdo->exec("DELETE FROM {$tTech} WHERE technician_id = 'demo' AND notes LIKE '%Demo account%'");
} catch (PDOException $e) { /* ignore */ }
// ── Auto-detect installer's network and add as trusted ──
@@ -909,7 +944,7 @@ function handleFinalize() {
$clientIp = getClientIp();
if ($clientIp && $clientIp !== 'unknown') {
// Get admin ID for foreign key
- $adminStmt = $pdo->prepare("SELECT id FROM admin_users WHERE username = ? LIMIT 1");
+ $adminStmt = $pdo->prepare("SELECT id FROM {$tAdmin} WHERE username = ? LIMIT 1");
$adminStmt->execute([$adminUser]);
$adminRow = $adminStmt->fetch(PDO::FETCH_ASSOC);
$adminId = $adminRow ? $adminRow['id'] : null;
@@ -920,7 +955,7 @@ function handleFinalize() {
// Add to trusted_networks (for 2FA bypass + USB auth)
try {
$stmt = $pdo->prepare("
- INSERT INTO trusted_networks (network_name, ip_range, bypass_2fa, allow_usb_auth, description, created_by_admin_id)
+ INSERT INTO {$tTrustedN} (network_name, ip_range, bypass_2fa, allow_usb_auth, description, created_by_admin_id)
VALUES (?, ?, 1, 1, ?, ?)
");
$stmt->execute([
@@ -934,7 +969,7 @@ function handleFinalize() {
// Add to admin_ip_whitelist (for admin panel access)
try {
$stmt = $pdo->prepare("
- INSERT INTO admin_ip_whitelist (ip_address, ip_range, description, created_by)
+ INSERT INTO {$tAdminIp} (ip_address, ip_range, description, created_by)
VALUES (?, ?, ?, ?)
");
$stmt->execute([
@@ -1144,9 +1179,20 @@ function calculateSubnet(string $ip, int $prefix = 24): string {
/**
* Generate the production config.php content
*/
-function generateConfig(string $host, string $port, string $user, string $pass, string $name, string $timezone): string {
+function generateConfig(
+ string $host,
+ string $port,
+ string $user,
+ string $pass,
+ string $name,
+ string $timezone,
+ string $prefix = '',
+ string $charset = 'utf8mb4'
+): string {
// Escape single quotes in password for PHP string
- $passEscaped = addcslashes($pass, "'\\");
+ $passEscaped = addcslashes($pass, "'\\");
+ $prefixEscaped = addcslashes($prefix, "'\\");
+ $charsetEsc = preg_replace('/[^a-z0-9_]/', '', $charset) ?: 'utf8mb4';
return <<<'PHP_HEADER'
'{$host}',\n"
. " 'dbname' => '{$name}',\n"
. " 'username' => '{$user}',\n"
. " 'password' => '{$passEscaped}',\n"
- . " 'charset' => 'utf8mb4',\n"
+ . " 'charset' => '{$charsetEsc}',\n"
. " 'port' => {$port},\n"
. "];\n\n"
. <<<'PHP_BODY'
@@ -1355,6 +1402,13 @@ function installerCheckIncompleteState(): void {
$host = $hM[1]; $name = $nM[1]; $user = $uM[1]; $pass = $pM[1]; $port = (int)$portM[1];
if (strtolower($host) === 'localhost') $host = '127.0.0.1';
+ // Read DB_PREFIX from config (empty for legacy installs).
+ $prefix = '';
+ if (preg_match("/define\(\s*'DB_PREFIX'\s*,\s*'([^']*)'\s*\)/", $configSrc, $pxM)) {
+ $prefix = $pxM[1];
+ }
+ $adminTable = $prefix . 'admin_users';
+
try {
$dsn = "mysql:host={$host};port={$port};dbname={$name};charset=utf8mb4";
$pdo = new PDO($dsn, $user, $pass, [
@@ -1363,15 +1417,15 @@ function installerCheckIncompleteState(): void {
]);
// admin_users table missing → install was never completed.
- $stmt = $pdo->query("SHOW TABLES LIKE 'admin_users'");
+ $stmt = $pdo->query("SHOW TABLES LIKE " . $pdo->quote($adminTable));
if (!$stmt->fetch()) {
- installerLog("auto-unlock: admin_users table missing — clearing install.lock");
+ installerLog("auto-unlock: {$adminTable} missing — clearing install.lock");
@unlink($lockPath);
return;
}
// admin_users empty → install was never completed.
- $cnt = (int)$pdo->query("SELECT COUNT(*) FROM admin_users")->fetchColumn();
+ $cnt = (int)$pdo->query("SELECT COUNT(*) FROM `{$adminTable}`")->fetchColumn();
if ($cnt === 0) {
installerLog("auto-unlock: admin_users empty — clearing install.lock");
@unlink($lockPath);
@@ -1447,17 +1501,19 @@ function handleHealth(): void {
$expectTables = ['admin_users', 'oem_keys', 'technicians', 'system_config', 'schema_versions'];
foreach ($expectTables as $t) {
+ $physical = installerT($t);
try {
- $stmt = $pdo->query("SHOW TABLES LIKE '" . str_replace("'", '', $t) . "'");
+ $stmt = $pdo->query("SHOW TABLES LIKE " . $pdo->quote($physical));
$found = (bool)$stmt->fetch();
- $checks[] = ['label' => "Table {$t}", 'status' => $found ? 'pass' : 'fail'];
+ $checks[] = ['label' => "Table {$physical}", 'status' => $found ? 'pass' : 'fail'];
} catch (PDOException $e) {
- $checks[] = ['label' => "Table {$t}", 'status' => 'fail'];
+ $checks[] = ['label' => "Table {$physical}", 'status' => 'fail'];
}
}
try {
- $admins = (int)$pdo->query("SELECT COUNT(*) FROM admin_users")->fetchColumn();
+ $adminTable = '`' . installerT('admin_users') . '`';
+ $admins = (int)$pdo->query("SELECT COUNT(*) FROM {$adminTable}")->fetchColumn();
$checks[] = ['label' => 'Admin accounts', 'status' => $admins > 0 ? 'pass' : 'fail', 'value' => $admins];
} catch (PDOException $e) {
$checks[] = ['label' => 'Admin accounts', 'status' => 'fail'];
diff --git a/FINAL_PRODUCTION_SYSTEM/install/index.php b/FINAL_PRODUCTION_SYSTEM/install/index.php
index f55d2e1..d7b7169 100644
--- a/FINAL_PRODUCTION_SYSTEM/install/index.php
+++ b/FINAL_PRODUCTION_SYSTEM/install/index.php
@@ -26,14 +26,20 @@
&& preg_match("/'password'\s*=>\s*'([^']*)'/", $configSrc, $pM)
&& preg_match("/'port'\s*=>\s*(\d+)/", $configSrc, $portM)) {
$autoHost = strtolower($hM[1]) === 'localhost' ? '127.0.0.1' : $hM[1];
+ // Read DB_PREFIX from config (empty for legacy installs).
+ $autoPrefix = '';
+ if (preg_match("/define\(\s*'DB_PREFIX'\s*,\s*'([^']*)'\s*\)/", $configSrc, $pxM)) {
+ $autoPrefix = $pxM[1];
+ }
+ $autoAdminTable = $autoPrefix . 'admin_users';
try {
$autoPdo = new PDO(
"mysql:host={$autoHost};port={$portM[1]};dbname={$nM[1]};charset=utf8mb4",
$uM[1], $pM[1],
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_TIMEOUT => 5]
);
- $hasAdminTable = (bool) $autoPdo->query("SHOW TABLES LIKE 'admin_users'")->fetch();
- $adminCount = $hasAdminTable ? (int) $autoPdo->query("SELECT COUNT(*) FROM admin_users")->fetchColumn() : 0;
+ $hasAdminTable = (bool) $autoPdo->query("SHOW TABLES LIKE " . $autoPdo->quote($autoAdminTable))->fetch();
+ $adminCount = $hasAdminTable ? (int) $autoPdo->query("SELECT COUNT(*) FROM `{$autoAdminTable}`")->fetchColumn() : 0;
if (!$hasAdminTable || $adminCount === 0) {
@unlink($lockFile);
@file_put_contents(
diff --git a/FINAL_PRODUCTION_SYSTEM/secure-admin.php b/FINAL_PRODUCTION_SYSTEM/secure-admin.php
index f043af9..a91e547 100644
--- a/FINAL_PRODUCTION_SYSTEM/secure-admin.php
+++ b/FINAL_PRODUCTION_SYSTEM/secure-admin.php
@@ -39,7 +39,7 @@ function checkIPWhitelist() {
// Get all active whitelist entries
$stmt = $pdo->prepare("
- SELECT ip_address, ip_range FROM admin_ip_whitelist
+ SELECT ip_address, ip_range FROM `" . t('admin_ip_whitelist') . "`
WHERE is_active = 1
");
$stmt->execute();
@@ -77,7 +77,7 @@ function checkIPWhitelist() {
// Handle logout
if (isset($_GET['logout'])) {
if (isset($_SESSION['admin_token'])) {
- $stmt = $pdo->prepare("UPDATE admin_sessions SET is_active = 0 WHERE session_token = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('admin_sessions') . "` SET is_active = 0 WHERE session_token = ?");
$stmt->execute([$_SESSION['admin_token']]);
logAdminActivity($_SESSION['admin_id'], $_SESSION['session_id'], 'LOGOUT', 'User logout');
}
diff --git a/FINAL_PRODUCTION_SYSTEM/setup/index.php b/FINAL_PRODUCTION_SYSTEM/setup/index.php
index 6dcc95e..9d0e839 100644
--- a/FINAL_PRODUCTION_SYSTEM/setup/index.php
+++ b/FINAL_PRODUCTION_SYSTEM/setup/index.php
@@ -500,7 +500,7 @@ function setupAdminAccount() {
$db['username'], $db['password']);
$stmt = $pdo->prepare("
- INSERT INTO admin_users (username, full_name, email, password_hash, role, must_change_password)
+ INSERT INTO `" . t('admin_users') . "` (username, full_name, email, password_hash, role, must_change_password)
VALUES (?, ?, ?, ?, 'super_admin', 0)
");
$stmt->execute([$admin['username'], $admin['full_name'], $admin['email'], $admin['password']]);
@@ -524,7 +524,7 @@ function applySystemConfiguration() {
['admin_ip_whitelist_enabled', $config['enable_ip_whitelist'] ? '1' : '0'],
];
- $stmt = $pdo->prepare("UPDATE system_config SET config_value = ? WHERE config_key = ?");
+ $stmt = $pdo->prepare("UPDATE `" . t('system_config') . "` SET config_value = ? WHERE config_key = ?");
foreach ($configs as $cfg) {
$stmt->execute([$cfg[1], $cfg[0]]);
}
diff --git a/tools/prefix-codemod.php b/tools/prefix-codemod.php
new file mode 100644
index 0000000..06a932d
--- /dev/null
+++ b/tools/prefix-codemod.php
@@ -0,0 +1,398 @@
+ overrides default detection. Useful inside Docker where
+// FINAL_PRODUCTION_SYSTEM lives at /var/www/html/activate, not next to tools/.
+$customRoot = '';
+foreach ($argv as $i => $arg) {
+ if ($arg === '--root' && isset($argv[$i + 1])) {
+ $customRoot = $argv[$i + 1];
+ break;
+ }
+ if (str_starts_with($arg, '--root=')) {
+ $customRoot = substr($arg, 7);
+ break;
+ }
+}
+
+if ($customRoot !== '') {
+ $appRoot = realpath($customRoot);
+} else {
+ $root = realpath(__DIR__ . '/..');
+ $appRoot = $root . DIRECTORY_SEPARATOR . 'FINAL_PRODUCTION_SYSTEM';
+}
+if (!is_dir($appRoot)) {
+ fwrite(STDERR, "FINAL_PRODUCTION_SYSTEM not found at {$appRoot}\n");
+ exit(1);
+}
+
+$dbDir = $appRoot . DIRECTORY_SEPARATOR . 'database';
+
+// ── 1. Discover canonical table list ──────────────────────────────
+$tables = discoverTables($dbDir);
+sort($tables);
+if (!$quiet) {
+ fwrite(STDOUT, "Discovered " . count($tables) . " tables.\n");
+}
+
+// Sort by length descending so longer names match before substring siblings
+// (e.g. `admin_users` before `admin`).
+usort($tables, fn($a, $b) => strlen($b) - strlen($a));
+
+// Build deny list — never rewrite these even if they appear in SQL contexts.
+// They are SQL keywords / column names that happen to look like table names.
+$denyAlias = [
+ 'config_value', // column in system_config
+ 'value', // generic column name
+ 'key', // SQL keyword + common column
+ 'name', // common column
+ 'role', // common column
+];
+$tables = array_values(array_diff($tables, $denyAlias));
+
+if (count($tables) < 5) {
+ fwrite(STDERR, "Refusing to run: only " . count($tables) . " tables discovered. Something is wrong.\n");
+ exit(2);
+}
+
+// ── 2. SQL pass ───────────────────────────────────────────────────
+$sqlFiles = glob($dbDir . '/*.sql');
+$sqlChanges = 0;
+$sqlFilesChanged = [];
+
+foreach ($sqlFiles as $f) {
+ $orig = file_get_contents($f);
+ $new = rewriteSqlBody($orig, $tables);
+ if ($new !== $orig) {
+ $sqlChanges += substr_count($new, '#__') - substr_count($orig, '#__');
+ $sqlFilesChanged[] = basename($f);
+ if ($apply) {
+ file_put_contents($f, $new);
+ }
+ }
+}
+
+if (!$quiet) {
+ fwrite(STDOUT, "SQL: " . count($sqlFilesChanged) . " files changed, +{$sqlChanges} `#__` markers.\n");
+}
+
+// ── 3. PHP pass ───────────────────────────────────────────────────
+$phpFiles = collectPhpFiles($appRoot);
+$phpChanges = 0;
+$phpFilesChanged = [];
+
+foreach ($phpFiles as $f) {
+ $orig = file_get_contents($f);
+ $new = rewritePhpBody($orig, $tables);
+ if ($new !== $orig) {
+ $phpFilesChanged[] = str_replace($appRoot . DIRECTORY_SEPARATOR, '', $f);
+ $diffLines = countDiff($orig, $new);
+ $phpChanges += $diffLines;
+ if ($apply) {
+ file_put_contents($f, $new);
+ }
+ }
+}
+
+if (!$quiet) {
+ fwrite(STDOUT, "PHP: " . count($phpFilesChanged) . " files changed, ~{$phpChanges} site rewrites.\n");
+}
+
+// ── 4. Verify pass (when --verify) ────────────────────────────────
+if ($verify) {
+ $stillBad = [];
+ foreach ($sqlFiles as $f) {
+ $body = file_get_contents($f);
+ foreach ($tables as $t) {
+ // Look for un-prefixed backticked refs.
+ // Pattern: `tablename` not preceded by `#__
+ if (preg_match('/(?isFile()) continue;
+ if (substr($f->getFilename(), -4) !== '.php') continue;
+ $path = str_replace('\\', '/', $f->getPathname());
+ foreach ($skip as $s) {
+ if (strpos($path, $s) !== false) continue 2;
+ }
+ $files[] = $f->getPathname();
+ }
+ return $files;
+}
+
+/**
+ * Rewrite SQL body: `tablename` → `#__tablename` for every canonical table.
+ * Idempotent: skips already-prefixed.
+ */
+function rewriteSqlBody(string $body, array $tables): string {
+ $kw = '(?:CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?|DROP\s+TABLE(?:\s+IF\s+EXISTS)?|ALTER\s+TABLE|INSERT\s+INTO|REPLACE\s+INTO|SELECT\s+(?:[^;]*?)FROM|UPDATE|DELETE\s+FROM|FROM|JOIN|REFERENCES|TRUNCATE(?:\s+TABLE)?|RENAME\s+TABLE|LOCK\s+TABLES|DESCRIBE|EXPLAIN)';
+
+ foreach ($tables as $t) {
+ // ── Pattern A: backticked refs ────────────────────────────
+ // (?
Date: Thu, 7 May 2026 07:29:52 +0300
Subject: [PATCH 09/15] =?UTF-8?q?P2:=20Installer=20resilience=20=E2=80=94?=
=?UTF-8?q?=20resume,=20retry/skip,=20structured=20log,=20health=20probe?=
=?UTF-8?q?=20(#21)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Phase 2 of the multi-panel installer plan. Adds operational robustness
on top of P0 hardening + P1 prefix support.
Changes:
1. Resumable installer
- install/.progress.json breadcrumb file. Each step writes its number
and timestamp on completion via the new progress_set action.
- On page boot, JS calls progress_get; if last_step >= 1, prompts
user "Resume from step N+1?" with Cancel = start over (which clears
the file). Bypassed for fresh installs.
- handleFinalize unlinks the breadcrumb on success (install complete).
2. Per-migration retry / skip
- Step 3 UI: when a migration errors, inline Retry + Skip buttons
appear next to the failed row.
- Retry: re-runs install_db_step for that file. On success, the
migration loop resumes from the next file.
- Skip: prompts a hard-yes confirmation, then calls the new
migration_skip action which inserts a row into schema_versions
with checksum prefix `SKIPPED:` (so future audits can tell apart
successful applies from forced skips). Loop resumes.
- Both paths respect the canonical migration whitelist.
3. Structured install.log
- installerLog() is now called from auto-unlock recovery, finalize
completion, progress_set, migration_skip, and progress_clear.
- Audit format: `[YYYY-MM-DD HH:MM:SS] event_name: details`.
4. Health-probe button on step 6
- "Run health check" button next to "Open Admin Panel".
- Calls existing handleHealth action; renders pass/fail per check
(DB connect, presence of {prefix}admin_users, oem_keys, technicians,
system_config, schema_versions, plus admin account count).
5. install.lock content extended
- Now persists db_prefix and db_charset alongside installer_ver,
admin_username, php_version, server_software. Makes post-install
forensics easier.
6. .gitignore
- install/install.log and install/.progress.json — runtime per-host
artifacts, never to be committed.
Verified live:
- POST progress_set/progress_get round-trip works
- Lint clean on ajax.php + index.php
- 14/14 frontend tests pass
Co-authored-by: ChesnoTech <263363000+ChesnoTech@users.noreply.github.com>
---
.gitignore | 4 +
FINAL_PRODUCTION_SYSTEM/install/ajax.php | 115 ++++++++++++++++++++++
FINAL_PRODUCTION_SYSTEM/install/index.php | 108 +++++++++++++++++++-
3 files changed, 222 insertions(+), 5 deletions(-)
diff --git a/.gitignore b/.gitignore
index 9aa8908..c0bd259 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,10 @@ logs/
# ── Uploaded client artifacts (per-instance, regenerated at runtime) ──
FINAL_PRODUCTION_SYSTEM/uploads/client-resources/
+# ── Installer runtime artifacts (per-host, never committed) ────────
+FINAL_PRODUCTION_SYSTEM/install/install.log
+FINAL_PRODUCTION_SYSTEM/install/.progress.json
+
# ── PHP Dependencies (managed by Composer) ────────────────
FINAL_PRODUCTION_SYSTEM/vendor/
diff --git a/FINAL_PRODUCTION_SYSTEM/install/ajax.php b/FINAL_PRODUCTION_SYSTEM/install/ajax.php
index a9cbf4c..d361a41 100644
--- a/FINAL_PRODUCTION_SYSTEM/install/ajax.php
+++ b/FINAL_PRODUCTION_SYSTEM/install/ajax.php
@@ -55,6 +55,18 @@
case 'health':
handleHealth();
break;
+ case 'progress_get':
+ handleProgressGet();
+ break;
+ case 'progress_set':
+ handleProgressSet();
+ break;
+ case 'progress_clear':
+ handleProgressClear();
+ break;
+ case 'migration_skip':
+ handleMigrationSkip();
+ break;
default:
echo json_encode(['success' => false, 'message' => 'Unknown action']);
}
@@ -996,10 +1008,16 @@ function handleFinalize() {
'admin_username' => $adminUser,
'php_version' => PHP_VERSION,
'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? 'unknown',
+ 'db_prefix' => $prefix,
+ 'db_charset' => $charset,
];
$lockPath = realpath(__DIR__ . '/..') . '/install.lock';
file_put_contents($lockPath, json_encode($lockData, JSON_PRETTY_PRINT));
+ // ── Clear resume breadcrumb (install is complete) ──
+ @unlink(installerProgressPath());
+ installerLog("finalize: install complete; admin={$adminUser}, prefix='" . $prefix . "', charset={$charset}");
+
// ── Response ──
echo json_encode([
'success' => true,
@@ -1535,3 +1553,100 @@ function installerLog(string $line): void {
FILE_APPEND
);
}
+
+// ═══════════════════════════════════════════════════════════════
+// P2: Resumable installer (progress breadcrumb file)
+// ═══════════════════════════════════════════════════════════════
+
+function installerProgressPath(): string {
+ return __DIR__ . '/.progress.json';
+}
+
+/**
+ * Read the progress breadcrumb. Returns the highest step the user has
+ * completed plus a per-step timestamp map.
+ */
+function handleProgressGet(): void {
+ $path = installerProgressPath();
+ if (!file_exists($path)) {
+ echo json_encode(['success' => true, 'progress' => null]);
+ return;
+ }
+ $data = json_decode(@file_get_contents($path), true);
+ if (!is_array($data)) {
+ echo json_encode(['success' => true, 'progress' => null]);
+ return;
+ }
+ echo json_encode(['success' => true, 'progress' => $data]);
+}
+
+/**
+ * Persist a step completion breadcrumb.
+ * Body: { step: 1..6 }
+ */
+function handleProgressSet(): void {
+ $step = (int)($_POST['step'] ?? 0);
+ if ($step < 1 || $step > 6) {
+ echo json_encode(['success' => false, 'message' => 'Invalid step']);
+ return;
+ }
+
+ $path = installerProgressPath();
+ $current = ['steps' => [], 'last_step' => 0, 'updated_at' => date('Y-m-d H:i:s')];
+ if (file_exists($path)) {
+ $loaded = json_decode(@file_get_contents($path), true);
+ if (is_array($loaded)) $current = array_merge($current, $loaded);
+ }
+
+ $current['steps'][(string)$step] = date('Y-m-d H:i:s');
+ if ($step > (int)($current['last_step'] ?? 0)) {
+ $current['last_step'] = $step;
+ }
+ $current['updated_at'] = date('Y-m-d H:i:s');
+
+ @file_put_contents($path, json_encode($current, JSON_PRETTY_PRINT));
+ installerLog("step_done: {$step}");
+ echo json_encode(['success' => true, 'progress' => $current]);
+}
+
+/**
+ * Wipe the progress file. Used on user-initiated "Start Over".
+ */
+function handleProgressClear(): void {
+ @unlink(installerProgressPath());
+ installerLog("progress_cleared by user");
+ echo json_encode(['success' => true]);
+}
+
+/**
+ * Mark a migration as forcibly skipped after an error (user clicked Skip
+ * on the per-migration retry UI). Records in schema_versions with the
+ * `checksum` column suffixed `:SKIPPED` so we can tell apart from
+ * successful applies.
+ *
+ * Body: { file: 'install.sql', version: 1, error: 'optional msg' }
+ */
+function handleMigrationSkip(): void {
+ $pdo = getInstallerPdo();
+ if (!$pdo) return;
+
+ $file = $_POST['file'] ?? '';
+ $version = (int)($_POST['version'] ?? 0);
+ $error = (string)($_POST['error'] ?? '');
+
+ $allowed = array_column(installerMigrationList(), 0);
+ if (!in_array($file, $allowed, true)) {
+ echo json_encode(['success' => false, 'message' => "Migration '{$file}' not on the canonical list."]);
+ return;
+ }
+
+ $svTable = '`' . installerT('schema_versions') . '`';
+ try {
+ $stmt = $pdo->prepare("INSERT IGNORE INTO {$svTable} (version, filename, checksum) VALUES (?, ?, ?)");
+ $stmt->execute([$version, $file, 'SKIPPED:' . substr(hash('sha256', $error . microtime()), 0, 16)]);
+ installerLog("migration_skipped: {$file} (error: " . substr($error, 0, 200) . ")");
+ echo json_encode(['success' => true, 'message' => "Migration '{$file}' marked as skipped."]);
+ } catch (PDOException $e) {
+ echo json_encode(['success' => false, 'message' => $e->getMessage()]);
+ }
+}
diff --git a/FINAL_PRODUCTION_SYSTEM/install/index.php b/FINAL_PRODUCTION_SYSTEM/install/index.php
index d7b7169..ac8b424 100644
--- a/FINAL_PRODUCTION_SYSTEM/install/index.php
+++ b/FINAL_PRODUCTION_SYSTEM/install/index.php
@@ -616,9 +616,13 @@
-
- Open Admin Panel →
-
+
+
@@ -645,9 +649,34 @@ function goStep(n) {
});
currentStep = n;
+ // Persist breadcrumb so reload picks up where the user left off (P2).
+ if (n > 1) post('progress_set', { step: n - 1 }).catch(() => {});
window.scrollTo(0, 0);
}
+// ── Resume on boot (P2): if .progress.json exists, jump to last+1 step ──
+async function maybeResumeFromProgress() {
+ try {
+ const r = await post('progress_get', {});
+ if (r.success && r.progress && r.progress.last_step) {
+ const last = parseInt(r.progress.last_step, 10);
+ if (last >= 1 && last < 6) {
+ const ok = confirm(
+ 'Previous installation in progress (step ' + last + ' completed at ' +
+ (r.progress.steps[last] || 'unknown') + ').\n\nResume from step ' +
+ (last + 1) + '? (Cancel = start over)'
+ );
+ if (ok) {
+ goStep(last + 1);
+ return true;
+ }
+ await post('progress_clear', {});
+ }
+ }
+ } catch (e) { /* progress endpoint optional */ }
+ return false;
+}
+
// ── AJAX Helper ──────────────────────────────────────
async function post(action, data = {}) {
const body = new URLSearchParams({ action, ...data });
@@ -845,6 +874,14 @@ function renderChecks(containerId, checks) {
if (!r.success && r.status === 'error') {
hadError = true;
+ // Append inline Retry / Skip buttons next to the error row
+ const errRow = log.lastChild;
+ const btnBar = document.createElement('div');
+ btnBar.style.cssText = 'margin:6px 0 12px 22px;display:flex;gap:8px;';
+ btnBar.innerHTML =
+ `Retry ` +
+ `Skip `;
+ errRow.appendChild(btnBar);
// Stop loop on first hard error so user can read it.
break;
}
@@ -855,12 +892,70 @@ function renderChecks(containerId, checks) {
$('migStatus').style.color = 'var(--success)';
$('migNext').classList.remove('hidden');
} else {
- $('migStatus').textContent = 'Installation failed';
+ $('migStatus').textContent = 'Installation failed — click Retry or Skip on the failed step';
$('migStatus').style.color = 'var(--danger)';
$('migBack').disabled = false;
}
}
+// ── P2: Per-migration retry/skip ─────────────────────
+async function retryMigration(file, version, btn) {
+ btn.disabled = true;
+ btn.textContent = 'Retrying...';
+ const r = await post('install_db_step', { ...dbCredentials, file, version });
+ btn.disabled = false;
+ btn.textContent = 'Retry';
+ const log = document.getElementById('migLog');
+ const cls = r.status === 'ok' ? 'ok' : r.status === 'skipped' ? 'skip' : 'err';
+ const icon = r.status === 'ok' ? '✓' : r.status === 'skipped' ? '→' : '✗';
+ log.innerHTML += `${icon} ${file} (retry): ${r.message || ''}
`;
+ log.scrollTop = log.scrollHeight;
+ if (r.success && r.status !== 'error') {
+ // Resume forward by reloading the migration list and continuing.
+ runMigrations();
+ }
+}
+
+async function skipMigration(file, version, errorMsg, btn) {
+ if (!confirm(`Skip migration "${file}"?\n\nThe failed step will be marked applied so the rest can run, but you may end up in a broken state. Only do this if you know the failure is benign (e.g. the table already exists from a prior install).`)) {
+ return;
+ }
+ btn.disabled = true;
+ btn.textContent = 'Skipping...';
+ const r = await post('migration_skip', { ...dbCredentials, file, version, error: errorMsg });
+ btn.disabled = false;
+ btn.textContent = 'Skip';
+ const log = document.getElementById('migLog');
+ if (r.success) {
+ log.innerHTML += `→ ${file}: marked SKIPPED by user
`;
+ runMigrations();
+ } else {
+ log.innerHTML += `✗ ${file}: skip failed: ${r.message}
`;
+ }
+ log.scrollTop = log.scrollHeight;
+}
+
+// ── P2: Step 6 health probe ──────────────────────────
+async function runHealthCheck(btn) {
+ btn.disabled = true;
+ btn.innerHTML = ' Probing...';
+ const r = await post('health', { ...dbCredentials });
+ btn.disabled = false;
+ btn.textContent = 'Run health check';
+
+ const out = document.getElementById('healthResult');
+ if (!out) return;
+ out.classList.remove('hidden');
+ let html = `Health: `;
+ html += '
';
+ for (const c of (r.checks || [])) {
+ const icon = c.status === 'pass' ? '✓' : '✗';
+ html += `${icon} ${c.label}${c.value !== undefined ? ' (' + c.value + ')' : ''}${c.message ? ' — ' + c.message : ''} `;
+ }
+ html += ' ';
+ out.innerHTML = html;
+}
+
// ── Step 4: Create Admin ────────────────────────────
async function createAdmin() {
const btn = document.getElementById('adminBtn');
@@ -928,7 +1023,10 @@ function renderChecks(containerId, checks) {
}
// ── Auto-run env check on load ──────────────────────
-document.addEventListener('DOMContentLoaded', runEnvCheck);
+document.addEventListener('DOMContentLoaded', async () => {
+ const resumed = await maybeResumeFromProgress();
+ if (!resumed) runEnvCheck();
+});