$headers @param list> $rows */ public function render(string $title, array $headers, array $rows): string { $headerCount = count($headers); foreach ($rows as $number => $row) { if (!is_array($row) || count($row) !== $headerCount) { throw new InvalidArgumentException(sprintf('Print report row %d must contain %d fields.', $number + 1, $headerCount)); } } $head = implode('', array_map(fn(mixed $value): string => '' . $this->escape($value) . '', $headers)); $body = ''; foreach ($rows as $row) { $body .= '' . implode('', array_map(fn(mixed $value): string => '' . $this->escape($value) . '', $row)) . ''; } $logoFile = function_exists('app_setting') ? app_setting('logo_filename') : null; $brandName = function_exists('app_setting') ? (app_setting('company_name', 'JOBcard') ?: 'JOBcard') : 'JOBcard'; $brand = $logoFile ? '
' . $this->escape($brandName) . '
' : ''; return '' . $this->escape($title) . '
' . $brand . '

' . $this->escape($title) . '

' . $head . '' . $body . '
'; } /** @param list> $rows */ public function renderRecords(string $title, array $rows): string { $headers = $rows === [] ? [] : array_keys($rows[0]); return $this->render($title, $headers, array_map(fn(array $row): array => array_values($row), $rows)); } private function escape(mixed $value): string { return htmlspecialchars((string)($value ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } }