output_type = $output_type; } // ================ // Print the Report // ================ public function print($pzfacts, $subscription, $exportjob_base_directory, $exportjob_file_name) { $report = new PzFacts_Print(); $exportjob_base_directory = str_replace('../exports/', './exports/', $exportjob_base_directory); $report->subscription_serial = $subscription[0]['subscription_serial']; $report->subscription_full_title = $subscription[0]['subscription_full_title']; $report->exportjob_file_name = $exportjob_file_name; $report->exportjob_base_directory = $exportjob_base_directory; $report->pzfacts_array = $pzfacts; $report->output_type = $this->output_type; $report->render(); return $exportjob_file_name; } } // ========================== // Class to Generate the .pdf // ========================== class PZFacts_Print extends TCPDF { // Variables public $subscription_serial = 0; public $pzfacts_array = array(); public $subscription_full_title = array(); public $exportjob_file_name = ""; public $exportjob_base_directory = ""; public $report_header_title = ""; public $first_page = true; public $output_type = "normal"; // ================= // Render the Report // ================= public function render() { // Set report defaults $this->report_header_title = $this->subscription_full_title; $this->title = $this->report_header_title; $this->SetFont("Calibri"); $this->SetMargins(2, 26, 10); $this->SetHeaderMargin(10); $this->SetFooterMargin(10); $this->setCellPaddings(0, 0, 0, 0); $this->setCellMargins(2, 0, 0, 0); $card = function (float $x, float $y, float $w, float $h, array $d) { // Card background + border $this->SetDrawColor(210, 210, 210); $this->SetFillColor(250, 250, 250); $this->Rect($x, $y, $w, $h, 'DF'); // Header bar $headerH = 10; $this->SetFillColor(30, 93, 168); // Bootstrap-ish primary $this->Rect($x, $y, $w, $headerH, 'F'); // Header text $this->SetTextColor(255, 255, 255); $this->SetFont('helvetica', 'B', 11); $this->SetXY($x + 3, $y + 2.5); $this->Cell($w - 6, 0, "Project Name: {$d['pzfact_project_name']}", 0, 0, 'L', false); // Body $this->SetTextColor(20, 20, 20); $this->SetFont('helvetica', '', 9); $padX = 3; $padY = 3; $curY = $y + $headerH + $padY; $bodyX = $x + $padX; $bodyW = $w - ($padX * 2); $colGap = 6; $colW = ($bodyW - $colGap) / 2; $leftX = $bodyX; $rightX = $bodyX + $colW + $colGap; $labelW = 38; $row2 = function (string $l1, string $v1, string $l2, string $v2) use (&$curY, $leftX, $rightX, $colW, $labelW) { $yStart = $curY; // LEFT cell $this->SetXY($leftX, $yStart); $this->SetFont('helvetica', 'B', 9); $this->Cell($labelW, 0, $l1, 0, 0, 'L'); $this->Cell(2, 0, '', 0, 0); // spacer $this->SetFont('helvetica', '', 9); $this->MultiCell($colW - $labelW, 0, $v1, 0, 'L', false, 1); $yAfterLeft = $this->GetY(); // RIGHT cell (reset to same starting Y) $this->SetXY($rightX, $yStart); $this->SetFont('helvetica', 'B', 9); $this->Cell($labelW, 0, $l2, 0, 0, 'L'); $this->SetFont('helvetica', '', 9); $this->MultiCell($colW - $labelW, 0, $v2, 0, 'L', false, 1); $yAfterRight = $this->GetY(); // Advance cursor to max height used $curY = max($yAfterLeft, $yAfterRight) + 1; }; $row1 = function (string $label, string $value) use (&$curY, $leftX, $bodyW, $labelW) { $yStart = $curY; $this->SetXY($leftX, $yStart); $this->SetFont('helvetica', 'B', 9); $this->Cell($labelW, 0, $label, 0, 0, 'L'); $this->Cell(2, 0, '', 0, 0); // spacer $this->SetFont('helvetica', '', 9); $this->MultiCell($bodyW - $labelW - 10, 0, $value, 0, 'L', false, 1); $curY = $this->GetY() + 1; }; $row2('Project Description:', $d['pzfact_project_description'], 'Project Type:', $d['pzfact_project_type']); $row2('Architect/Engineer: ', $d['pzfact_professional_name'], 'Estimated Project Value:', $d['pzfact_dollar_value']); $row2('', '', 'District:', $d['pzfact_district']); $row2('Owner Name: ', $d['pzfact_owner_name'], 'Land Lot:', $d['pzfact_land_lot']); $row2('Owner Address: ', $d['pzfact_owner_address'], 'Action Code:', $d['pzfact_action_code']); $row2('Owner Phone: ', $d['pzfact_owner_phone'], 'Action Date:', $d['pzfact_action_date']); $row2('', '', 'Lot Acres:', $d['pzfact_acres']); $row2('', '', '', ''); $row1('Status:', $d['pzfact_status']); }; // Get the data for the report $planningzoningfacts = $this->pzfacts_array; $this->AddPage('L', 'LETTER'); $marginL = 10; $marginT = 20; // below header area $pageW = $this->getPageWidth(); $pageH = $this->getPageHeight(); $usableW = $pageW - ($marginL * 2); $usableH = $pageH - $marginT - 12; // leave footer space $gapY = 6; // Two rows per page $cardW = $usableW; $cardH = ($usableH - $gapY) / 2; $index = 0; foreach ($planningzoningfacts as $planningzoningfact) { // Add a new page every 2 cards if ($index > 0 && $index % 2 === 0) { $this->AddPage('L', 'LETTER'); } $slot = $index % 2; $x = $marginL; $y = $marginT + ($slot * ($cardH + $gapY)); // Map your XML fields to the card placeholders (use verbose) $d = [ // Left Side of Card 'subscription_serial' => trim((string) $planningzoningfact['pzfact_legacy_id']), 'pzfact_project_description' => trim((string) $planningzoningfact['pzfact_project_description']), 'pzfact_project_name' => trim((string) $planningzoningfact['pzfact_project_name']), 'pzfact_project_type' => trim((string) $planningzoningfact['pzfact_project_type_verbose']), 'pzfact_professional_name' => trim((string) $planningzoningfact['pzfact_professional_name']), 'pzfact_owner_name' => trim((string) $planningzoningfact['pzfact_owner_name']), 'pzfact_owner_address' => trim((string) $planningzoningfact['pzfact_owner_address']), 'pzfact_owner_phone' => $this->formatPhoneNumber(trim((string) $planningzoningfact['pzfact_owner_phone'])), // Right Side of Card 'pzfact_dollar_value' => trim((string) $planningzoningfact['pzfact_dollar_value']), 'pzfact_district' => trim((string) $planningzoningfact['pzfact_district']), 'pzfact_land_lot' => trim((string) $planningzoningfact['pzfact_land_lot']), 'pzfact_action_code' => trim((string) $planningzoningfact['pzfact_action_code_verbose']), 'pzfact_action_date' => trim((string) $planningzoningfact['pzfact_action_date_verbose']), 'pzfact_acres' => trim((string) $planningzoningfact['pzfact_acres']), 'pzfact_entry_date' => trim((string) $planningzoningfact['pzfact_entry_date_verbose']), 'pzfact_status' => trim((string) $planningzoningfact['pzfact_status']), 'pzfact_dollar_value' => trim((string) $planningzoningfact['pzfact_dollar_value']), ]; $card($x, $y, $cardW, $cardH, $d); $index++; } // ----------------- // Render the Report // ----------------- $this->SetFont('helvetica', '', 9); // -------------- // Return the pdf // -------------- while (ob_get_level() > 0) { ob_end_clean(); } $absPath = __DIR__ . "/../{$this->exportjob_base_directory}/{$this->exportjob_file_name}"; $this->Output($absPath, "F"); return $absPath; } // ====== // Header // ====== public function Header() { $this->setCellPaddings(0, 0, 0, 0); $this->setCellMargins(2, 0, 0, 0); $basePath = realpath(__DIR__ . '/..'); $this->Image($basePath . "/images/dec-international-logo.png", 250, 8, 20, 8); $this->SetFont("Calibri", "B", 16); $this->Cell(4, 0, "", 0, 0, "L"); $this->Cell(0, 0, $this->report_header_title . " Report", 0, 0, "L"); $this->Ln(10); } // ====== // Footer // ====== public function Footer() { $this->SetFont("Calibri ", "I", 8); $X = $this->GetX(); $this->Cell(0, 10, "DEC International ", 0, false, "C"); $this->SetX($X); $this->Cell(40, 10, "Page " . $this->getAliasNumPage() . "/" . $this->getAliasNbPages(), 0, false, "L"); $this->Cell(0, 10, date("n/j/Y g:i A "), 0, false, "R" ); } // ======================= // Format Raw Phone Number // ======================= public function formatPhoneNumber(string $phonenumber): string { // Remove everything except digits $digits = preg_replace('/\D+/', '', $phonenumber); // Handle leading country code (US) if (strlen($digits) === 11 && str_starts_with($digits, '1')) { $digits = substr($digits, 1); } return substr($digits, 0, 3) . '-' . substr($digits, 3, 3) . '-' . substr($digits, 6, 4); } } ?>