From 796e1b3072e7a1ada81354b212269404401c2763 Mon Sep 17 00:00:00 2001 From: Jukka Lampikoski Date: Tue, 10 Mar 2026 16:04:14 +0200 Subject: [PATCH] feat: rewrite db.php from PDO to MySQLi PDO extension was not available on CloudLinux/alt-php84 server. MySQLi is available, so rewrote entire database layer to use it. Added helper functions (_dbRun, _dbFetchAll, _dbFetchOne, etc.) that handle named parameter conversion and type binding automatically. All public db*() function signatures remain identical. Co-Authored-By: Claude Opus 4.6 --- db.php | 449 +++++++++++++++++++++++---------------------------- phpcheck.php | 10 -- 2 files changed, 205 insertions(+), 254 deletions(-) delete mode 100644 phpcheck.php diff --git a/db.php b/db.php index dc99222..d4c8138 100644 --- a/db.php +++ b/db.php @@ -1,6 +1,6 @@ PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::ATTR_EMULATE_PREPARES => false, - ]); + + mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); + $db = new mysqli($cfg['host'], $cfg['username'], $cfg['password'], $cfg['dbname']); + $db->set_charset($cfg['charset'] ?? 'utf8mb4'); } - return $pdo; + return $db; +} + +// ==================== HELPER-FUNKTIOT ==================== + +/** + * Valmistele ja suorita kysely. Tukee sekä nimettyja (:name) että positiivisia (?) parametreja. + */ +function _dbRun(string $sql, array $params = []): mysqli_stmt { + $db = getDb(); + + // Muunna nimetyt parametrit (:name) positiivisiksi (?) + if (!empty($params) && !array_is_list($params)) { + $ordered = []; + $sql = preg_replace_callback('/:(\w+)/', function($m) use ($params, &$ordered) { + $key = $m[1]; + $ordered[] = array_key_exists($key, $params) ? $params[$key] : null; + return '?'; + }, $sql); + $params = $ordered; + } + + $stmt = $db->prepare($sql); + if ($stmt === false) { + throw new RuntimeException("MySQL prepare failed: " . $db->error); + } + + if (!empty($params)) { + $types = ''; + foreach ($params as $p) { + if (is_int($p)) $types .= 'i'; + elseif (is_float($p)) $types .= 'd'; + elseif (is_bool($p)) $types .= 'i'; + else $types .= 's'; + } + // Booleanit → int + $vals = array_map(fn($v) => is_bool($v) ? (int)$v : $v, array_values($params)); + $stmt->bind_param($types, ...$vals); + } + + $stmt->execute(); + return $stmt; +} + +/** SELECT — kaikki rivit */ +function _dbFetchAll(string $sql, array $params = []): array { + $stmt = _dbRun($sql, $params); + $result = $stmt->get_result(); + return $result ? $result->fetch_all(MYSQLI_ASSOC) : []; +} + +/** SELECT — yksi rivi */ +function _dbFetchOne(string $sql, array $params = []): ?array { + $stmt = _dbRun($sql, $params); + $result = $stmt->get_result(); + $row = $result ? $result->fetch_assoc() : null; + return $row ?: null; +} + +/** SELECT — yhden sarakkeen arvot */ +function _dbFetchColumn(string $sql, array $params = []): array { + $stmt = _dbRun($sql, $params); + $result = $stmt->get_result(); + if (!$result) return []; + $col = []; + while ($row = $result->fetch_row()) { + $col[] = $row[0]; + } + return $col; +} + +/** SELECT — yksittäinen skalaariarvo */ +function _dbFetchScalar(string $sql, array $params = []) { + $stmt = _dbRun($sql, $params); + $result = $stmt->get_result(); + $row = $result ? $result->fetch_row() : null; + return $row ? $row[0] : null; +} + +/** INSERT/UPDATE/DELETE — suorita, palauta affected_rows */ +function _dbExecute(string $sql, array $params = []): int { + $stmt = _dbRun($sql, $params); + return $stmt->affected_rows; } // ==================== TAULUJEN LUONTI ==================== @@ -31,7 +111,6 @@ function getDb(): PDO { function initDatabase(): void { $db = getDb(); - // Jokainen CREATE TABLE omana exec()-kutsuna — MySQL/PDO ei tue useaa lausetta kerralla $tables = [ "CREATE TABLE IF NOT EXISTS companies ( id VARCHAR(50) PRIMARY KEY, @@ -264,10 +343,8 @@ function initDatabase(): void { ]; foreach ($tables as $i => $sql) { - try { - $db->exec($sql); - } catch (PDOException $e) { - throw new RuntimeException("Taulun luonti epäonnistui (taulu #" . ($i+1) . "): " . $e->getMessage()); + if ($db->query($sql) === false) { + throw new RuntimeException("Taulun luonti epäonnistui (taulu #" . ($i+1) . "): " . $db->error); } } } @@ -275,14 +352,10 @@ function initDatabase(): void { // ==================== YRITYKSET ==================== function dbLoadCompanies(): array { - $db = getDb(); - $companies = $db->query("SELECT * FROM companies ORDER BY nimi")->fetchAll(); + $companies = _dbFetchAll("SELECT * FROM companies ORDER BY nimi"); - // Liitä domainit foreach ($companies as &$c) { - $stmt = $db->prepare("SELECT domain FROM company_domains WHERE company_id = ?"); - $stmt->execute([$c['id']]); - $c['domains'] = $stmt->fetchAll(PDO::FETCH_COLUMN); + $c['domains'] = _dbFetchColumn("SELECT domain FROM company_domains WHERE company_id = ?", [$c['id']]); $c['aktiivinen'] = (bool)$c['aktiivinen']; } return $companies; @@ -290,17 +363,16 @@ function dbLoadCompanies(): array { function dbSaveCompany(array $company): void { $db = getDb(); - $db->beginTransaction(); + $db->begin_transaction(); try { - $stmt = $db->prepare(" + _dbExecute(" INSERT INTO companies (id, nimi, luotu, aktiivinen, primary_color, subtitle, logo_file, api_key, cors_origins) VALUES (:id, :nimi, :luotu, :aktiivinen, :primary_color, :subtitle, :logo_file, :api_key, :cors_origins) ON DUPLICATE KEY UPDATE nimi = VALUES(nimi), aktiivinen = VALUES(aktiivinen), primary_color = VALUES(primary_color), subtitle = VALUES(subtitle), logo_file = VALUES(logo_file), api_key = VALUES(api_key), cors_origins = VALUES(cors_origins) - "); - $stmt->execute([ + ", [ 'id' => $company['id'], 'nimi' => $company['nimi'], 'luotu' => $company['luotu'] ?? date('Y-m-d H:i:s'), @@ -313,38 +385,35 @@ function dbSaveCompany(array $company): void { ]); // Päivitä domainit - $db->prepare("DELETE FROM company_domains WHERE company_id = ?")->execute([$company['id']]); + _dbExecute("DELETE FROM company_domains WHERE company_id = ?", [$company['id']]); if (!empty($company['domains'])) { - $ins = $db->prepare("INSERT INTO company_domains (company_id, domain) VALUES (?, ?)"); foreach ($company['domains'] as $domain) { $domain = trim($domain); - if ($domain) $ins->execute([$company['id'], $domain]); + if ($domain) { + _dbExecute("INSERT INTO company_domains (company_id, domain) VALUES (?, ?)", [$company['id'], $domain]); + } } } $db->commit(); } catch (Exception $e) { - $db->rollBack(); + $db->rollback(); throw $e; } } function dbDeleteCompany(string $companyId): void { - $db = getDb(); - $db->prepare("DELETE FROM companies WHERE id = ?")->execute([$companyId]); + _dbExecute("DELETE FROM companies WHERE id = ?", [$companyId]); } function dbGetBranding(string $host): array { - $db = getDb(); $host = strtolower(trim($host)); - $stmt = $db->prepare(" + $company = _dbFetchOne(" SELECT c.* FROM companies c JOIN company_domains cd ON c.id = cd.company_id WHERE LOWER(cd.domain) = ? LIMIT 1 - "); - $stmt->execute([$host]); - $company = $stmt->fetch(); + ", [$host]); if ($company) { $logoUrl = !empty($company['logo_file']) @@ -371,41 +440,29 @@ function dbGetBranding(string $host): array { } function dbGetCompanyByDomain(string $host): ?array { - $db = getDb(); - $stmt = $db->prepare(" + return _dbFetchOne(" SELECT c.* FROM companies c JOIN company_domains cd ON c.id = cd.company_id WHERE LOWER(cd.domain) = ? LIMIT 1 - "); - $stmt->execute([strtolower(trim($host))]); - return $stmt->fetch() ?: null; + ", [strtolower(trim($host))]); } function dbGetCompanyByApiKey(string $apiKey): ?array { - $db = getDb(); - $stmt = $db->prepare("SELECT * FROM companies WHERE api_key = ? AND api_key != '' LIMIT 1"); - $stmt->execute([$apiKey]); - return $stmt->fetch() ?: null; + return _dbFetchOne("SELECT * FROM companies WHERE api_key = ? AND api_key != '' LIMIT 1", [$apiKey]); } // ==================== KÄYTTÄJÄT ==================== function dbLoadUsers(): array { - $db = getDb(); - $users = $db->query("SELECT * FROM users ORDER BY luotu")->fetchAll(); + $users = _dbFetchAll("SELECT * FROM users ORDER BY luotu"); foreach ($users as &$u) { - // Yritykset - $stmt = $db->prepare("SELECT company_id FROM user_companies WHERE user_id = ?"); - $stmt->execute([$u['id']]); - $u['companies'] = $stmt->fetchAll(PDO::FETCH_COLUMN); + $u['companies'] = _dbFetchColumn("SELECT company_id FROM user_companies WHERE user_id = ?", [$u['id']]); - // Allekirjoitukset - $stmt = $db->prepare("SELECT mailbox_id, signature FROM user_signatures WHERE user_id = ?"); - $stmt->execute([$u['id']]); + $sigRows = _dbFetchAll("SELECT mailbox_id, signature FROM user_signatures WHERE user_id = ?", [$u['id']]); $sigs = []; - foreach ($stmt->fetchAll() as $row) { + foreach ($sigRows as $row) { $sigs[$row['mailbox_id']] = $row['signature']; } $u['signatures'] = $sigs; @@ -414,20 +471,14 @@ function dbLoadUsers(): array { } function dbGetUser(string $id): ?array { - $db = getDb(); - $stmt = $db->prepare("SELECT * FROM users WHERE id = ?"); - $stmt->execute([$id]); - $u = $stmt->fetch(); + $u = _dbFetchOne("SELECT * FROM users WHERE id = ?", [$id]); if (!$u) return null; - $stmt = $db->prepare("SELECT company_id FROM user_companies WHERE user_id = ?"); - $stmt->execute([$id]); - $u['companies'] = $stmt->fetchAll(PDO::FETCH_COLUMN); + $u['companies'] = _dbFetchColumn("SELECT company_id FROM user_companies WHERE user_id = ?", [$id]); - $stmt = $db->prepare("SELECT mailbox_id, signature FROM user_signatures WHERE user_id = ?"); - $stmt->execute([$id]); + $sigRows = _dbFetchAll("SELECT mailbox_id, signature FROM user_signatures WHERE user_id = ?", [$id]); $sigs = []; - foreach ($stmt->fetchAll() as $row) { + foreach ($sigRows as $row) { $sigs[$row['mailbox_id']] = $row['signature']; } $u['signatures'] = $sigs; @@ -436,20 +487,14 @@ function dbGetUser(string $id): ?array { } function dbGetUserByUsername(string $username): ?array { - $db = getDb(); - $stmt = $db->prepare("SELECT * FROM users WHERE username = ?"); - $stmt->execute([$username]); - $u = $stmt->fetch(); + $u = _dbFetchOne("SELECT * FROM users WHERE username = ?", [$username]); if (!$u) return null; - $stmt = $db->prepare("SELECT company_id FROM user_companies WHERE user_id = ?"); - $stmt->execute([$u['id']]); - $u['companies'] = $stmt->fetchAll(PDO::FETCH_COLUMN); + $u['companies'] = _dbFetchColumn("SELECT company_id FROM user_companies WHERE user_id = ?", [$u['id']]); - $stmt = $db->prepare("SELECT mailbox_id, signature FROM user_signatures WHERE user_id = ?"); - $stmt->execute([$u['id']]); + $sigRows = _dbFetchAll("SELECT mailbox_id, signature FROM user_signatures WHERE user_id = ?", [$u['id']]); $sigs = []; - foreach ($stmt->fetchAll() as $row) { + foreach ($sigRows as $row) { $sigs[$row['mailbox_id']] = $row['signature']; } $u['signatures'] = $sigs; @@ -459,16 +504,15 @@ function dbGetUserByUsername(string $username): ?array { function dbSaveUser(array $user): void { $db = getDb(); - $db->beginTransaction(); + $db->begin_transaction(); try { - $stmt = $db->prepare(" + _dbExecute(" INSERT INTO users (id, username, password_hash, nimi, role, email, luotu) VALUES (:id, :username, :password_hash, :nimi, :role, :email, :luotu) ON DUPLICATE KEY UPDATE username = VALUES(username), password_hash = VALUES(password_hash), nimi = VALUES(nimi), role = VALUES(role), email = VALUES(email) - "); - $stmt->execute([ + ", [ 'id' => $user['id'], 'username' => $user['username'], 'password_hash' => $user['password_hash'], @@ -479,40 +523,36 @@ function dbSaveUser(array $user): void { ]); // Yritykset - $db->prepare("DELETE FROM user_companies WHERE user_id = ?")->execute([$user['id']]); + _dbExecute("DELETE FROM user_companies WHERE user_id = ?", [$user['id']]); if (!empty($user['companies'])) { - $ins = $db->prepare("INSERT IGNORE INTO user_companies (user_id, company_id) VALUES (?, ?)"); foreach ($user['companies'] as $cid) { - $ins->execute([$user['id'], $cid]); + _dbExecute("INSERT IGNORE INTO user_companies (user_id, company_id) VALUES (?, ?)", [$user['id'], $cid]); } } // Allekirjoitukset - $db->prepare("DELETE FROM user_signatures WHERE user_id = ?")->execute([$user['id']]); + _dbExecute("DELETE FROM user_signatures WHERE user_id = ?", [$user['id']]); if (!empty($user['signatures'])) { - $ins = $db->prepare("INSERT INTO user_signatures (user_id, mailbox_id, signature) VALUES (?, ?, ?)"); foreach ($user['signatures'] as $mbId => $sig) { - $ins->execute([$user['id'], $mbId, $sig]); + _dbExecute("INSERT INTO user_signatures (user_id, mailbox_id, signature) VALUES (?, ?, ?)", [$user['id'], $mbId, $sig]); } } $db->commit(); } catch (Exception $e) { - $db->rollBack(); + $db->rollback(); throw $e; } } function dbDeleteUser(string $userId): void { - $db = getDb(); - $db->prepare("DELETE FROM users WHERE id = ?")->execute([$userId]); + _dbExecute("DELETE FROM users WHERE id = ?", [$userId]); } // ==================== ASETUKSET (global) ==================== function dbLoadConfig(): array { - $db = getDb(); - $rows = $db->query("SELECT config_key, config_value FROM config")->fetchAll(); + $rows = _dbFetchAll("SELECT config_key, config_value FROM config"); $config = []; foreach ($rows as $row) { $decoded = json_decode($row['config_value'], true); @@ -522,69 +562,52 @@ function dbLoadConfig(): array { } function dbSaveConfig(array $config): void { - $db = getDb(); - $stmt = $db->prepare(" - INSERT INTO config (config_key, config_value) VALUES (?, ?) - ON DUPLICATE KEY UPDATE config_value = VALUES(config_value) - "); foreach ($config as $key => $value) { - $stmt->execute([$key, is_array($value) ? json_encode($value) : $value]); + _dbExecute( + "INSERT INTO config (config_key, config_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE config_value = VALUES(config_value)", + [$key, is_array($value) ? json_encode($value) : (string)$value] + ); } } // ==================== SALASANAN PALAUTUS ==================== function dbSaveToken(string $userId, string $token): void { - $db = getDb(); - $stmt = $db->prepare("INSERT INTO reset_tokens (user_id, token, created_at) VALUES (?, ?, NOW())"); - $stmt->execute([$userId, hash('sha256', $token)]); + _dbExecute("INSERT INTO reset_tokens (user_id, token, created_at) VALUES (?, ?, NOW())", [$userId, hash('sha256', $token)]); } function dbValidateToken(string $token): ?string { - $db = getDb(); $hash = hash('sha256', $token); - $stmt = $db->prepare("SELECT user_id FROM reset_tokens WHERE token = ? AND created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)"); - $stmt->execute([$hash]); - $row = $stmt->fetch(); + $row = _dbFetchOne("SELECT user_id FROM reset_tokens WHERE token = ? AND created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)", [$hash]); return $row ? $row['user_id'] : null; } function dbRemoveToken(string $token): void { - $db = getDb(); - $db->prepare("DELETE FROM reset_tokens WHERE token = ?")->execute([hash('sha256', $token)]); + _dbExecute("DELETE FROM reset_tokens WHERE token = ?", [hash('sha256', $token)]); // Siivoa vanhentuneet - $db->exec("DELETE FROM reset_tokens WHERE created_at < DATE_SUB(NOW(), INTERVAL 1 HOUR)"); + getDb()->query("DELETE FROM reset_tokens WHERE created_at < DATE_SUB(NOW(), INTERVAL 1 HOUR)"); } // ==================== RATE LIMITING ==================== function dbCheckRateLimit(string $ip): bool { - $db = getDb(); - $stmt = $db->prepare("SELECT COUNT(*) FROM login_attempts WHERE ip = ? AND attempted_at > DATE_SUB(NOW(), INTERVAL 15 MINUTE)"); - $stmt->execute([$ip]); - return $stmt->fetchColumn() < 10; + $count = _dbFetchScalar("SELECT COUNT(*) FROM login_attempts WHERE ip = ? AND attempted_at > DATE_SUB(NOW(), INTERVAL 15 MINUTE)", [$ip]); + return (int)$count < 10; } function dbRecordLoginAttempt(string $ip): void { - $db = getDb(); - $db->prepare("INSERT INTO login_attempts (ip, attempted_at) VALUES (?, NOW())")->execute([$ip]); + _dbExecute("INSERT INTO login_attempts (ip, attempted_at) VALUES (?, NOW())", [$ip]); // Siivoa vanhat (yli 1h) - $db->exec("DELETE FROM login_attempts WHERE attempted_at < DATE_SUB(NOW(), INTERVAL 1 HOUR)"); + getDb()->query("DELETE FROM login_attempts WHERE attempted_at < DATE_SUB(NOW(), INTERVAL 1 HOUR)"); } // ==================== ASIAKKAAT ==================== function dbLoadCustomers(string $companyId): array { - $db = getDb(); - $stmt = $db->prepare("SELECT * FROM customers WHERE company_id = ? ORDER BY yritys"); - $stmt->execute([$companyId]); - $customers = $stmt->fetchAll(); + $customers = _dbFetchAll("SELECT * FROM customers WHERE company_id = ? ORDER BY yritys", [$companyId]); - // Liitä liittymät - $connStmt = $db->prepare("SELECT * FROM customer_connections WHERE customer_id = ?"); foreach ($customers as &$c) { - $connStmt->execute([$c['id']]); - $conns = $connStmt->fetchAll(); + $conns = _dbFetchAll("SELECT * FROM customer_connections WHERE customer_id = ?", [$c['id']]); $c['liittymat'] = array_map(function($conn) { return [ 'asennusosoite' => $conn['asennusosoite'] ?? '', @@ -596,7 +619,6 @@ function dbLoadCustomers(string $companyId): array { 'alkupvm' => $conn['alkupvm'] ?? '', ]; }, $conns); - // Poista company_id vastauksesta (ei tarvita frontissa) unset($c['company_id']); } return $customers; @@ -604,9 +626,9 @@ function dbLoadCustomers(string $companyId): array { function dbSaveCustomer(string $companyId, array $customer): void { $db = getDb(); - $db->beginTransaction(); + $db->begin_transaction(); try { - $stmt = $db->prepare(" + _dbExecute(" INSERT INTO customers (id, company_id, yritys, yhteyshenkilö, puhelin, sahkoposti, laskutusosoite, laskutuspostinumero, laskutuskaupunki, laskutussahkoposti, elaskuosoite, elaskuvalittaja, ytunnus, lisatiedot, luotu, muokattu, muokkaaja) @@ -621,8 +643,7 @@ function dbSaveCustomer(string $companyId, array $customer): void { elaskuosoite = VALUES(elaskuosoite), elaskuvalittaja = VALUES(elaskuvalittaja), ytunnus = VALUES(ytunnus), lisatiedot = VALUES(lisatiedot), muokattu = VALUES(muokattu), muokkaaja = VALUES(muokkaaja) - "); - $stmt->execute([ + ", [ 'id' => $customer['id'], 'company_id' => $companyId, 'yritys' => $customer['yritys'] ?? '', @@ -643,14 +664,13 @@ function dbSaveCustomer(string $companyId, array $customer): void { ]); // Päivitä liittymät - $db->prepare("DELETE FROM customer_connections WHERE customer_id = ?")->execute([$customer['id']]); + _dbExecute("DELETE FROM customer_connections WHERE customer_id = ?", [$customer['id']]); if (!empty($customer['liittymat'])) { - $ins = $db->prepare(" - INSERT INTO customer_connections (customer_id, asennusosoite, postinumero, kaupunki, liittymanopeus, hinta, sopimuskausi, alkupvm) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) - "); foreach ($customer['liittymat'] as $l) { - $ins->execute([ + _dbExecute(" + INSERT INTO customer_connections (customer_id, asennusosoite, postinumero, kaupunki, liittymanopeus, hinta, sopimuskausi, alkupvm) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ", [ $customer['id'], $l['asennusosoite'] ?? '', $l['postinumero'] ?? '', @@ -664,23 +684,19 @@ function dbSaveCustomer(string $companyId, array $customer): void { } $db->commit(); } catch (Exception $e) { - $db->rollBack(); + $db->rollback(); throw $e; } } function dbDeleteCustomer(string $customerId): void { - $db = getDb(); - $db->prepare("DELETE FROM customers WHERE id = ?")->execute([$customerId]); + _dbExecute("DELETE FROM customers WHERE id = ?", [$customerId]); } // ==================== LIIDIT ==================== function dbLoadLeads(string $companyId): array { - $db = getDb(); - $stmt = $db->prepare("SELECT * FROM leads WHERE company_id = ? ORDER BY luotu DESC"); - $stmt->execute([$companyId]); - $leads = $stmt->fetchAll(); + $leads = _dbFetchAll("SELECT * FROM leads WHERE company_id = ? ORDER BY luotu DESC", [$companyId]); foreach ($leads as &$l) { unset($l['company_id']); } @@ -688,8 +704,7 @@ function dbLoadLeads(string $companyId): array { } function dbSaveLead(string $companyId, array $lead): void { - $db = getDb(); - $stmt = $db->prepare(" + _dbExecute(" INSERT INTO leads (id, company_id, yritys, yhteyshenkilo, puhelin, sahkoposti, osoite, kaupunki, tila, muistiinpanot, luotu, luoja, muokattu, muokkaaja) VALUES (:id, :company_id, :yritys, :yhteyshenkilo, :puhelin, :sahkoposti, :osoite, :kaupunki, :tila, :muistiinpanot, :luotu, :luoja, :muokattu, :muokkaaja) ON DUPLICATE KEY UPDATE @@ -698,8 +713,7 @@ function dbSaveLead(string $companyId, array $lead): void { osoite = VALUES(osoite), kaupunki = VALUES(kaupunki), tila = VALUES(tila), muistiinpanot = VALUES(muistiinpanot), muokattu = VALUES(muokattu), muokkaaja = VALUES(muokkaaja) - "); - $stmt->execute([ + ", [ 'id' => $lead['id'], 'company_id' => $companyId, 'yritys' => $lead['yritys'] ?? '', @@ -718,24 +732,17 @@ function dbSaveLead(string $companyId, array $lead): void { } function dbDeleteLead(string $leadId): void { - $db = getDb(); - $db->prepare("DELETE FROM leads WHERE id = ?")->execute([$leadId]); + _dbExecute("DELETE FROM leads WHERE id = ?", [$leadId]); } // ==================== TIKETIT ==================== function dbLoadTickets(string $companyId): array { - $db = getDb(); - $stmt = $db->prepare("SELECT * FROM tickets WHERE company_id = ? ORDER BY updated DESC"); - $stmt->execute([$companyId]); - $tickets = $stmt->fetchAll(); - - $msgStmt = $db->prepare("SELECT * FROM ticket_messages WHERE ticket_id = ? ORDER BY timestamp"); - $tagStmt = $db->prepare("SELECT tag FROM ticket_tags WHERE ticket_id = ?"); + $tickets = _dbFetchAll("SELECT * FROM tickets WHERE company_id = ? ORDER BY updated DESC", [$companyId]); foreach ($tickets as &$t) { // Viestit - $msgStmt->execute([$t['id']]); + $msgs = _dbFetchAll("SELECT * FROM ticket_messages WHERE ticket_id = ? ORDER BY timestamp", [$t['id']]); $t['messages'] = array_map(function($m) { return [ 'id' => $m['id'], @@ -746,11 +753,10 @@ function dbLoadTickets(string $companyId): array { 'timestamp' => $m['timestamp'], 'message_id' => $m['message_id'] ?? '', ]; - }, $msgStmt->fetchAll()); + }, $msgs); // Tagit - $tagStmt->execute([$t['id']]); - $t['tags'] = $tagStmt->fetchAll(PDO::FETCH_COLUMN); + $t['tags'] = _dbFetchColumn("SELECT tag FROM ticket_tags WHERE ticket_id = ?", [$t['id']]); unset($t['company_id']); } @@ -759,9 +765,9 @@ function dbLoadTickets(string $companyId): array { function dbSaveTicket(string $companyId, array $ticket): void { $db = getDb(); - $db->beginTransaction(); + $db->begin_transaction(); try { - $stmt = $db->prepare(" + _dbExecute(" INSERT INTO tickets (id, company_id, subject, from_email, from_name, status, type, assigned_to, customer_id, customer_name, message_id, mailbox_id, auto_close_at, created, updated) VALUES (:id, :company_id, :subject, :from_email, :from_name, :status, :type, @@ -772,8 +778,7 @@ function dbSaveTicket(string $companyId, array $ticket): void { customer_id = VALUES(customer_id), customer_name = VALUES(customer_name), message_id = VALUES(message_id), mailbox_id = VALUES(mailbox_id), auto_close_at = VALUES(auto_close_at), updated = VALUES(updated) - "); - $stmt->execute([ + ", [ 'id' => $ticket['id'], 'company_id' => $companyId, 'subject' => $ticket['subject'] ?? '', @@ -793,12 +798,11 @@ function dbSaveTicket(string $companyId, array $ticket): void { // Viestit — lisää vain uudet (ei poista vanhoja) if (!empty($ticket['messages'])) { - $ins = $db->prepare(" - INSERT IGNORE INTO ticket_messages (id, ticket_id, type, from_email, from_name, body, timestamp, message_id) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) - "); foreach ($ticket['messages'] as $m) { - $ins->execute([ + _dbExecute(" + INSERT IGNORE INTO ticket_messages (id, ticket_id, type, from_email, from_name, body, timestamp, message_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ", [ $m['id'], $ticket['id'], $m['type'], @@ -812,52 +816,44 @@ function dbSaveTicket(string $companyId, array $ticket): void { } // Tagit — korvaa kaikki - $db->prepare("DELETE FROM ticket_tags WHERE ticket_id = ?")->execute([$ticket['id']]); + _dbExecute("DELETE FROM ticket_tags WHERE ticket_id = ?", [$ticket['id']]); if (!empty($ticket['tags'])) { - $ins = $db->prepare("INSERT INTO ticket_tags (ticket_id, tag) VALUES (?, ?)"); foreach ($ticket['tags'] as $tag) { - if ($tag) $ins->execute([$ticket['id'], $tag]); + if ($tag) { + _dbExecute("INSERT INTO ticket_tags (ticket_id, tag) VALUES (?, ?)", [$ticket['id'], $tag]); + } } } $db->commit(); } catch (Exception $e) { - $db->rollBack(); + $db->rollback(); throw $e; } } function dbDeleteTicket(string $ticketId): void { - $db = getDb(); - $db->prepare("DELETE FROM tickets WHERE id = ?")->execute([$ticketId]); + _dbExecute("DELETE FROM tickets WHERE id = ?", [$ticketId]); } function dbFindTicketByMessageId(string $companyId, string $messageId): ?array { - $db = getDb(); // Ensin tikettitasolla - $stmt = $db->prepare("SELECT * FROM tickets WHERE company_id = ? AND message_id = ? LIMIT 1"); - $stmt->execute([$companyId, $messageId]); - $t = $stmt->fetch(); + $t = _dbFetchOne("SELECT * FROM tickets WHERE company_id = ? AND message_id = ? LIMIT 1", [$companyId, $messageId]); if ($t) return $t; // Sitten viestien message_id:stä - $stmt = $db->prepare(" + return _dbFetchOne(" SELECT t.* FROM tickets t JOIN ticket_messages tm ON t.id = tm.ticket_id WHERE t.company_id = ? AND tm.message_id = ? LIMIT 1 - "); - $stmt->execute([$companyId, $messageId]); - return $stmt->fetch() ?: null; + ", [$companyId, $messageId]); } // ==================== ARKISTO ==================== function dbLoadArchive(string $companyId): array { - $db = getDb(); - $stmt = $db->prepare("SELECT * FROM archives WHERE company_id = ? ORDER BY archived_at DESC"); - $stmt->execute([$companyId]); - $rows = $stmt->fetchAll(); + $rows = _dbFetchAll("SELECT * FROM archives WHERE company_id = ? ORDER BY archived_at DESC", [$companyId]); return array_map(function($row) { $data = json_decode($row['data'], true) ?? []; $data['id'] = $row['id']; @@ -867,61 +863,46 @@ function dbLoadArchive(string $companyId): array { } function dbArchiveCustomer(string $companyId, array $customerData): void { - $db = getDb(); - $stmt = $db->prepare("INSERT INTO archives (id, company_id, data, archived_at) VALUES (?, ?, ?, NOW())"); - $stmt->execute([$customerData['id'], $companyId, json_encode($customerData, JSON_UNESCAPED_UNICODE)]); + _dbExecute("INSERT INTO archives (id, company_id, data, archived_at) VALUES (?, ?, ?, NOW())", + [$customerData['id'], $companyId, json_encode($customerData, JSON_UNESCAPED_UNICODE)]); } function dbRestoreArchive(string $archiveId): ?array { - $db = getDb(); - $stmt = $db->prepare("SELECT * FROM archives WHERE id = ?"); - $stmt->execute([$archiveId]); - $row = $stmt->fetch(); + $row = _dbFetchOne("SELECT * FROM archives WHERE id = ?", [$archiveId]); if (!$row) return null; - $db->prepare("DELETE FROM archives WHERE id = ?")->execute([$archiveId]); + _dbExecute("DELETE FROM archives WHERE id = ?", [$archiveId]); return json_decode($row['data'], true); } function dbDeleteArchive(string $archiveId): void { - $db = getDb(); - $db->prepare("DELETE FROM archives WHERE id = ?")->execute([$archiveId]); + _dbExecute("DELETE FROM archives WHERE id = ?", [$archiveId]); } // ==================== CHANGELOG ==================== function dbAddLog(string $companyId, string $user, string $action, string $customerId = '', string $customerName = '', string $details = ''): void { if (empty($companyId)) return; - $db = getDb(); $id = bin2hex(random_bytes(8)); - $stmt = $db->prepare(" + _dbExecute(" INSERT INTO changelog (id, company_id, timestamp, user, action, customer_id, customer_name, details) VALUES (?, ?, NOW(), ?, ?, ?, ?, ?) - "); - $stmt->execute([$id, $companyId, $user, $action, $customerId, $customerName, $details]); + ", [$id, $companyId, $user, $action, $customerId, $customerName, $details]); // Pidä max 500 per yritys - $db->prepare(" - DELETE FROM changelog WHERE company_id = ? AND id NOT IN ( - SELECT id FROM (SELECT id FROM changelog WHERE company_id = ? ORDER BY timestamp DESC LIMIT 500) tmp - ) - ")->execute([$companyId, $companyId]); + getDb()->query("DELETE FROM changelog WHERE company_id = '" . getDb()->real_escape_string($companyId) . "' AND id NOT IN ( + SELECT id FROM (SELECT id FROM changelog WHERE company_id = '" . getDb()->real_escape_string($companyId) . "' ORDER BY timestamp DESC LIMIT 500) tmp + )"); } function dbLoadChangelog(string $companyId, int $limit = 100): array { - $db = getDb(); - $stmt = $db->prepare("SELECT * FROM changelog WHERE company_id = ? ORDER BY timestamp DESC LIMIT ?"); - $stmt->execute([$companyId, $limit]); - return $stmt->fetchAll(); + return _dbFetchAll("SELECT * FROM changelog WHERE company_id = ? ORDER BY timestamp DESC LIMIT ?", [$companyId, $limit]); } // ==================== POSTILAATIKOT ==================== function dbLoadMailboxes(string $companyId): array { - $db = getDb(); - $stmt = $db->prepare("SELECT * FROM mailboxes WHERE company_id = ?"); - $stmt->execute([$companyId]); - $boxes = $stmt->fetchAll(); + $boxes = _dbFetchAll("SELECT * FROM mailboxes WHERE company_id = ?", [$companyId]); foreach ($boxes as &$b) { $b['aktiivinen'] = (bool)$b['aktiivinen']; $b['imap_port'] = (int)$b['imap_port']; @@ -931,8 +912,7 @@ function dbLoadMailboxes(string $companyId): array { } function dbSaveMailbox(string $companyId, array $mailbox): void { - $db = getDb(); - $stmt = $db->prepare(" + _dbExecute(" INSERT INTO mailboxes (id, company_id, nimi, imap_host, imap_port, imap_user, imap_encryption, imap_password, smtp_from_email, smtp_from_name, aktiivinen) VALUES (:id, :company_id, :nimi, :imap_host, :imap_port, :imap_user, :imap_encryption, :imap_password, :smtp_from_email, :smtp_from_name, :aktiivinen) ON DUPLICATE KEY UPDATE @@ -940,8 +920,7 @@ function dbSaveMailbox(string $companyId, array $mailbox): void { imap_user = VALUES(imap_user), imap_encryption = VALUES(imap_encryption), imap_password = VALUES(imap_password), smtp_from_email = VALUES(smtp_from_email), smtp_from_name = VALUES(smtp_from_name), aktiivinen = VALUES(aktiivinen) - "); - $stmt->execute([ + ", [ 'id' => $mailbox['id'], 'company_id' => $companyId, 'nimi' => $mailbox['nimi'] ?? '', @@ -957,29 +936,22 @@ function dbSaveMailbox(string $companyId, array $mailbox): void { } function dbDeleteMailbox(string $mailboxId): void { - $db = getDb(); - $db->prepare("DELETE FROM mailboxes WHERE id = ?")->execute([$mailboxId]); + _dbExecute("DELETE FROM mailboxes WHERE id = ?", [$mailboxId]); } function dbGetMailbox(string $mailboxId): ?array { - $db = getDb(); - $stmt = $db->prepare("SELECT * FROM mailboxes WHERE id = ?"); - $stmt->execute([$mailboxId]); - $b = $stmt->fetch(); + $b = _dbFetchOne("SELECT * FROM mailboxes WHERE id = ?", [$mailboxId]); if ($b) { $b['aktiivinen'] = (bool)$b['aktiivinen']; $b['imap_port'] = (int)$b['imap_port']; } - return $b ?: null; + return $b; } // ==================== TIKETTISÄÄNNÖT ==================== function dbLoadTicketRules(string $companyId): array { - $db = getDb(); - $stmt = $db->prepare("SELECT * FROM ticket_rules WHERE company_id = ? ORDER BY priority"); - $stmt->execute([$companyId]); - $rules = $stmt->fetchAll(); + $rules = _dbFetchAll("SELECT * FROM ticket_rules WHERE company_id = ? ORDER BY priority", [$companyId]); foreach ($rules as &$r) { $r['priority'] = (int)$r['priority']; $r['auto_close_days'] = (int)$r['auto_close_days']; @@ -989,16 +961,14 @@ function dbLoadTicketRules(string $companyId): array { } function dbSaveTicketRule(string $companyId, array $rule): void { - $db = getDb(); - $stmt = $db->prepare(" + _dbExecute(" INSERT INTO ticket_rules (id, company_id, name, from_contains, priority, tag, assign_to, status_set, type_set, auto_close_days) VALUES (:id, :company_id, :name, :from_contains, :priority, :tag, :assign_to, :status_set, :type_set, :auto_close_days) ON DUPLICATE KEY UPDATE name = VALUES(name), from_contains = VALUES(from_contains), priority = VALUES(priority), tag = VALUES(tag), assign_to = VALUES(assign_to), status_set = VALUES(status_set), type_set = VALUES(type_set), auto_close_days = VALUES(auto_close_days) - "); - $stmt->execute([ + ", [ 'id' => $rule['id'], 'company_id' => $companyId, 'name' => $rule['name'] ?? '', @@ -1013,8 +983,7 @@ function dbSaveTicketRule(string $companyId, array $rule): void { } function dbDeleteTicketRule(string $ruleId): void { - $db = getDb(); - $db->prepare("DELETE FROM ticket_rules WHERE id = ?")->execute([$ruleId]); + _dbExecute("DELETE FROM ticket_rules WHERE id = ?", [$ruleId]); } // ==================== YRITYKSEN API-ASETUKSET ==================== @@ -1029,28 +998,20 @@ function dbGetCompanyConfig(string $companyId): array { } function dbGetCompanyApiKey(string $companyId): string { - $db = getDb(); - $stmt = $db->prepare("SELECT api_key FROM companies WHERE id = ?"); - $stmt->execute([$companyId]); - return $stmt->fetchColumn() ?: ''; + return _dbFetchScalar("SELECT api_key FROM companies WHERE id = ?", [$companyId]) ?: ''; } function dbGetCompanyCorsOrigins(string $companyId): array { - $db = getDb(); - $stmt = $db->prepare("SELECT cors_origins FROM companies WHERE id = ?"); - $stmt->execute([$companyId]); - $val = $stmt->fetchColumn(); + $val = _dbFetchScalar("SELECT cors_origins FROM companies WHERE id = ?", [$companyId]); if (!$val) return []; $decoded = json_decode($val, true); return is_array($decoded) ? $decoded : []; } function dbSetCompanyApiKey(string $companyId, string $apiKey): void { - $db = getDb(); - $db->prepare("UPDATE companies SET api_key = ? WHERE id = ?")->execute([$apiKey, $companyId]); + _dbExecute("UPDATE companies SET api_key = ? WHERE id = ?", [$apiKey, $companyId]); } function dbSetCompanyCorsOrigins(string $companyId, array $origins): void { - $db = getDb(); - $db->prepare("UPDATE companies SET cors_origins = ? WHERE id = ?")->execute([json_encode($origins), $companyId]); + _dbExecute("UPDATE companies SET cors_origins = ? WHERE id = ?", [json_encode($origins), $companyId]); } diff --git a/phpcheck.php b/phpcheck.php deleted file mode 100644 index 67c3209..0000000 --- a/phpcheck.php +++ /dev/null @@ -1,10 +0,0 @@ -PHP Info"; -echo "

PHP versio: " . PHP_VERSION . "

"; -echo "

SAPI: " . php_sapi_name() . "

"; -echo "

PDO loaded: " . (extension_loaded('pdo') ? 'KYLLÄ ✅' : 'EI ❌') . "

"; -echo "

PDO MySQL: " . (extension_loaded('pdo_mysql') ? 'KYLLÄ ✅' : 'EI ❌') . "

"; -echo "

MySQLi: " . (extension_loaded('mysqli') ? 'KYLLÄ ✅' : 'EI ❌') . "

"; -echo "

php.ini sijainti: " . php_ini_loaded_file() . "

"; -echo "

Kaikki laajennukset:

"; -echo "

" . implode(', ', get_loaded_extensions()) . "

";