PDO::ERRMODE_EXCEPTION, PDO::MYSQL_ATTR_LOCAL_INFILE => true]); $files = glob($currentDir . DIRECTORY_SEPARATOR . "*.txt"); if (empty($files)) { throw new Exception("No .txt files found in {$currentDir}"); } $importBatchCode = createUuid(); echo "Import batch: {$importBatchCode}" . PHP_EOL; if ($fullReload) { echo "Truncating fwc_recreational_licenses..." . PHP_EOL; $pdo->exec("TRUNCATE TABLE fwc_recreational_licenses"); } $hasFailures = false; foreach ($files as $filePath) { $sourceFile = basename($filePath); $fileSize = filesize($filePath); echo "Importing {$sourceFile}..." . PHP_EOL; $importSerial = createImportRecord($pdo, $importBatchCode, $sourceFile, $filePath, $fileSize); try { updateImportStatus($pdo, $importSerial, "running"); $beforeCount = getLicenseCount($pdo); $loadedRows = loadFile($pdo, $filePath, $sourceFile, $importBatchCode); purgeOutOfStateRecords($pdo); $afterCount = getLicenseCount($pdo); $recordsImported = max(0, $afterCount - $beforeCount); completeImport($pdo, $importSerial, $recordsImported); echo "Loaded {$loadedRows} rows; retained {$recordsImported} Florida records from {$sourceFile}" . PHP_EOL; } catch (Exception $e) { $hasFailures = true; failImport($pdo, $importSerial, $e->getMessage()); echo "ERROR importing {$sourceFile}: " . $e->getMessage() . PHP_EOL; } } if ($hasFailures) { sendWebHookAlert("FWC recreational license reload completed with errors.", "Script Alert", "ff9900"); echo "Import completed with errors." . PHP_EOL; exit(1); } echo "Import complete." . PHP_EOL; sendWebHookAlert("FWC recreational license reload successful."); } catch (Exception $e) { echo "Import failed: " . $e->getMessage() . PHP_EOL; sendWebHookAlert("FWC recreational license reload failed: " . $e->getMessage(), "Script Alert", "ff0000"); exit(1); } // ======================= // Load File into MySQL // ======================= function loadFile(PDO $pdo, string $filePath, string $sourceFile, string $importBatchCode): int { $filePathSql = $pdo->quote($filePath); $sourceFileSql = $pdo->quote($sourceFile); $importBatchCodeSql = $pdo->quote($importBatchCode); $sql = "LOAD DATA LOCAL INFILE {$filePathSql} INTO TABLE fwc_recreational_licenses CHARACTER SET utf8mb4 FIELDS TERMINATED BY '\\t' LINES TERMINATED BY '\\n' IGNORE 1 LINES ( @lastName, @firstName, @middleName, @street1, @city, @state, @zipCode, @phoneNumber, @emailAddress, @Gender, @Ethnicity, @LicenseType, @LicenseExpireDate, @LicenseStartDate, @AgeAtTimeOfRun, @County ) SET license_source_file = {$sourceFileSql}, license_last_name = TRIM(@lastName), license_first_name = TRIM(@firstName), license_middle_name = TRIM(@middleName), license_street1 = TRIM(@street1), license_city = TRIM(@city), license_state = TRIM(@state), license_zip_code = TRIM(@zipCode), license_phone_number = TRIM(@phoneNumber), license_email_address = TRIM(@emailAddress), license_gender = TRIM(@Gender), license_ethnicity = TRIM(@Ethnicity), license_type = TRIM(@LicenseType), license_expire_date = COALESCE(STR_TO_DATE(NULLIF(TRIM(@LicenseExpireDate), ''), '%Y-%m-%d %H:%i:%s'), STR_TO_DATE(NULLIF(TRIM(@LicenseExpireDate), ''), '%Y-%m-%d')), license_start_date = COALESCE(STR_TO_DATE(NULLIF(TRIM(@LicenseStartDate), ''), '%Y-%m-%d %H:%i:%s'), STR_TO_DATE(NULLIF(TRIM(@LicenseStartDate), ''), '%Y-%m-%d')), license_age_at_time_of_run = CASE WHEN TRIM(@AgeAtTimeOfRun) REGEXP '^[0-9]+$' THEN TRIM(@AgeAtTimeOfRun) ELSE NULL END, license_county = TRIM(REPLACE(@County, '\\r', '')), import_batch_code = {$importBatchCodeSql}"; return (int) $pdo->exec($sql); } // =============== // Import Tracking // =============== function createImportRecord(PDO $pdo, string $batchCode, string $sourceFile, string $filePath, int $fileSize): int { $sql = " INSERT INTO fwc_recreational_license_imports ( import_batch_code, source_file, file_path, file_size, import_status, started_at ) VALUES ( :import_batch_code, :source_file, :file_path, :file_size, 'queued', NOW())"; $statement = $pdo->prepare($sql); $statement->execute([":import_batch_code" => $batchCode, ":source_file" => $sourceFile, ":file_path" => $filePath, ":file_size" => $fileSize]); return (int) $pdo->lastInsertId(); } function updateImportStatus(PDO $pdo, int $importSerial, string $status): void { $sql = "UPDATE fwc_recreational_license_imports SET import_status = :status WHERE import_serial = :import_serial"; $statement = $pdo->prepare($sql); $statement->execute([":status" => $status, ":import_serial" => $importSerial]); } function completeImport(PDO $pdo, int $importSerial, int $recordsImported): void { $sql = "UPDATE fwc_recreational_license_imports SET import_status = 'complete', records_imported = :records_imported, completed_at = NOW() WHERE import_serial = :import_serial"; $statement = $pdo->prepare($sql); $statement->execute([":records_imported" => $recordsImported, ":import_serial" => $importSerial]); } function failImport(PDO $pdo, int $importSerial, string $message): void { $sql = "UPDATE fwc_recreational_license_imports SET import_status = 'failed', error_message = :error_message, completed_at = NOW() WHERE import_serial = :import_serial"; $statement = $pdo->prepare($sql); $statement->execute([":error_message" => $message, ":import_serial" => $importSerial]); } function purgeOutOfStateRecords(PDO $pdo): void { $sql = "delete from fwc_recreational_licenses where license_state != 'FL'"; $statement = $pdo->prepare($sql); $statement->execute(); } // ======= // Helpers // ======= function getLicenseCount(PDO $pdo): int { return (int) $pdo->query("SELECT COUNT(*) FROM fwc_recreational_licenses")->fetchColumn(); } function createUuid(): string { $data = random_bytes(16); $data[6] = chr(ord($data[6]) & 0x0f | 0x40); $data[8] = chr(ord($data[8]) & 0x3f | 0x80); return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); } function normalizePath(string $path): string { $realPath = realpath($path); return $realPath === false ? rtrim($path, DIRECTORY_SEPARATOR) : $realPath; } function getDatabaseConfig(): array { $config = [ "host" => getenv("FWC_DB_HOST") ?: "", "name" => getenv("FWC_DB_NAME") ?: "", "user" => getenv("FWC_DB_USER") ?: "", "pass" => getenv("FWC_DB_PASS") ?: "", ]; if ($config["host"] !== "" && $config["name"] !== "" && $config["user"] !== "" && $config["pass"] !== "") { return $config; } $settingsPath = __DIR__ . "/../../xml/settings.xml"; if (!file_exists($settingsPath)) { throw new Exception("Database settings not found. Set FWC_DB_HOST, FWC_DB_NAME, FWC_DB_USER, and FWC_DB_PASS or create {$settingsPath}"); } $settings = new SimpleXMLElement(file_get_contents($settingsPath)); $config = [ "host" => trim((string) $settings->DatabaseServer), "name" => trim((string) $settings->DatabaseName), "user" => trim((string) $settings->DatabaseUser), "pass" => trim((string) $settings->DatabasePassword), ]; foreach ($config as $key => $value) { if ($value === "") { throw new Exception("Database setting {$key} is missing in {$settingsPath}"); } } return $config; } function sendWebHookAlert(string $message, string $title = "Script Alert", string $alerttype = "1fff00"): void { $webhookUrl = getenv("FWC_WEBHOOK_URL") ?: ""; if ($webhookUrl === "") { $settingsPath = __DIR__ . "/../../xml/settings.xml"; if (file_exists($settingsPath)) { $settings = new SimpleXMLElement(file_get_contents($settingsPath)); $webhookUrl = trim((string) $settings->DiscordWebHookURL); } } if (trim($webhookUrl) === "") { echo "Webhook alert skipped: FWC_WEBHOOK_URL is not set." . PHP_EOL; return; } if (!function_exists("curl_init")) { echo "Webhook alert skipped: PHP curl extension is not available." . PHP_EOL; return; } $data = [ 'embeds' => [[ 'title' => $title, 'description' => $message, 'color' => hexdec($alerttype), 'footer' => [ 'text' => 'OSITS System Notification', ], 'timestamp' => date('c'), ]] ]; $jsonData = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); $ch = curl_init($webhookUrl); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); $response = curl_exec($ch); if ($response === false) { echo "Webhook alert failed: " . curl_error($ch) . PHP_EOL; } curl_close($ch); }