0, 'path' => '/', 'secure' => $isHttps, 'httponly' => true, 'samesite' => 'Lax', ]); session_start(); } date_default_timezone_set('Europe/Istanbul'); const APP_NAME = 'FinansTakip Admin'; const DB_HOST = 'localhost'; const DB_PORT = '3306'; const DB_NAME = 'serkansenguncom_btadmin'; const DB_USER = 'serkansenguncom_btuser'; const DB_PASS = 'I&goOS~B,UCz'; function h(?string $value): string { return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); } function redirect(string $path): void { header('Location: ' . $path); exit; } function set_flash(string $type, string $message): void { $_SESSION['flash'] = [ 'type' => $type, 'message' => $message, ]; } function get_flash(): ?array { if (!isset($_SESSION['flash'])) { return null; } $flash = $_SESSION['flash']; unset($_SESSION['flash']); return $flash; } function csrf_token(): string { if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } return $_SESSION['csrf_token']; } function verify_csrf_token(?string $token): bool { return !empty($token) && !empty($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token); } function available_permissions(): array { return [ 'Dashboard', 'Transactions', 'Accounts', 'Reports', 'Users', 'Settings', ]; } function role_options(): array { return [ 1 => 'Firma Admini', 2 => 'Firma Kullanicisi', ]; } function role_label(int $roleId): string { $roles = role_options(); return $roles[$roleId] ?? 'Bilinmiyor'; } function admin_package_type_options(): array { return [ 'license' => 'Lisans', 'user' => 'Kullanici', ]; } function admin_get_company_status(array $companyRow): array { $today = new DateTimeImmutable('today'); $startRaw = trim((string) ($companyRow['license_start_date'] ?? '')); $endRaw = trim((string) ($companyRow['license_end_date'] ?? '')); $deactivatedRaw = trim((string) ($companyRow['deactivated_at'] ?? '')); $startDate = $startRaw !== '' ? DateTimeImmutable::createFromFormat('Y-m-d', $startRaw) : null; $endDate = $endRaw !== '' ? DateTimeImmutable::createFromFormat('Y-m-d', $endRaw) : null; $deactivatedAt = $deactivatedRaw !== '' ? new DateTimeImmutable($deactivatedRaw) : null; $isManuallyActive = (int) ($companyRow['is_active'] ?? 1) === 1; $daysRemaining = null; $daysInactive = null; if ($endDate instanceof DateTimeImmutable) { $daysRemaining = (int) $today->diff($endDate)->format('%r%a'); } $licenseActive = $endDate instanceof DateTimeImmutable ? $today <= $endDate : true; $statusActive = $isManuallyActive && $licenseActive; if (!$statusActive) { if ($deactivatedAt instanceof DateTimeImmutable) { $daysInactive = (int) $deactivatedAt->setTime(0, 0)->diff($today)->format('%a'); } elseif ($endDate instanceof DateTimeImmutable && $today > $endDate) { $daysInactive = max(0, (int) $endDate->diff($today)->format('%a')); } else { $daysInactive = 0; } } return [ 'is_active' => $statusActive, 'days_remaining' => $daysRemaining, 'days_inactive' => $daysInactive, 'start_date' => $startDate?->format('Y-m-d') ?? '', 'end_date' => $endDate?->format('Y-m-d') ?? '', 'is_license_expired' => !$licenseActive, 'is_manually_inactive' => !$isManuallyActive, ]; } function admin_get_packages(PDO $pdo, string $packageType): array { $stmt = $pdo->prepare(' SELECT * FROM license_packages WHERE package_type = ? AND is_active = 1 ORDER BY sort_order ASC, id ASC '); $stmt->execute([$packageType]); return $stmt->fetchAll(); } function admin_apply_package(PDO $pdo, int $companyId, int $packageId): void { $stmtCompany = $pdo->prepare('SELECT * FROM companies WHERE id = ? LIMIT 1'); $stmtCompany->execute([$companyId]); $companyRow = $stmtCompany->fetch(); if (!$companyRow) { throw new RuntimeException('Firma bulunamadi.'); } $stmtPackage = $pdo->prepare('SELECT * FROM license_packages WHERE id = ? AND is_active = 1 LIMIT 1'); $stmtPackage->execute([$packageId]); $package = $stmtPackage->fetch(); if (!$package) { throw new RuntimeException('Paket bulunamadi.'); } $today = new DateTimeImmutable('today'); $purchaseTime = new DateTimeImmutable('now'); $packageType = (string) $package['package_type']; $pdo->beginTransaction(); try { if ($packageType === 'license') { $currentEndRaw = trim((string) ($companyRow['license_end_date'] ?? '')); $currentEnd = $currentEndRaw !== '' ? DateTimeImmutable::createFromFormat('Y-m-d', $currentEndRaw) : null; $startsAt = $currentEnd instanceof DateTimeImmutable && $currentEnd >= $today ? $currentEnd->modify('+1 day') : $today; $endsAt = $startsAt->modify('+' . (int) $package['duration_days'] . ' days'); $startDate = trim((string) ($companyRow['license_start_date'] ?? '')); if ($startDate === '' || !($currentEnd instanceof DateTimeImmutable) || $currentEnd < $today) { $startDate = $today->format('Y-m-d'); } $stmtUpdate = $pdo->prepare(' UPDATE companies SET license_start_date = ?, license_end_date = ?, is_active = 1, deactivated_at = NULL, updated_at = NOW() WHERE id = ? '); $stmtUpdate->execute([ $startDate, $endsAt->format('Y-m-d'), $companyId, ]); $stmtOrder = $pdo->prepare(' INSERT INTO company_package_orders (company_id, package_id, package_type, package_name, duration_days, user_count, purchased_at, starts_at, ends_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW()) '); $stmtOrder->execute([ $companyId, $packageId, $packageType, $package['package_name'], (int) $package['duration_days'], 0, $purchaseTime->format('Y-m-d H:i:s'), $startsAt->format('Y-m-d'), $endsAt->format('Y-m-d'), ]); } elseif ($packageType === 'user') { $currentMaxUsers = max(0, (int) ($companyRow['max_users'] ?? 0)); $newMaxUsers = $currentMaxUsers + max(1, (int) $package['user_count']); $stmtUpdate = $pdo->prepare(' UPDATE companies SET max_users = ?, updated_at = NOW() WHERE id = ? '); $stmtUpdate->execute([$newMaxUsers, $companyId]); $stmtOrder = $pdo->prepare(' INSERT INTO company_package_orders (company_id, package_id, package_type, package_name, duration_days, user_count, purchased_at, starts_at, ends_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, NULL, NULL, NOW()) '); $stmtOrder->execute([ $companyId, $packageId, $packageType, $package['package_name'], 0, (int) $package['user_count'], $purchaseTime->format('Y-m-d H:i:s'), ]); } else { throw new RuntimeException('Gecersiz paket tipi.'); } $pdo->commit(); } catch (Throwable $exception) { if ($pdo->inTransaction()) { $pdo->rollBack(); } throw $exception; } } function admin_drop_company_database(array $companyRow): void { $databaseName = admin_company_database_name($companyRow); if ($databaseName === '') { return; } if (!preg_match('/^[A-Za-z0-9_]+$/', $databaseName)) { throw new RuntimeException('Guvensiz veritabani adi tespit edildi.'); } $dbUser = trim((string) ($companyRow['db_user'] ?? 'serkansenguncom_btuser')); $dbPass = (string) ($companyRow['db_pass'] ?? 'I&goOS~B,UCz'); $dbPort = (string) ((int) ($companyRow['db_port'] ?? 3306)); $errors = []; foreach (admin_company_connection_hosts((string) ($companyRow['db_host'] ?? 'localhost')) as $host) { try { $serverPdo = admin_create_pdo_connection($host, $dbPort, null, $dbUser, $dbPass); $serverPdo->exec('DROP DATABASE IF EXISTS `' . str_replace('`', '``', $databaseName) . '`'); return; } catch (Throwable $exception) { $errors[] = $host . ': ' . $exception->getMessage(); } } if ($errors !== []) { throw new RuntimeException('Veritabani silinemedi. Denenen hostlar: ' . implode(' | ', $errors)); } } function admin_delete_company(PDO $pdo, int $companyId): void { $stmtCompany = $pdo->prepare('SELECT * FROM companies WHERE id = ? LIMIT 1'); $stmtCompany->execute([$companyId]); $companyRow = $stmtCompany->fetch(); if (!$companyRow) { throw new RuntimeException('Firma bulunamadi.'); } $status = admin_get_company_status($companyRow); if ($status['is_active']) { throw new RuntimeException('Aktif firma silinemez. Once firmayi pasif hale getirin.'); } admin_drop_company_database($companyRow); $pdo->beginTransaction(); try { $stmtDelete = $pdo->prepare('DELETE FROM companies WHERE id = ?'); $stmtDelete->execute([$companyId]); $pdo->commit(); } catch (Throwable $exception) { if ($pdo->inTransaction()) { $pdo->rollBack(); } throw $exception; } } function normalize_permissions(array $postedPermissions, int $roleId): string { $validPermissions = available_permissions(); if ($roleId === 1) { return implode(',', $validPermissions); } $permissions = array_values(array_intersect($validPermissions, $postedPermissions)); return implode(',', $permissions); } function split_permissions(?string $permissions): array { if (empty($permissions)) { return []; } return array_values(array_filter(array_map('trim', explode(',', $permissions)))); } function admin_connection_hosts(): array { $hosts = [DB_HOST, '127.0.0.1', 'localhost']; return array_values(array_unique(array_filter($hosts))); } function admin_company_connection_hosts(string $preferredHost): array { $preferredHost = trim($preferredHost); $hosts = []; if ($preferredHost !== '') { $hosts[] = $preferredHost; } if ($preferredHost === '' || in_array($preferredHost, ['127.0.0.1', 'localhost'], true)) { array_unshift($hosts, 'localhost'); $hosts[] = '127.0.0.1'; } return array_values(array_unique(array_filter($hosts))); } function admin_create_pdo_connection(string $host, string $port, ?string $database, string $user, string $pass): PDO { $databasePart = $database !== null && $database !== '' ? ';dbname=' . $database : ''; $dsn = 'mysql:host=' . $host . ';port=' . $port . $databasePart . ';charset=utf8mb4'; return new PDO($dsn, $user, $pass, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]); } function admin_connect(bool $withDatabase = true): PDO { $lastException = null; foreach (admin_connection_hosts() as $host) { try { return admin_create_pdo_connection($host, DB_PORT, $withDatabase ? DB_NAME : null, DB_USER, DB_PASS); } catch (PDOException $exception) { $lastException = $exception; } } if ($lastException instanceof PDOException) { throw $lastException; } throw new RuntimeException('Veritabani baglantisi kurulamadi.'); } function admin_pdo(): PDO { static $pdo = null; if ($pdo instanceof PDO) { return $pdo; } try { $pdo = admin_connect(true); } catch (PDOException $exception) { try { $pdo = admin_connect(false); } catch (Throwable $fallbackException) { http_response_code(500); exit('Merkezi veritabanina baglanti kurulamadi. Lutfen ayarlari kontrol edin.'); } } return $pdo; } $pdo = admin_pdo(); function admin_required_tables(): array { return ['admins', 'companies', 'company_users']; } function admin_schema_missing_tables(PDO $pdo): array { $placeholders = implode(',', array_fill(0, count(admin_required_tables()), '?')); $sql = "SELECT table_name FROM information_schema.tables WHERE table_schema = ? AND table_name IN ($placeholders)"; $params = array_merge([DB_NAME], admin_required_tables()); $stmt = $pdo->prepare($sql); $stmt->execute($params); $existing = $stmt->fetchAll(PDO::FETCH_COLUMN); return array_values(array_diff(admin_required_tables(), $existing)); } function admin_table_has_column(PDO $pdo, string $tableName, string $columnName): bool { $stmt = $pdo->prepare( 'SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = ? AND table_name = ? AND column_name = ?' ); $stmt->execute([DB_NAME, $tableName, $columnName]); return (int) $stmt->fetchColumn() > 0; } function admin_table_columns(PDO $pdo, string $tableName): array { $stmt = $pdo->prepare( 'SELECT column_name FROM information_schema.columns WHERE table_schema = ? AND table_name = ?' ); $stmt->execute([DB_NAME, $tableName]); return $stmt->fetchAll(PDO::FETCH_COLUMN); } function admin_make_company_code(string $companyName, int $companyId): string { $normalized = strtoupper($companyName); $normalized = preg_replace('/[^A-Z0-9]+/', '-', $normalized ?? '') ?? ''; $normalized = trim($normalized, '-'); if ($normalized === '') { $normalized = 'FIRMA'; } return substr($normalized, 0, 40) . '-' . $companyId; } function admin_customer_schema_path(): string { // Canli ortamda path yapisi farkli olabilir. // Varsayilan olarak api subdomainindeki database.sql'e bakabiliriz. return dirname(__DIR__, 2) . '/api.serkansengun.com.tr/database.sql'; } function admin_load_customer_schema_sql(): string { $schemaPath = admin_customer_schema_path(); if (!is_file($schemaPath)) { // Yedek plan: yereldeki dosyayi referans alalım (eger ayni sunucudaysalar) $schemaPath = '/home/serkansenguncom/public_html/api.serkansengun.com.tr/database.sql'; } if (!is_file($schemaPath)) { throw new RuntimeException('Musteri veritabani sema dosyasi bulunamadi: ' . $schemaPath); } $sql = (string) file_get_contents($schemaPath); if (trim($sql) === '') { throw new RuntimeException('Musteri veritabani sema dosyasi bos.'); } $sql = preg_replace('/^\s*CREATE\s+DATABASE\s+IF\s+NOT\s+EXISTS\s+`[^`]+`.+?;\s*/imu', '', $sql) ?? $sql; $sql = preg_replace('/^\s*USE\s+`[^`]+`;\s*/imu', '', $sql) ?? $sql; $sql = preg_replace('/^\s*INSERT\s+IGNORE\s+INTO\s+`Users`\s+.+?;\s*/ims', '', $sql) ?? $sql; $sql = preg_replace('/^\s*INSERT\s+IGNORE\s+INTO\s+`Categories`\s+.+?;\s*/ims', '', $sql) ?? $sql; return trim($sql); } function admin_split_sql_statements(string $sql): array { $statements = []; $buffer = ''; $length = strlen($sql); $inSingleQuote = false; $inDoubleQuote = false; $inLineComment = false; $inBlockComment = false; for ($i = 0; $i < $length; $i++) { $char = $sql[$i]; $next = $i + 1 < $length ? $sql[$i + 1] : ''; if ($inLineComment) { if ($char === "\n") { $inLineComment = false; $buffer .= $char; } continue; } if ($inBlockComment) { if ($char === '*' && $next === '/') { $inBlockComment = false; $i++; } continue; } if (!$inSingleQuote && !$inDoubleQuote) { if ($char === '-' && $next === '-') { $afterNext = $i + 2 < $length ? $sql[$i + 2] : ''; if ($afterNext === ' ' || $afterNext === "\t" || $afterNext === "\r" || $afterNext === "\n") { $inLineComment = true; $i++; continue; } } if ($char === '#') { $inLineComment = true; continue; } if ($char === '/' && $next === '*') { $inBlockComment = true; $i++; continue; } } if ($char === "'" && !$inDoubleQuote) { $escaped = $i > 0 && $sql[$i - 1] === '\\'; if (!$escaped) { $inSingleQuote = !$inSingleQuote; } } elseif ($char === '"' && !$inSingleQuote) { $escaped = $i > 0 && $sql[$i - 1] === '\\'; if (!$escaped) { $inDoubleQuote = !$inDoubleQuote; } } if ($char === ';' && !$inSingleQuote && !$inDoubleQuote) { $statement = trim($buffer); if ($statement !== '') { $statements[] = $statement; } $buffer = ''; continue; } $buffer .= $char; } $statement = trim($buffer); if ($statement !== '') { $statements[] = $statement; } return $statements; } function admin_provision_company_database(string $dbHost, int $dbPort, string $dbName, string $dbUser, string $dbPass): void { $dbName = trim($dbName); if ($dbName === '') { throw new RuntimeException('Veritabani adi bos olamaz.'); } if (!preg_match('/^[A-Za-z0-9_]+$/', $dbName)) { throw new RuntimeException('Veritabani adinda sadece harf, rakam ve alt cizgi kullanilabilir.'); } $port = (string) $dbPort; $errors = []; $escapedDbName = str_replace('`', '``', $dbName); $statements = admin_split_sql_statements(admin_load_customer_schema_sql()); foreach (admin_company_connection_hosts($dbHost) as $host) { try { $serverPdo = admin_create_pdo_connection($host, $port, null, $dbUser, $dbPass); $serverPdo->exec('CREATE DATABASE IF NOT EXISTS `' . $escapedDbName . '` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci'); $databasePdo = admin_create_pdo_connection($host, $port, $dbName, $dbUser, $dbPass); foreach ($statements as $statement) { $databasePdo->exec($statement); } return; } catch (Throwable $exception) { $errors[] = $host . ': ' . $exception->getMessage(); } } if ($errors !== []) { throw new RuntimeException('Denenen hostlar: ' . implode(' | ', $errors)); } } function admin_company_database_name(array $companyRow): string { $dbName = trim((string) ($companyRow['database_name'] ?? $companyRow['db_name'] ?? '')); if ($dbName === '') { return ''; } // cPanel prefix (serkansenguncom_) ekle $prefix = 'serkansenguncom_'; if (strpos($dbName, $prefix) !== 0) { $dbName = $prefix . $dbName; } return $dbName; } function admin_company_pdo(array $companyRow): PDO { $databaseName = admin_company_database_name($companyRow); $dbUser = trim((string) ($companyRow['db_user'] ?? 'serkansenguncom_btuser')); $dbPass = (string) ($companyRow['db_pass'] ?? 'I&goOS~B,UCz'); $dbPort = (string) ((int) ($companyRow['db_port'] ?? 3306)); $errors = []; if ($databaseName === '') { throw new RuntimeException('Firma veritabani adi tanimli degil.'); } foreach (admin_company_connection_hosts((string) ($companyRow['db_host'] ?? 'localhost')) as $host) { try { return admin_create_pdo_connection($host, $dbPort, $databaseName, $dbUser, $dbPass); } catch (Throwable $exception) { $errors[] = $host . ': ' . $exception->getMessage(); } } throw new RuntimeException('Firma veritabanina baglanilamadi. Denenen hostlar: ' . implode(' | ', $errors)); } function admin_current_db_table_columns(PDO $pdo, string $tableName): array { $stmt = $pdo->prepare( 'SELECT COLUMN_NAME FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ?' ); $stmt->execute([$tableName]); return $stmt->fetchAll(PDO::FETCH_COLUMN); } function admin_current_db_has_table(PDO $pdo, string $tableName): bool { $stmt = $pdo->prepare( 'SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ?' ); $stmt->execute([$tableName]); return (int) $stmt->fetchColumn() > 0; } function admin_ensure_company_roles(PDO $companyPdo): void { if (!admin_current_db_has_table($companyPdo, 'UserRoles')) { return; } $columns = admin_current_db_table_columns($companyPdo, 'UserRoles'); if (!in_array('Id', $columns, true) || !in_array('RoleName', $columns, true)) { return; } $companyPdo->exec("INSERT IGNORE INTO UserRoles (Id, RoleName) VALUES (1, 'Admin'), (2, 'User')"); } function admin_sync_company_user_to_customer_db(array $companyRow, array $companyUser): void { $companyPdo = admin_company_pdo($companyRow); if (!admin_current_db_has_table($companyPdo, 'Users')) { throw new RuntimeException('Musteri veritabaninda Users tablosu bulunamadi.'); } admin_ensure_company_roles($companyPdo); $userColumns = admin_current_db_table_columns($companyPdo, 'Users'); $lookupParts = []; $lookupParams = []; if (in_array('Email', $userColumns, true)) { $lookupParts[] = 'Email = ?'; $lookupParams[] = $companyUser['email']; } if (in_array('Username', $userColumns, true)) { $lookupParts[] = 'Username = ?'; $lookupParams[] = $companyUser['username']; } if ($lookupParts === []) { throw new RuntimeException('Users tablosunda Email veya Username alani yok.'); } $stmt = $companyPdo->prepare('SELECT Id FROM Users WHERE ' . implode(' OR ', $lookupParts) . ' LIMIT 1'); $stmt->execute($lookupParams); $localUser = $stmt->fetch(); $updateFields = []; $updateParams = []; if (in_array('Username', $userColumns, true)) { $updateFields[] = 'Username = ?'; $updateParams[] = $companyUser['username']; } if (in_array('Email', $userColumns, true)) { $updateFields[] = 'Email = ?'; $updateParams[] = $companyUser['email']; } if (in_array('PasswordHash', $userColumns, true)) { $updateFields[] = 'PasswordHash = ?'; $updateParams[] = $companyUser['password_hash']; } if (in_array('FullName', $userColumns, true)) { $updateFields[] = 'FullName = ?'; $updateParams[] = $companyUser['full_name']; } if (in_array('RoleId', $userColumns, true)) { $updateFields[] = 'RoleId = ?'; $updateParams[] = (int) ($companyUser['role_id'] ?? 2); } if (in_array('IsActive', $userColumns, true)) { $updateFields[] = 'IsActive = ?'; $updateParams[] = (int) ($companyUser['is_active'] ?? 1); } if ($localUser) { $updateParams[] = $localUser['Id']; $stmt = $companyPdo->prepare('UPDATE Users SET ' . implode(', ', $updateFields) . ' WHERE Id = ?'); $stmt->execute($updateParams); } else { $placeholders = implode(', ', array_fill(0, count($updateFields), '?')); $columnsCsv = implode(', ', array_map(fn($f) => str_replace(' = ?', '', $f), $updateFields)); $stmt = $companyPdo->prepare("INSERT INTO Users ($columnsCsv) VALUES ($placeholders)"); $stmt->execute($updateParams); } } function admin_get_mail_templates(): array { return [ 'activation' => [ 'name' => 'Kullanici Aktivasyon Maili', 'subject' => 'Hesabiniz Olusturuldu', 'preview_vars' => [ 'full_name' => 'Ahmet Yilmaz', 'username' => 'ahmetyilmaz', 'password' => '12345678', 'login_url' => 'http://app.serkansengun.com.tr', ] ], 'welcome' => [ 'name' => 'Hos Geldiniz (Deneme Baslangici)', 'subject' => 'FinansTakip\'e Hos Geldiniz', 'preview_vars' => [ 'full_name' => 'Mehmet Demir', 'company_name' => 'Demir Teknoloji Ltd.', 'expiry_date' => '24.05.2026', ] ], 'test' => [ 'name' => 'SMTP Test Maili', 'subject' => 'Sistem Test Mesaji', 'preview_vars' => [ 'time' => date('H:i:s'), ] ], ]; }