first commit

This commit is contained in:
2026-07-03 15:46:56 -04:00
commit bf8532aa1e
1545 changed files with 450330 additions and 0 deletions
@@ -0,0 +1,389 @@
<?php
// ===============================
// FWC Commercial License Import
// ===============================
$options = getopt("", [
"base-dir::",
"commercial-dir::",
"full-reload::",
]);
$baseDir = normalizePath($options["base-dir"] ?? (__DIR__ . "/../../data/fwcdata"));
$currentDir = normalizePath($options["commercial-dir"] ?? ($baseDir . "/licenses/current/commercial"));
$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_commercial_licenses..." . PHP_EOL;
$pdo->exec("TRUNCATE TABLE fwc_commercial_licenses");
}
$hasFailures = false;
foreach ($files as $filePath) {
$sourceFile = basename($filePath);
$fileSize = filesize($filePath);
echo "Importing {$sourceFile}..." . PHP_EOL;
$licenseCode = getLicenseCode($sourceFile);
$importSerial = createImportRecord($pdo, $importBatchCode, $sourceFile, $licenseCode, $filePath, $fileSize);
try {
updateImportStatus($pdo, $importSerial, "running");
$beforeCount = getLicenseCount($pdo);
$loadedRows = loadFile($pdo, $filePath, $sourceFile, $importBatchCode);
$afterCount = getLicenseCount($pdo);
$recordsImported = max(0, $afterCount - $beforeCount);
completeImport($pdo, $importSerial, $recordsImported);
echo "Loaded {$loadedRows} rows 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 commercial license reload completed with errors.", "Script Alert", "ff9900");
echo "Import completed with errors." . PHP_EOL;
exit(1);
}
echo "Import complete." . PHP_EOL;
sendWebHookAlert("FWC commercial license reload successful.");
} catch (Exception $e) {
echo "Import failed: " . $e->getMessage() . PHP_EOL;
sendWebHookAlert("FWC commercial 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);
$licenseCodeSql = $pdo->quote(getLicenseCode($sourceFile));
$importBatchCodeSql = $pdo->quote($importBatchCode);
$sql = "LOAD DATA LOCAL INFILE {$filePathSql}
INTO TABLE fwc_commercial_licenses
CHARACTER SET utf8mb4
FIELDS TERMINATED BY '\\t'
LINES TERMINATED BY '\\n'
IGNORE 1 LINES
( @LicenseNumber,
@ApplicantId,
@FloridaResidency,
@IssuedTo,
@LicenseScope,
@LicenseBeginDate,
@LicenseExpireDate,
@CompanyOrLastName,
@FirstName,
@MiddleInit,
@Suffix,
@EmailAddress,
@County,
@MailStreet1,
@MailStreet2,
@MailCity,
@MailState,
@MailZipCode,
@MailZip4,
@ApplicantPhone,
@EndorsementNumber,
@EndorsementExpirationDate,
@TotalTagQty )
SET
commercial_license_source_file = {$sourceFileSql},
commercial_license_code = {$licenseCodeSql},
commercial_license_number = TRIM(@LicenseNumber),
commercial_license_applicant_id = CASE
WHEN TRIM(@ApplicantId) REGEXP '^[0-9]+$' THEN TRIM(@ApplicantId)
ELSE NULL
END,
commercial_license_florida_residency = TRIM(@FloridaResidency),
commercial_license_issued_to = TRIM(@IssuedTo),
commercial_license_scope = TRIM(@LicenseScope),
commercial_license_begin_date = COALESCE(STR_TO_DATE(NULLIF(TRIM(@LicenseBeginDate), ''), '%Y-%m-%d %H:%i:%s'),
STR_TO_DATE(NULLIF(TRIM(@LicenseBeginDate), ''), '%Y-%m-%d')),
commercial_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')),
commercial_license_company_or_last_name = TRIM(@CompanyOrLastName),
commercial_license_first_name = TRIM(@FirstName),
commercial_license_middle_init = TRIM(@MiddleInit),
commercial_license_suffix = TRIM(@Suffix),
commercial_license_email_address = TRIM(@EmailAddress),
commercial_license_county = TRIM(@County),
commercial_license_mail_street1 = TRIM(@MailStreet1),
commercial_license_mail_street2 = TRIM(@MailStreet2),
commercial_license_mail_city = TRIM(@MailCity),
commercial_license_mail_state = TRIM(@MailState),
commercial_license_mail_zip_code = TRIM(@MailZipCode),
commercial_license_mail_zip4 = TRIM(@MailZip4),
commercial_license_applicant_phone = TRIM(@ApplicantPhone),
commercial_license_endorsement_number = TRIM(@EndorsementNumber),
commercial_license_endorsement_expiration_date = COALESCE(STR_TO_DATE(NULLIF(TRIM(@EndorsementExpirationDate), ''), '%Y-%m-%d %H:%i:%s'),
STR_TO_DATE(NULLIF(TRIM(@EndorsementExpirationDate), ''), '%Y-%m-%d')),
commercial_license_total_tag_qty = CASE
WHEN TRIM(REPLACE(@TotalTagQty, '\\r', '')) REGEXP '^[0-9]+$' THEN TRIM(REPLACE(@TotalTagQty, '\\r', ''))
ELSE NULL
END,
commercial_license_import_batch_code = {$importBatchCodeSql}";
return (int) $pdo->exec($sql);
}
// ===============
// Import Tracking
// ===============
function createImportRecord(PDO $pdo, string $batchCode, string $sourceFile, string $licenseCode, string $filePath, int $fileSize): int {
$sql = " INSERT INTO fwc_commercial_license_imports (
import_batch_code,
source_file,
license_code,
file_path,
file_size,
import_status,
started_at )
VALUES ( :import_batch_code,
:source_file,
:license_code,
:file_path,
:file_size,
'queued',
NOW())";
$statement = $pdo->prepare($sql);
$statement->execute([":import_batch_code" => $batchCode, ":source_file" => $sourceFile, ":license_code" => $licenseCode, ":file_path" => $filePath, ":file_size" => $fileSize]);
return (int) $pdo->lastInsertId();
}
function updateImportStatus(PDO $pdo, int $importSerial, string $status): void {
$sql = "UPDATE fwc_commercial_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_commercial_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_commercial_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]);
}
// =======
// Helpers
// =======
function getLicenseCount(PDO $pdo): int {
return (int) $pdo->query("SELECT COUNT(*) FROM fwc_commercial_licenses")->fetchColumn();
}
function getLicenseCode(string $sourceFile): string {
$fileName = strtolower(pathinfo($sourceFile, PATHINFO_FILENAME));
$licenseCodePatterns = [
"closedseasonspinylobsterplaneandvessel" => "CSV",
"closedseasonspinylobster" => "CS",
"depredationpermit" => "DP",
"freshwatercommercialfishing" => "FCL",
"nonresidentfreshwaterretaildealers" => "FRD",
"nonresidentfreshwaterwholesalebuyers" => "FWB",
"nonresidentfreshwaterwholesaledealers" => "FWD",
"stjohnsriverliveshrimp" => "LS",
"retailcentral" => "RC",
"residentfreshwaterfishandfrogdealers" => "RFD",
"retailother" => "RO",
"sardinelikefish" => "SL",
"saltwaterproducts" => "SP",
"wholesaledealer" => "WD",
];
foreach ($licenseCodePatterns as $pattern => $licenseCode) {
if (strpos($fileName, $pattern) !== false) {
return $licenseCode;
}
}
return strtoupper(substr($fileName, 0, 10));
}
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);
}
@@ -0,0 +1,347 @@
<?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);
}
+226
View File
@@ -0,0 +1,226 @@
<?php
// ====================
// Import Voter History
// ====================
class Import_VoterHistory {
// Variables
private $log = "";
private $settings = "";
private $connection = "";
// ==================
// Class Constructor.
// ==================
public function __construct() {
// Open the Log
$this->log = fopen("../scripts/import_voter_history.log", "w+");
if ($this->log === false) {
error_log("VoterVue : Unable to open log file");
exit();
}
$this->log(str_repeat("*", 50));
// Get Settings
$this->settings = new SimpleXMLElement((file_exists("../xml/settings.xml") ? file_get_contents("../xml/settings.xml") : "<settings/>"));
if ($this->settings->count() === 0) {
error_log("VoterVue : Unable to initialize settings");
exit();
}
// Open Database Connections
$this->connection = $this->connect();
}
// ==============================================
// Return a VoterVue Database Database Connection
// ==============================================
public function connect() {
try {
$host = (string) $this->settings->DatabaseServer;
$database = (string) $this->settings->DatabaseName;
$user = (string) $this->settings->DatabaseUser;
$password = (string) $this->settings->DatabasePassword;
$connection = new PDO("mysql:host={$host};dbname={$database};charset=utf8", $user, $password);
$connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException | Exception $e) {
trigger_error($e->getMessage());
}
return $connection;
}
// =============================
// Log a message to the Log File
// =============================
private function log($message = "") {
fwrite($this->log, date("Y-m-d H:i A") . " : " . "{$message} \n");
}
// ===================
// Process Parcel Data
// ===================
public function process() {
$this->log("Starting Voter History Import Process");
$this->importVoterHistory();
$this->log("Voter History Import Process Completed");
}
// ====================
// Import Voter History
// ====================
private function importVoterHistory() {
$this->log("Processing Voter History Records");
$path = '/data/ositssrc/projects/votervue_raw_data/20251210_Voter_History_20251210';
$files = glob("{$path}/*.txt");
sort($files);
$SQL = "INSERT IGNORE INTO voterhistory
(
voterhistory_county_code,
voterhistory_voter_id,
voterhistory_election_date,
voterhistory_election_type,
voterhistory_history_code
)
VALUES ( ?, ?, ?, ?, ? )";
$statement = $this->connection->prepare($SQL);
$counter = 0;
$batchCounter = 0;
$batchSize = 50000;
foreach ($files as $file) {
$this->log("Importing history file: {$file}");
$fh = fopen($file, 'r');
if ($fh === false) {
$this->log("Unable to open {$file}");
continue;
}
$lineNum = 0;
try {
// Start transaction for this file
$this->connection->beginTransaction();
while (($line = fgets($fh)) !== false) {
$lineNum++;
$line = rtrim($line, "\r\n");
if ($line === '') {
continue;
}
$cols = explode("\t", $line);
if (count($cols) < 5) {
$this->log("Skipped malformed line {$lineNum} in {$file} (cols=" . count($cols) . ")");
continue;
}
[$county_code, $voter_id, $election_date, $election_type, $history_code] = $cols;
$statement->execute([
$this->normalize($county_code),
$this->normalize($voter_id),
$this->parseDate($election_date),
$this->normalize($election_type),
$this->normalize($history_code)
]);
$counter++;
$batchCounter++;
// Commit every batchSize rows
if ($batchCounter >= $batchSize) {
$this->connection->commit();
$this->connection->beginTransaction();
$batchCounter = 0;
$this->log("Processed {$counter} history records...");
}
}
// Final commit for this file
$this->connection->commit();
$batchCounter = 0;
} catch (Throwable $e) {
// Roll back any open transaction for this file
if ($this->connection->inTransaction()) {
$this->connection->rollBack();
}
$this->log("Insert failed {$file}:{$lineNum} - " . $e->getMessage());
}
fclose($fh);
}
$this->log("Completed voter history import. Total records processed: {$counter}");
}
// =================================
// Return a $this->normalized String
// =================================
private function normalize(?string $value): ?string {
$value = trim((string) $value);
return ($value === '' || $value === '*') ? null : $value;
}
// ====================
// Return a Parsed Date
// ====================
private function parseDate(?string $value): ?string {
$value = $this->normalize($value);
if ($value === null) {
return null;
}
$dt = DateTime::createFromFormat('m/d/Y', $value);
return $dt ? $dt->format('Y-m-d') : null;
}
}
?>
+277
View File
@@ -0,0 +1,277 @@
<?php
// ====================
// Import Voter Records
// ====================
class Import_VoterRecords {
// Variables
private $log = "";
private $settings = "";
private $connection = "";
// ==================
// Class Constructor.
// ==================
public function __construct() {
// Open the Log
$this->log = fopen("../scripts/import_voter_records.log", "w+");
if ($this->log === false) {
error_log("VoterVue : Unable to open log file");
exit();
}
$this->log(str_repeat("*", 50));
// Get Settings
$this->settings = new SimpleXMLElement((file_exists("../xml/settings.xml") ? file_get_contents("../xml/settings.xml") : "<settings/>"));
if ($this->settings->count() === 0) {
error_log("VoterVue : Unable to initialize settings");
exit();
}
// Open Database Connections
$this->connection = $this->connect();
}
// ================================================
// Return a New DEC-DR Database Database Connection
// ================================================
public function connect() {
try {
$host = (string) $this->settings->DatabaseServer;
$database = (string) $this->settings->DatabaseName;
$user = (string) $this->settings->DatabaseUser;
$password = (string) $this->settings->DatabasePassword;
$connection = new PDO("mysql:host={$host};dbname={$database};charset=utf8", $user, $password);
$connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException | Exception $e) {
trigger_error($e->getMessage());
}
return $connection;
}
// =============================
// Log a message to the Log File
// =============================
private function log($message = "") {
fwrite($this->log, date("Y-m-d H:i A") . " : " . "{$message} \n");
}
// ===================
// Process Parcel Data
// ===================
public function process() {
$this->log("Starting Voter Record Import Process");
$this->importVoterRecords();
$this->log("Voter Record Import Process Complete");
}
// ====================
// Import Voter Records
// ====================
private function importVoterRecords() {
$this->log("Processing Voter Records");
$voter_record_file_path = '/data/ositssrc/projects/votervue_raw_data/20251210_Voter_Detail_20251210';
$voter_history_files = glob("{$voter_record_file_path}/*.txt");
sort($voter_history_files);
$SQL = "INSERT INTO voters
( voter_county_code,
voter_id,
voter_name_last,
voter_name_suffix,
voter_name_first,
voter_name_middle,
voter_public_records_exempt,
voter_residence_address_1,
voter_residence_address_2,
voter_residence_city,
voter_residence_state,
voter_residence_zipcode,
voter_mailing_address_1,
voter_mailing_address_2,
voter_mailing_address_3,
voter_mailing_city,
voter_mailing_state,
voter_mailing_zipcode,
voter_mailing_country,
voter_gender,
voter_race,
voter_birth_date,
voter_registration_date,
voter_party_affiliation,
voter_precinct,
voter_precinct_group,
voter_precinct_split,
voter_precinct_suffix,
voter_voter_status,
voter_congressional_district,
voter_house_district,
voter_senate_district,
voter_county_commission_district,
voter_school_board_district,
voter_daytime_area_code,
voter_daytime_phone_number,
voter_daytime_phone_extension,
voter_email_address )
VALUES
( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ? )";
$statement = $this->connection->prepare($SQL);
$counter = 0;
foreach ($voter_history_files as $file) {
$this->log("Importing file: {$file}");
$voterhistory = fopen($file, 'r');
if ($voterhistory === false) {
$this->log("Unable to open {$file}");
echo "❌ Unable to open {$file}\n";
continue;
}
$lineNum = 0;
while (($line = fgets($voterhistory)) !== false) {
$lineNum++;
$line = rtrim($line, "\r\n");
if ($line === '') {
continue;
}
$cols = explode("\t", $line);
if (count($cols) < 38) {
$this->log("Skipped malformed line {$lineNum} in {$file} (cols=" . count($cols) . ")");
continue;
}
[$county_code, $voter_id, $name_last, $name_suffix, $name_first, $name_middle, $public_exempt, $res_addr1, $res_addr2, $res_city, $res_state, $res_zip,
$mail_addr1, $mail_addr2, $mail_addr3, $mail_city, $mail_state, $mail_zip, $mail_country, $gender, $race, $birth_date, $reg_date, $party, $precinct,
$precinct_group, $precinct_split, $precinct_suffix, $status, $cd, $hd, $sd, $ccd, $sbd, $area, $phone, $ext, $email] = $cols;
try {
$statement->execute([
$this->normalize($county_code),
$this->normalize($voter_id),
$this->normalize($name_last),
$this->normalize($name_suffix),
$this->normalize($name_first),
$this->normalize($name_middle),
$this->normalize($public_exempt),
$this->normalize($res_addr1),
$this->normalize($res_addr2),
$this->normalize($res_city),
$this->normalize($res_state),
$this->normalize($res_zip),
$this->normalize($mail_addr1),
$this->normalize($mail_addr2),
$this->normalize($mail_addr3),
$this->normalize($mail_city),
$this->normalize($mail_state),
$this->normalize($mail_zip),
$this->normalize($mail_country),
$this->normalize($gender),
$this->normalize($race),
$this->parseDate($birth_date),
$this->parseDate($reg_date),
$this->normalize($party),
$this->normalize($precinct),
$this->normalize($precinct_group),
$this->normalize($precinct_split),
$this->normalize($precinct_suffix),
$this->normalize($status),
$this->normalize($cd),
$this->normalize($hd),
$this->normalize($sd),
$this->normalize($ccd),
$this->normalize($sbd),
$this->normalize($area),
$this->normalize($phone),
$this->normalize($ext),
$this->normalize($email)
]);
$counter++;
if (($counter % 10) === 0) {
$this->log("Processed {$counter} records...");
}
} catch (Throwable $e) {
$this->log("Insert failed {$file}:{$lineNum} - " . $e->getMessage());
echo "❌ Insert failed {$file}:{$lineNum}{$e->getMessage()}\n";
}
}
fclose($voterhistory);
}
$this->log("Completed voter import. Total records processed: {$counter}");
}
// =================================
// Return a $this->normalized String
// =================================
private function normalize(?string $value): ?string {
$value = trim((string) $value);
return ($value === '' || $value === '*') ? null : $value;
}
// ====================
// Return a Parsed Date
// ====================
private function parseDate(?string $value): ?string {
$value = $this->normalize($value);
if ($value === null) {
return null;
}
$dt = DateTime::createFromFormat('m/d/Y', $value);
return $dt ? $dt->format('Y-m-d') : null;
}
}
?>