> $territories * @param list> $allHouseholds All household rows (with territory_id) * @return string Raw ZIP binary content */ public function buildZip(array $territories, array $allHouseholds): string { $byTerritory = []; foreach ($allHouseholds as $h) { $tid = (int) $h['territory_id']; $byTerritory[$tid][] = $h; } $zipFile = tempnam(sys_get_temp_dir(), 'territory_export_'); $zip = new \ZipArchive(); $zip->open($zipFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE); foreach ($territories as $territory) { $tid = (int) $territory['id']; $households = $byTerritory[$tid] ?? []; $slug = $this->slug($territory['name']); $xlsx = $this->buildXlsx($territory, $households); $zip->addFromString("{$slug}.xlsx", $xlsx); $pdf = $this->buildPdf($territory, $households); $zip->addFromString("{$slug}.pdf", $pdf); } $zip->close(); $content = (string) file_get_contents($zipFile); unlink($zipFile); return $content; } /** @param list> $households */ private function buildXlsx(array $territory, array $households): string { $spreadsheet = new Spreadsheet(); $spreadsheet->removeSheetByIndex(0); $byStreet = $this->groupByStreet($households); if (empty($byStreet)) { $sheet = $spreadsheet->createSheet(); $sheet->setTitle('No Data'); $sheet->setCellValue('A1', 'No households found for this territory.'); } foreach ($byStreet as $streetName => $streetHouseholds) { $sheet = $spreadsheet->createSheet(); $sheet->setTitle(substr((string) $streetName, 0, 31)); [$even, $odd] = $this->splitEvenOdd($streetHouseholds); $headerStyle = [ 'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']], 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '1D7A6D']], 'borders' => ['bottom' => ['borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN]], ]; $evenHeaders = ['#', 'Address (Even)', 'Bus.', 'DNC', 'DNC Date']; $oddHeaders = ['#', 'Address (Odd)', 'Bus.', 'DNC', 'DNC Date']; foreach ($evenHeaders as $col => $header) { $cell = $this->colLetter($col + 1) . '1'; $sheet->setCellValue($cell, $header); $sheet->getStyle($cell)->applyFromArray($headerStyle); } $sheet->setCellValue('G1', ''); foreach ($oddHeaders as $col => $header) { $cell = $this->colLetter($col + 8) . '1'; $sheet->setCellValue($cell, $header); $sheet->getStyle($cell)->applyFromArray($headerStyle); } $row = 2; foreach ($even as $h) { $sheet->setCellValue("A{$row}", $h['street_number'] ?? ''); $sheet->setCellValue("B{$row}", $h['address'] ?? ''); $sheet->setCellValue("C{$row}", $h['is_business'] ? 'Yes' : ''); $sheet->setCellValue("D{$row}", $h['do_not_call'] ? 'Yes' : ''); $sheet->setCellValue("E{$row}", $h['do_not_call_date'] ?? ''); $row++; } $row = 2; foreach ($odd as $h) { $sheet->setCellValue("H{$row}", $h['street_number'] ?? ''); $sheet->setCellValue("I{$row}", $h['address'] ?? ''); $sheet->setCellValue("J{$row}", $h['is_business'] ? 'Yes' : ''); $sheet->setCellValue("K{$row}", $h['do_not_call'] ? 'Yes' : ''); $sheet->setCellValue("L{$row}", $h['do_not_call_date'] ?? ''); $row++; } foreach (range('A', 'L') as $col) { $sheet->getColumnDimension($col)->setAutoSize(true); } } $writer = new Xlsx($spreadsheet); $tmpFile = tempnam(sys_get_temp_dir(), 'territory_xlsx_'); $writer->save($tmpFile); $content = (string) file_get_contents($tmpFile); unlink($tmpFile); return $content; } /** @param list> $households */ private function buildPdf(array $territory, array $households): string { $options = new Options(); $options->set('defaultFont', 'DejaVu Sans'); $options->set('isRemoteEnabled', false); $dompdf = new Dompdf($options); $dompdf->loadHtml($this->buildPdfHtml($territory, $households)); $dompdf->setPaper('A4', 'landscape'); $dompdf->render(); return (string) $dompdf->output(); } /** @param list> $households */ private function buildPdfHtml(array $territory, array $households): string { $title = htmlspecialchars($territory['name'], ENT_QUOTES, 'UTF-8'); $byStreet = $this->groupByStreet($households); $html = ' '; $html .= "

Territory: {$title}

"; if (empty($byStreet)) { $html .= '

No households found.

'; } foreach ($byStreet as $streetName => $streetHouseholds) { $escapedStreet = htmlspecialchars((string) $streetName, ENT_QUOTES, 'UTF-8'); [$even, $odd] = $this->splitEvenOdd($streetHouseholds); $html .= "

{$escapedStreet}

"; $html .= ''; $max = max(count($even), count($odd), 1); $evenVals = array_values($even); $oddVals = array_values($odd); for ($i = 0; $i < $max; $i++) { $html .= ''; if (isset($evenVals[$i])) { $h = $evenVals[$i]; $addr = htmlspecialchars($h['address'] ?? '', ENT_QUOTES, 'UTF-8'); $dnc = $h['do_not_call'] ? 'DNC' : ''; $html .= ""; } else { $html .= ''; } $html .= ''; if (isset($oddVals[$i])) { $h = $oddVals[$i]; $addr = htmlspecialchars($h['address'] ?? '', ENT_QUOTES, 'UTF-8'); $dnc = $h['do_not_call'] ? 'DNC' : ''; $html .= ""; } else { $html .= ''; } $html .= ''; } $html .= '
#Address (Even)DNC #Address (Odd)DNC
{$h['street_number']}{$addr}{$dnc}{$h['street_number']}{$addr}{$dnc}
'; } $html .= ''; return $html; } /** * @param list> $households * @return array>> */ private function groupByStreet(array $households): array { $byStreet = []; foreach ($households as $h) { $street = trim((string) ($h['street_name'] ?? '')); if ($street === '') { $street = 'Unknown Street'; } $byStreet[$street][] = $h; } ksort($byStreet); return $byStreet; } /** * @param list> $households * @return array{list>, list>} */ private function splitEvenOdd(array $households): array { $even = []; $odd = []; foreach ($households as $h) { $num = (int) ($h['street_number'] ?? 0); if ($num % 2 === 0) { $even[] = $h; } else { $odd[] = $h; } } usort($even, fn($a, $b) => (int) ($a['street_number'] ?? 0) <=> (int) ($b['street_number'] ?? 0)); usort($odd, fn($a, $b) => (int) ($a['street_number'] ?? 0) <=> (int) ($b['street_number'] ?? 0)); return [$even, $odd]; } private function colLetter(int $n): string { $letter = ''; while ($n > 0) { $n--; $letter = chr(65 + ($n % 26)) . $letter; $n = (int) ($n / 26); } return $letter; } private function slug(string $name): string { $slug = strtolower(preg_replace('/[^a-zA-Z0-9]+/', '_', $name) ?? $name); return trim($slug, '_') ?: 'territory'; } }