RprtCli/app/src/Utils/PdfExport/PdfExportService.php

202 lines
7.0 KiB
PHP

<?php
declare(strict_types=1);
namespace RprtCli\Utils\PdfExport;
use Exception;
use RprtCli\Utils\Configuration\ConfigurationInterface;
use Mpdf\Output\Destination;
use RprtCli\Utils\CsvReport\ReportCsvInterface;
class PdfExportService implements PdfExportInterface {
protected $templatePath;
protected $output;
protected $config;
protected $mpdf;
public function __construct(ConfigurationInterface $config, $mpdf) {
$this->config = $config;
$this->mpdf = $mpdf;
}
public function getTemplatePath(): ?string {
if (!isset($this->templatePath)) {
$template_path = $this->config->get('export.template_path', FALSE);
if (!$template_path) {
$template_path = readline('Enter template file path: ');
}
if (!file_exists($template_path)) {
throw new Exception('Template file not found!');
}
$this->templatePath = $template_path;
}
return $this->templatePath;
}
public function setTemplatePath(string $path): void {
if (file_exists($path)) {
$this->templatePath = $path;
return;
}
throw new Exception('Invoice template file not found!');
}
// @TODO move this method to CsvReport.
// Add classes to table elements.
// @TODO would it make sense to allow more per user extending?
// @TODO - too much assumptions on data structure. Create a class with
// precise data structure and use that to pass the data around.
public function parsedDataToHtmlTable(array $data): ?string {
$table = '<table><thead><tr class="tr-header">';
$header = array_shift($data);
$classes = '';
foreach ($header as $index => $cell) {
$table .= "<th class=\"th-{$index}\">{$cell}</th>";
}
$table .= '</tr></thead><tbody>';
foreach ($data as $row_index => $row) {
if (!$row) {
if ($row === ReportCsvInterface::SEPARATOR_MEDIUM) {
$classes = 'bold';
}
elseif ($row === ReportCsvInterface::SEPARATOR_HARD) {
$classes = 'bold center';
}
continue;
}
list($cells, $colspan) = [[], 0];
foreach ($row as $index => $cell) {
if (!$cell) {
$colspan += 1;
continue;
}
if ($colspan) {
$colspan += 1;
$cells[] = "<td class=\"td-{$index} colspan\" colspan=\"{$colspan}\">{$cell}</td>";
$colspan = 0;
}
else {
$cells[] = "<td class=\"td-{$index}\">{$cell}</td>";
}
}
$rows[] = "<tr class=\"tr-{$row_index} {$classes}\">" . implode($cells) . '</tr>';
$classes = '';
}
$table .= implode('', $rows);
$table .= '</tbody></table>';
// var_dump($table);
return $table;
}
public function replaceTokensInTemplate(string $template_path, array $tokens): ?string
{
$template = file_get_contents($template_path);
foreach ($tokens as $key => $value) {
$template = str_replace("[[{$key}]]", $value, $template);
}
return $template;
}
// @TODO write a method to gather tokens.
public function getTokensInTemplate(string $template): array {
// @TODO find substrings of type [[key]]
preg_match_all('/\[\[([a-z0-9-_]+)\]\]/', $template, $match);
return $match[1];
}
// @TODO support multiple templates by adding template id in config.
/**
* Get tokens to replace in the template.
*
* @param string $template_path
* Path to the template file (long term plan is to support multiple templates).
* @param bool $skip_missing
* Just skip missing tokens.
* @param array $runtime_tokens
* Provide tokens at runtime of the application (not supported yet). @TODO
* @param string $config
* Config path to tokens.
*
* @return array
* Token keys and values array.
*/
public function gatherTokensForTemplate(string $template_path, bool $skip_missing = FALSE, array $runtime_tokens = [], string $config = 'export.tokens'): array {
list($tokens, $missing) = [[], []];
$token_keys = $this->getTokensInTemplate(file_get_contents($template_path));
$config_tokens = $this->config->get($config);
foreach ($token_keys as $token_key) {
if (isset($runtime_tokens[$token_key])) {
$tokens[$token_key] = $runtime_tokens[$token_key];
}
elseif (!isset($config_tokens[$token_key]) && !$skip_missing) {
$tokens[$token_key] = readline("Enter value to replace [[{$token_key}]] in template: ");
}
elseif (isset($config_tokens[$token_key])) {
$tokens[$token_key] = $config_tokens[$token_key];
}
else {
$missing[] = $token_key;
}
}
return $tokens;
}
public function pdfExport(string $html, $output = null): bool {
$this->mpdf->SetProtection(array('print'));
// @TODO make configurable.
$this->mpdf->SetTitle("Invoice");
$this->mpdf->SetAuthor("rprt-cli");
$this->mpdf->SetDisplayMode('fullpage');
$this->mpdf->WriteHTML($html);
$this->mpdf->Output($this->getOutput(), Destination::FILE);
return file_exists($this->output);
}
protected function getOutput() {
if (isset($this->output)) {
return $this->output;
}
$output = $this->config->get('export.output', NULL) ?? readline('Enter output file path: ');
$date = strtotime("-1 month");
$output = str_replace('[[month]]', date('F', $date), $output);
$output = str_replace('[[year]]', date('Y', $date), $output);
$this->output = $output;
return $output;
}
public function setOutput(string $path): void {
$this->output = $path;
}
public function fromDefaultDataToPdf(array $data, array $tokens = []): string {
$template_path = $this->getTemplatePath();
$tokens = $this->defaultTokens();
$tokens['table'] = $this->parsedDataToHtmlTable($data);
$tokens = $this->gatherTokensForTemplate($template_path, FALSE, $tokens);
$html = $this->replaceTokensInTemplate($template_path, $tokens);
$success = $this->pdfExport($html);
if ($success) {
return $this->getOutput();
}
return '';
}
/**
* Get default tokens.
*/
protected function defaultTokens(): array {
$tokens = [];
$tokens['today'] = date('j. m. y');
$month_ago = strtotime('1 month ago');
$tokens['number'] = date('m/y', $month_ago);
$tokens['date_start'] = date('1. m. Y', $month_ago);
$tokens['date_end'] = date("d. m. Y", mktime(0, 0, 0, (int) date("m"), 0));
return $tokens;
}
}