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); }