Refactor for PDF Generation

This commit is contained in:
David Bomba 2022-12-29 01:50:11 +11:00
parent ca853d29e5
commit 3ec7f6a80d
5 changed files with 227 additions and 195 deletions

View File

@ -24,6 +24,7 @@ use App\Models\Quote;
use App\Models\QuoteInvitation;
use App\Models\RecurringInvoice;
use App\Models\RecurringInvoiceInvitation;
use App\Services\Pdf\PdfService;
use App\Services\PdfMaker\Design as PdfDesignModel;
use App\Services\PdfMaker\Design as PdfMakerDesign;
use App\Services\PdfMaker\PdfMaker as PdfMakerService;
@ -49,22 +50,14 @@ use setasign\Fpdi\PdfParser\StreamReader;
class CreateEntityPdf implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker, MakesHash, PageNumbering;
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $entity;
public $company;
public $contact;
private $disk;
public $invitation;
public $entity_string = '';
public $client;
/**
* Create a new job instance.
*
@ -72,145 +65,57 @@ class CreateEntityPdf implements ShouldQueue
*/
public function __construct($invitation, $disk = null)
{
$this->invitation = $invitation;
if ($invitation instanceof InvoiceInvitation) {
// $invitation->load('contact.client.company','invoice.client','invoice.user.account');
$this->entity = $invitation->invoice;
$this->entity_string = 'invoice';
} elseif ($invitation instanceof QuoteInvitation) {
// $invitation->load('contact.client.company','quote.client','quote.user.account');
$this->entity = $invitation->quote;
$this->entity_string = 'quote';
} elseif ($invitation instanceof CreditInvitation) {
// $invitation->load('contact.client.company','credit.client','credit.user.account');
$this->entity = $invitation->credit;
$this->entity_string = 'credit';
} elseif ($invitation instanceof RecurringInvoiceInvitation) {
// $invitation->load('contact.client.company','recurring_invoice');
$this->entity = $invitation->recurring_invoice;
$this->entity_string = 'recurring_invoice';
}
$this->company = $invitation->company;
$this->contact = $invitation->contact;
$this->client = $invitation->contact->client;
$this->client->load('company');
$this->disk = $disk ?? config('filesystems.default');
}
public function handle()
{
MultiDB::setDb($this->company->db);
/* Forget the singleton*/
App::forgetInstance('translator');
$starttime = microtime(true);
/* Init a new copy of the translator*/
$t = app('translator');
/* Set the locale*/
App::setLocale($this->client->locale());
MultiDB::setDb($this->invitation->company->db);
/* Set customized translations _NOW_ */
$t->replace(Ninja::transformTranslations($this->client->getMergedSettings()));
if ($this->invitation instanceof InvoiceInvitation) {
$this->entity = $this->invitation->invoice;
$path = $this->invitation->contact->client->invoice_filepath($this->invitation);
} elseif ($this->invitation instanceof QuoteInvitation) {
$this->entity = $this->invitation->quote;
$path = $this->invitation->contact->client->quote_filepath($this->invitation);
} elseif ($this->invitation instanceof CreditInvitation) {
$this->entity = $this->invitation->credit;
$path = $this->invitation->contact->client->credit_filepath($this->invitation);
} elseif ($this->invitation instanceof RecurringInvoiceInvitation) {
$this->entity = $this->invitation->recurring_invoice;
$path = $this->invitation->contact->client->recurring_invoice_filepath($this->invitation);
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom)->generate($this->invitation);
}
$entity_design_id = '';
if ($this->entity instanceof Invoice) {
$path = $this->client->invoice_filepath($this->invitation);
$entity_design_id = 'invoice_design_id';
} elseif ($this->entity instanceof Quote) {
$path = $this->client->quote_filepath($this->invitation);
$entity_design_id = 'quote_design_id';
} elseif ($this->entity instanceof Credit) {
$path = $this->client->credit_filepath($this->invitation);
$entity_design_id = 'credit_design_id';
} elseif ($this->entity instanceof RecurringInvoice) {
$path = $this->client->recurring_invoice_filepath($this->invitation);
$entity_design_id = 'invoice_design_id';
}
$file_path = $path.$this->entity->numberFormatter().'.pdf';
$entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey($this->client->getSetting($entity_design_id));
$design = Design::find($entity_design_id);
/* Catch all in case migration doesn't pass back a valid design */
if (! $design) {
$design = Design::find(2);
}
$html = new HtmlEngine($this->invitation);
if ($design->is_custom) {
$options = [
'custom_partials' => json_decode(json_encode($design->design), true),
];
$template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options);
} else {
$template = new PdfMakerDesign(strtolower($design->name));
}
$variables = $html->generateLabelsAndValues();
$state = [
'template' => $template->elements([
'client' => $this->client,
'entity' => $this->entity,
'pdf_variables' => (array) $this->company->settings->pdf_variables,
'$product' => $design->design->product,
'variables' => $variables,
]),
'variables' => $variables,
'options' => [
'all_pages_header' => $this->entity->client->getSetting('all_pages_header'),
'all_pages_footer' => $this->entity->client->getSetting('all_pages_footer'),
],
'process_markdown' => $this->entity->client->company->markdown_enabled,
];
$maker = new PdfMakerService($state);
$maker
->design($template)
->build();
$pdf = null;
try {
if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') {
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, $this->company);
if ($numbered_pdf) {
$pdf = $numbered_pdf;
}
} else {
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, $this->company);
if ($numbered_pdf) {
$pdf = $numbered_pdf;
}
}
} catch (\Exception $e) {
nlog(print_r($e->getMessage(), 1));
}
if (config('ninja.log_pdf_html')) {
info($maker->getCompiledHTML());
}
// $state = [
// 'template' => $template->elements([
// 'client' => $this->client,
// 'entity' => $this->entity,
// 'pdf_variables' => (array) $this->company->settings->pdf_variables,
// '$product' => $design->design->product,
// 'variables' => $variables,
// ]),
// 'variables' => $variables,
// 'options' => [
// 'all_pages_header' => $this->entity->client->getSetting('all_pages_header'),
// 'all_pages_footer' => $this->entity->client->getSetting('all_pages_footer'),
// ],
// 'process_markdown' => $this->entity->client->company->markdown_enabled,
// ];
$pdf = (new PdfService($this->invitation))->getPdf();
$endtime = microtime(true);
nlog($endtime - $starttime);
if ($pdf) {
try {
@ -220,13 +125,6 @@ class CreateEntityPdf implements ShouldQueue
}
}
$this->invitation = null;
$this->entity = null;
$this->company = null;
$this->client = null;
$this->contact = null;
$maker = null;
$state = null;
return $file_path;
}

View File

@ -20,6 +20,7 @@ use DOMDocument;
use DOMXPath;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
use League\CommonMark\CommonMarkConverter;
class PdfBuilder
{
@ -27,19 +28,35 @@ class PdfBuilder
public PdfService $service;
private CommonMarkConverter $commonmark;
/**
* an array of sections to be injected into the template
*
* @var array
*/
public array $sections = [];
/**
* The DOM Document;
*
* @var $document
*/
public DomDocument $document;
/**
* @param PdfService $service
* @return void
*/
public function __construct(PdfService $service)
{
$this->service = $service;
$this->commonmark = new CommonMarkConverter([
'allow_unsafe_links' => false,
]);
}
/**
@ -52,11 +69,7 @@ class PdfBuilder
{
$this->getTemplate()
->buildSections();
nlog($this->sections);
$this
->buildSections()
->getEmptyElements()
->updateElementProperties()
->updateVariables();
@ -97,6 +110,7 @@ class PdfBuilder
$this->xpath = new DOMXPath($document);
return $this;
}
/**
@ -120,9 +134,11 @@ class PdfBuilder
private function mergeSections(array $section) :self
{
$this->sections = array_merge($this->sections, $section);
return $this;
}
/**
@ -212,6 +228,7 @@ class PdfBuilder
return [
['element' => 'p', 'content' => '$outstanding_label: ' . Number::formatMoney($outstanding, $this->service->config->client)],
];
}
@ -222,6 +239,7 @@ class PdfBuilder
*/
public function statementPaymentTable(): array
{
if (is_null($this->service->option['payments'])) {
return [];
}
@ -255,6 +273,7 @@ class PdfBuilder
['element' => 'thead', 'elements' => $this->buildTableHeader('statement_payment')],
['element' => 'tbody', 'elements' => $tbody],
];
}
/**
@ -666,6 +685,7 @@ class PdfBuilder
}
return $elements;
}
/**
@ -680,6 +700,7 @@ class PdfBuilder
*/
public function processTaxColumns(string $type): void
{
if ($type == 'product') {
$type_id = 1;
}
@ -722,6 +743,7 @@ class PdfBuilder
array_splice($this->service->config->pdf_variables["{$type}_columns"], $key, 1, $taxes);
}
}
}
/**
@ -752,6 +774,7 @@ class PdfBuilder
['element' => 'script', 'content' => $javascript],
['element' => 'script', 'content' => $html_decode],
]];
}
/**
@ -772,6 +795,7 @@ class PdfBuilder
]);
return $this;
}
/**
@ -786,7 +810,6 @@ class PdfBuilder
private function getProductEntityDetails(): self
{
if($this->service->config->entity_string == 'invoice')
{
$this->mergeSections( [
@ -821,7 +844,6 @@ class PdfBuilder
return $this;
}
/**
@ -850,6 +872,7 @@ class PdfBuilder
*/
private function statementTableTotals(): array
{
return [
['element' => 'div', 'properties' => ['style' => 'display: flex; flex-direction: column;'], 'elements' => [
['element' => 'div', 'properties' => ['style' => 'margin-top: 1.5rem; display: block; align-items: flex-start; page-break-inside: avoid; visible !important;'], 'elements' => [
@ -857,6 +880,7 @@ class PdfBuilder
]],
]],
];
}
/**
@ -898,6 +922,7 @@ class PdfBuilder
}
return false;
}
//First pass done, need a second pass to abstract this content completely.
@ -1053,6 +1078,7 @@ class PdfBuilder
]);
return $this;
}
/**
@ -1063,6 +1089,7 @@ class PdfBuilder
*/
public function getClientDetails(): self
{
$this->mergeSections( [
'client-details' => [
'id' => 'client-details',
@ -1071,6 +1098,7 @@ class PdfBuilder
]);
return $this;
}
/**
@ -1080,6 +1108,7 @@ class PdfBuilder
*/
public function productTable(): array
{
$product_items = collect($this->service->config->entity->line_items)->filter(function ($item) {
return $item->type_id == 1 || $item->type_id == 6 || $item->type_id == 5;
});
@ -1092,6 +1121,7 @@ class PdfBuilder
['element' => 'thead', 'elements' => $this->buildTableHeader('product')],
['element' => 'tbody', 'elements' => $this->buildTableBody('$product')],
];
}
/**
@ -1101,6 +1131,7 @@ class PdfBuilder
*/
public function taskTable(): array
{
$task_items = collect($this->service->config->entity->line_items)->filter(function ($item) {
return $item->type_id == 2;
});
@ -1113,6 +1144,7 @@ class PdfBuilder
['element' => 'thead', 'elements' => $this->buildTableHeader('task')],
['element' => 'tbody', 'elements' => $this->buildTableBody('$task')],
];
}
@ -1156,6 +1188,7 @@ class PdfBuilder
$variables = $this->service->config->pdf_variables['invoice_details'];
return $this->genericDetailsBuilder($variables);
}
/**
@ -1166,6 +1199,7 @@ class PdfBuilder
*/
public function quoteDetails(): array
{
$variables = $this->service->config->pdf_variables['quote_details'];
if ($this->service->config->entity->partial > 0) {
@ -1173,6 +1207,7 @@ class PdfBuilder
}
return $this->genericDetailsBuilder($variables);
}
@ -1188,6 +1223,7 @@ class PdfBuilder
$variables = $this->service->config->pdf_variables['credit_details'];
return $this->genericDetailsBuilder($variables);
}
/**
@ -1220,6 +1256,7 @@ class PdfBuilder
});
return $this->genericDetailsBuilder($variables);
}
/**
@ -1255,6 +1292,7 @@ class PdfBuilder
}
return $elements;
}
@ -1301,6 +1339,7 @@ class PdfBuilder
*/
public function clientDetails(): array
{
$elements = [];
if(!$this->service->config->client)
@ -1313,6 +1352,7 @@ class PdfBuilder
}
return $elements;
}
/**
@ -1323,7 +1363,6 @@ class PdfBuilder
public function deliveryNoteTable(): array
{
/* Static array of delivery note columns*/
$thead = [
['element' => 'th', 'content' => '$item_label', 'properties' => ['data-ref' => 'delivery_note-item_label']],
['element' => 'th', 'content' => '$description_label', 'properties' => ['data-ref' => 'delivery_note-description_label']],
@ -1354,6 +1393,7 @@ class PdfBuilder
['element' => 'thead', 'elements' => $thead],
['element' => 'tbody', 'elements' => $this->buildTableBody(PdfService::DELIVERY_NOTE)],
];
}
/**
@ -1366,6 +1406,7 @@ class PdfBuilder
*/
public function processNewLines(array &$items): void
{
foreach ($items as $key => $item) {
foreach ($item as $variable => $value) {
$item[$variable] = str_replace("\n", '<br>', $value);
@ -1373,6 +1414,7 @@ class PdfBuilder
$items[$key] = $item;
}
}
/**
@ -1383,6 +1425,7 @@ class PdfBuilder
*/
public function companyDetails(): array
{
$variables = $this->service->config->pdf_variables['company_details'];
$elements = [];
@ -1392,6 +1435,7 @@ class PdfBuilder
}
return $elements;
}
/**
@ -1403,6 +1447,7 @@ class PdfBuilder
*/
public function companyAddress(): array
{
$variables = $this->service->config->pdf_variables['company_address'];
$elements = [];
@ -1412,6 +1457,7 @@ class PdfBuilder
}
return $elements;
}
/**
@ -1423,6 +1469,7 @@ class PdfBuilder
*/
public function vendorDetails(): array
{
$elements = [];
$variables = $this->service->config->pdf_variables['vendor_details'];
@ -1432,6 +1479,7 @@ class PdfBuilder
}
return $elements;
}
@ -1442,11 +1490,14 @@ class PdfBuilder
public function getSectionNode(string $selector)
{
return $this->document->getElementById($selector);
}
public function updateElementProperties() :self
{
foreach ($this->sections as $element) {
if (isset($element['tag'])) {
$node = $this->document->getElementsByTagName($element['tag'])->item(0);
@ -1468,6 +1519,7 @@ class PdfBuilder
}
return $this;
}
public function updateElementProperty($element, string $attribute, ?string $value)
@ -1487,15 +1539,17 @@ class PdfBuilder
}
return $element;
}
public function createElementContent($element, $children) :self
{
foreach ($children as $child) {
$contains_html = false;
if ($child['element'] !== 'script') {
if (array_key_exists('process_markdown', $this->service->options) && array_key_exists('content', $child) && $this->service->options['process_markdown']) {
if ($this->service->company->markdown_enabled && array_key_exists('content', $child)) {
$child['content'] = str_replace('<br>', "\r", $child['content']);
$child['content'] = $this->commonmark->convert($child['content'] ?? '');
}
@ -1539,10 +1593,12 @@ class PdfBuilder
}
return $this;
}
public function updateVariables()
{
$html = strtr($this->getCompiledHTML(), $this->service->html_variables['labels']);
$html = strtr($html, $this->service->html_variables['values']);
@ -1552,10 +1608,12 @@ class PdfBuilder
$this->document->saveHTML();
return $this;
}
public function updateVariable(string $element, string $variable, string $value)
{
$element = $this->document->getElementById($element);
$original = $element->nodeValue;
@ -1569,10 +1627,12 @@ class PdfBuilder
);
return $element;
}
public function getEmptyElements() :self
{
foreach ($this->sections as $element) {
if (isset($element['elements'])) {
$this->getEmptyChildrens($element['elements'], $this->service->html_variables);
@ -1580,10 +1640,12 @@ class PdfBuilder
}
return $this;
}
public function getEmptyChildrens(array $children)
{
foreach ($children as $key => $child) {
if (isset($child['content']) && isset($child['show_empty']) && $child['show_empty'] === false) {
$value = strtr($child['content'], $this->service->html_variables['values']);
@ -1598,6 +1660,7 @@ class PdfBuilder
}
return $this;
}
}

View File

@ -29,7 +29,9 @@ use App\Models\RecurringInvoiceInvitation;
use App\Models\Vendor;
use App\Models\VendorContact;
use App\Services\Pdf\PdfService;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
use Illuminate\Support\Facades\App;
class PdfConfiguration
{
@ -61,7 +63,9 @@ class PdfConfiguration
/**
* The parent object of the currency
*
* @var App\Models\Client | App\Models\Vendor
*
*/
public $currency_entity;
@ -72,20 +76,36 @@ class PdfConfiguration
}
public function init()
public function init(): self
{
$this->setEntityType()
->setEntityProperties()
->setPdfVariables()
->setDesign()
->setCurrency();
->setCurrency()
->setLocale();
return $this;
}
private function setCurrency() :self
private function setLocale(): self
{
App::forgetInstance('translator');
$t = app('translator');
App::setLocale($this->settings_object->locale());
$t->replace(Ninja::transformTranslations($this->settings));
return $this;
}
private function setCurrency(): self
{
$this->currency = $this->client ? $this->client->currency() : $this->vendor->currency();
@ -100,6 +120,7 @@ class PdfConfiguration
{
$default = (array) CompanySettings::getEntityVariableDefaults();
$variables = (array)$this->service->company->settings->pdf_variables;
foreach ($default as $property => $value) {
@ -139,11 +160,13 @@ class PdfConfiguration
}
return $this;
}
private function setEntityProperties()
{
$entity_design_id = '';
$entity_design_id = '';
if ($this->entity instanceof Invoice) {
@ -198,6 +221,7 @@ class PdfConfiguration
$this->path = $this->path.$this->entity->numberFormatter().'.pdf';
return $this;
}
private function setDesign()

View File

@ -12,13 +12,19 @@
namespace App\Services\Pdf;
use App\Models\Account;
use App\Services\Pdf\PdfConfiguration;
use App\Utils\HtmlEngine;
use App\Models\Company;
use App\Services\Pdf\PdfConfiguration;
use App\Utils\HostedPDF\NinjaPdf;
use App\Utils\HtmlEngine;
use App\Utils\PhantomJS\Phantom;
use App\Utils\Traits\Pdf\PageNumbering;
use App\Utils\Traits\Pdf\PdfMaker;
class PdfService
{
use PdfMaker, PageNumbering;
public $invitation;
public Company $company;
@ -55,58 +61,90 @@ class PdfService
$this->html_variables = (new HtmlEngine($invitation))->generateLabelsAndValues();
$this->builder = (new PdfBuilder($this));
$this->designer = (new PdfDesigner($this))->build();
$this->document_type = $document_type;
$this->options = $options;
}
public function build()
{
$this->builder->build();
return $this;
$this->builder = (new PdfBuilder($this))->build();
}
/**
* Resolves the PDF generation type and
* attempts to generate a PDF from the HTML
* string.
*
* @return mixed
*
*/
public function getPdf()
{
try {
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom')
{
$pdf = (new Phantom)->convertHtmlToPdf($this->getHtml());
}
elseif (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja')
{
$pdf = (new NinjaPdf())->build($this->getHtml());
}
else
{
$pdf = $this->makePdf(null, null, $this->getHtml());
}
$numbered_pdf = $this->pageNumbering($pdf, $this->company);
if ($numbered_pdf)
{
$pdf = $numbered_pdf;
}
} catch (\Exception $e) {
nlog(print_r($e->getMessage(), 1));
throw new \Exception($e->getMessage(), $e->getCode());
}
return $pdf;
}
public function getHtml()
/**
* Renders the dom document to HTML
*
* @return string
*
*/
public function getHtml(): string
{
return $this->builder->getCompiledHTML();
$html = $this->builder->getCompiledHTML();
if (config('ninja.log_pdf_html'))
{
info($html);
}
return $html;
}
// $state = [
// 'template' => $template->elements([
// 'client' => $this->client,
// 'entity' => $this->entity,
// 'pdf_variables' => (array) $this->company->settings->pdf_variables,
// '$product' => $design->design->product,
// 'variables' => $variables,
// ]),
// 'variables' => $variables,
// 'options' => [
// 'all_pages_header' => $this->entity->client->getSetting('all_pages_header'),
// 'all_pages_footer' => $this->entity->client->getSetting('all_pages_footer'),
// ],
// 'process_markdown' => $this->entity->client->company->markdown_enabled,
// ];
// $maker = new PdfMakerService($state);
// $maker
// ->design($template)
// ->build();
}

View File

@ -33,6 +33,17 @@ class PdfServiceTest extends TestCase
$this->makeTestData();
}
public function testPdfGeneration()
{
$invitation = $this->invoice->invitations->first();
$service = new PdfService($invitation);
$this->assertNotNull($service->getPdf());
}
public function testHtmlGeneration()
{
@ -40,9 +51,7 @@ class PdfServiceTest extends TestCase
$service = new PdfService($invitation);
$this->assertIsString($service->build()->getHtml());
nlog($service->build()->getHtml());
$this->assertIsString($service->getHtml());
}