Files
votervue/classes/imports/Import_RecreationalLicenses.php
T
2026-07-03 15:46:56 -04:00

348 lines
12 KiB
PHP

<?php
// ===============================
// FWC Recreational License Import
// ===============================
$options = getopt("", [
"base-dir::",
"recreational-dir::",
"full-reload::",
]);
$baseDir = normalizePath($options["base-dir"] ?? (__DIR__ . "/../../data/fwcdata"));
$currentDir = normalizePath($options["recreational-dir"] ?? ($baseDir . "/licenses/current/recreational"));
$fullReload = true; // true = truncate license table before importing all current files
if (isset($options["full-reload"])) {
$fullReload = filter_var($options["full-reload"], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? true;
}
try {
$databaseConfig = getDatabaseConfig();
$dbHost = $databaseConfig["host"];
$dbName = $databaseConfig["name"];
$dbUser = $databaseConfig["user"];
$dbPass = $databaseConfig["pass"];
$pdo = new PDO("mysql:host={$dbHost};dbname={$dbName};charset=utf8mb4", $dbUser, $dbPass, [PDO::ATTR_ERRMODE => 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);
}