From e4fc9e2cc897116a699327cb132b5a949b23a03d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 17 Oct 2023 21:28:18 +1100 Subject: [PATCH 1/4] Refactor for live previews --- app/Http/Controllers/PreviewController.php | 180 ++++++++++++++---- .../ProtectedDownloadController.php | 2 +- .../Preview/PreviewInvoiceRequest.php | 126 +++++++++++- app/Jobs/Util/PreviewPdf.php | 13 +- tests/Feature/LiveDesignTest.php | 26 +++ 5 files changed, 297 insertions(+), 50 deletions(-) diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index 0f94c8e9ac1a..6777af3f941d 100644 --- a/app/Http/Controllers/PreviewController.php +++ b/app/Http/Controllers/PreviewController.php @@ -11,42 +11,43 @@ namespace App\Http\Controllers; -use App\DataMapper\Analytics\LivePreview; -use App\Factory\CreditFactory; -use App\Factory\InvoiceFactory; -use App\Factory\QuoteFactory; -use App\Factory\RecurringInvoiceFactory; -use App\Http\Requests\Preview\DesignPreviewRequest; -use App\Http\Requests\Preview\PreviewInvoiceRequest; -use App\Jobs\Util\PreviewPdf; -use App\Libraries\MultiDB; +use App\Utils\Ninja; +use App\Models\Quote; use App\Models\Client; -use App\Models\ClientContact; use App\Models\Credit; use App\Models\Invoice; -use App\Models\InvoiceInvitation; -use App\Models\Quote; -use App\Models\RecurringInvoice; -use App\Repositories\CreditRepository; -use App\Repositories\InvoiceRepository; -use App\Repositories\QuoteRepository; -use App\Repositories\RecurringInvoiceRepository; +use App\Utils\HtmlEngine; +use App\Libraries\MultiDB; +use App\Factory\QuoteFactory; +use App\Jobs\Util\PreviewPdf; +use App\Models\ClientContact; use App\Services\Pdf\PdfMock; +use App\Factory\CreditFactory; +use App\Factory\InvoiceFactory; +use App\Utils\Traits\MakesHash; +use App\Models\RecurringInvoice; +use App\Utils\PhantomJS\Phantom; +use App\Models\InvoiceInvitation; use App\Services\PdfMaker\Design; +use App\Utils\HostedPDF\NinjaPdf; +use Illuminate\Support\Facades\DB; +use App\Services\PdfMaker\PdfMaker; +use Illuminate\Support\Facades\App; +use App\Repositories\QuoteRepository; +use Illuminate\Support\Facades\Cache; +use App\Repositories\CreditRepository; +use App\Utils\Traits\MakesInvoiceHtml; +use Turbo124\Beacon\Facades\LightLogs; +use App\Repositories\InvoiceRepository; +use App\Utils\Traits\Pdf\PageNumbering; +use App\Factory\RecurringInvoiceFactory; +use Illuminate\Support\Facades\Response; +use App\DataMapper\Analytics\LivePreview; +use App\Repositories\RecurringInvoiceRepository; +use App\Http\Requests\Preview\DesignPreviewRequest; use App\Services\PdfMaker\Design as PdfDesignModel; use App\Services\PdfMaker\Design as PdfMakerDesign; -use App\Services\PdfMaker\PdfMaker; -use App\Utils\HostedPDF\NinjaPdf; -use App\Utils\HtmlEngine; -use App\Utils\Ninja; -use App\Utils\PhantomJS\Phantom; -use App\Utils\Traits\MakesHash; -use App\Utils\Traits\MakesInvoiceHtml; -use App\Utils\Traits\Pdf\PageNumbering; -use Illuminate\Support\Facades\App; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Response; -use Turbo124\Beacon\Facades\LightLogs; +use App\Http\Requests\Preview\PreviewInvoiceRequest; class PreviewController extends BaseController { @@ -56,7 +57,108 @@ class PreviewController extends BaseController public function __construct() { - parent::__construct(); + parent::__construct(); + } + + private function purgeCache() + { nlog(auth()->user()->id); + Cache::pull("preview_".auth()->user()->id); + } + + public function newLivePreview(PreviewInvoiceRequest $request) + { + + $time = time(); + nlog($time); + + $invitation = $request->resolveInvitation(); + + App::forgetInstance('translator'); + $t = app('translator'); + App::setLocale($invitation->contact->preferredLocale()); + $t->replace(Ninja::transformTranslations($invitation->{$request->entity}->client->getMergedSettings())); + + $html = new HtmlEngine($invitation); + $variables = $html->generateLabelsAndValues(); + + $entity_obj = $invitation->{$request->entity}; + + // if (! $invitation->{$request->entity}->id ?? true) { + // $invitation->{$request->entity}->service()->fillDefaults(); + // } + + $design = \App\Models\Design::withTrashed()->find($entity_obj->design_id ?? 2); + + /* Catch all in case migration doesn't pass back a valid design */ + if (! $design) { + $design = \App\Models\Design::find(2); + } + + 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)); + } + + $state = [ + 'template' => $template->elements([ + 'client' => $entity_obj->client, + 'entity' => $entity_obj, + 'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables, + '$product' => $design->design->product, + 'variables' => $variables, + ]), + 'variables' => $variables, + 'options' => [ + 'all_pages_header' => $entity_obj->client->getSetting('all_pages_header'), + 'all_pages_footer' => $entity_obj->client->getSetting('all_pages_footer'), + ], + 'process_markdown' => $entity_obj->client->company->markdown_enabled, + ]; + + $maker = new PdfMaker($state); + + $maker + ->design($template) + ->build(); + + if (request()->query('html') == 'true') { + return $maker->getCompiledHTML(); + } + + //if phantom js...... inject here.. + if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { + return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true)); + } + + /** @var \App\Models\User $user */ + $user = auth()->user(); + $company = $user->company(); + + 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, $company); + + if ($numbered_pdf) { + $pdf = $numbered_pdf; + } + + return $pdf; + } + + $pdf = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle(); + + nlog("merpy derp {$time}"); + + return response()->streamDownload(function () use ($pdf) { + echo $pdf; + }, 'preview.pdf', ['Content-Type' => 'application/pdf','Cache-Control:' => 'no-cache']); + } /** @@ -173,10 +275,18 @@ class PreviewController extends BaseController public function live(PreviewInvoiceRequest $request) { + + // if(Cache::has("preview_".auth()->user()->id)) + // return response()->json(['message' => 'Please wait a few seconds before trying again, this many requests are not good.'], 400); + + nlog("should not see a lot of these"); + if (Ninja::isHosted() && !in_array($request->getHost(), ['preview.invoicing.co','staging.invoicing.co'])) { return response()->json(['message' => 'This server cannot handle this request.'], 400); } + Cache::put("preview_".auth()->user()->id, 60); + $start = microtime(true); /** @var \App\Models\User $user */ @@ -287,9 +397,13 @@ class PreviewController extends BaseController DB::connection(config('database.default'))->rollBack(); if (request()->query('html') == 'true') { + $this->purgeCache(); return $maker->getCompiledHTML(); } + } catch(\Exception $e) { + + $this->purgeCache(); DB::connection(config('database.default'))->rollBack(); @@ -302,6 +416,7 @@ class PreviewController extends BaseController //if phantom js...... inject here.. if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { + $this->purgeCache(); return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true)); } @@ -319,7 +434,7 @@ class PreviewController extends BaseController if ($numbered_pdf) { $pdf = $numbered_pdf; } - + $this->purgeCache(); return $pdf; } @@ -335,7 +450,8 @@ class PreviewController extends BaseController $response->header('Content-Type', 'application/pdf'); $response->header('Server-Timing', microtime(true)-$start); - + nlog("returning a response"); + $this->purgeCache(); return $response; } diff --git a/app/Http/Controllers/ProtectedDownloadController.php b/app/Http/Controllers/ProtectedDownloadController.php index b8f598a11021..014527bace14 100644 --- a/app/Http/Controllers/ProtectedDownloadController.php +++ b/app/Http/Controllers/ProtectedDownloadController.php @@ -23,7 +23,7 @@ class ProtectedDownloadController extends BaseController public function index(Request $request) { - + /** @var string $hashed_path */ $hashed_path = Cache::pull($request->hash); if (!$hashed_path) { diff --git a/app/Http/Requests/Preview/PreviewInvoiceRequest.php b/app/Http/Requests/Preview/PreviewInvoiceRequest.php index bfa4db546470..50cc0a7fdf73 100644 --- a/app/Http/Requests/Preview/PreviewInvoiceRequest.php +++ b/app/Http/Requests/Preview/PreviewInvoiceRequest.php @@ -11,15 +11,29 @@ namespace App\Http\Requests\Preview; +use App\Models\Quote; +use App\Models\Client; +use App\Models\Credit; +use App\Models\Invoice; +use App\Libraries\MultiDB; use App\Http\Requests\Request; -use App\Utils\Traits\CleanLineItems; +use App\Models\CompanyGateway; +use App\Models\QuoteInvitation; use App\Utils\Traits\MakesHash; +use Illuminate\Validation\Rule; +use App\Models\CreditInvitation; +use App\Models\RecurringInvoice; +use App\Models\InvoiceInvitation; +use App\Utils\Traits\CleanLineItems; +use App\Models\RecurringInvoiceInvitation; class PreviewInvoiceRequest extends Request { use MakesHash; use CleanLineItems; + private string $entity_plural = ''; + /** * Determine if the user is authorized to make this request. * @@ -27,20 +41,32 @@ class PreviewInvoiceRequest extends Request */ public function authorize() : bool { - return auth()->user()->hasIntersectPermissionsOrAdmin(['view_invoice', 'view_quote', 'view_recurring_invoice', 'view_credit', 'create_invoice', 'create_quote', 'create_recurring_invoice', 'create_credit','edit_invoice', 'edit_quote', 'edit_recurring_invoice', 'edit_credit']); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->hasIntersectPermissionsOrAdmin(['view_invoice', 'view_quote', 'view_recurring_invoice', 'view_credit', 'create_invoice', 'create_quote', 'create_recurring_invoice', 'create_credit','edit_invoice', 'edit_quote', 'edit_recurring_invoice', 'edit_credit']); } public function rules() { - $rules = []; + /** @var \App\Models\User $user */ + $user = auth()->user(); - $rules['number'] = ['nullable']; + return [ + 'number' => 'nullable', + 'entity' => 'bail|sometimes|in:invoice,quote,credit,recurring_invoice', + 'entity_id' => ['bail','sometimes','integer',Rule::exists($this->entity_plural, 'id')->where('is_deleted',0)->where('company_id', $user->company()->id)], + 'client_id' => ['required', Rule::exists(Client::class, 'id')->where('is_deleted', 0)->where('company_id', $user->company()->id)], + ]; - return $rules; } public function prepareForValidation() { + + /** @var \App\Models\User $user */ + $user = auth()->user(); + $input = $this->all(); $input = $this->decodePrimaryKeys($input); @@ -50,6 +76,96 @@ class PreviewInvoiceRequest extends Request $input['balance'] = 0; $input['number'] = isset($input['number']) ? $input['number'] : ctrans('texts.live_preview').' #'.rand(0, 1000); + if($input['entity_id'] ?? false) + $input['entity_id'] = $this->decodePrimaryKey($input['entity_id'], true); + + $this->convertEntityPlural($input['entity'] ?? 'invoice'); + $this->replace($input); } + + public function resolveInvitation() + { + $invitation = false; + + if(! $this->entity_id ?? false) + return $this->stubInvitation(); + + match($this->entity){ + 'invoice' => $invitation = Invoice::withTrashed()->where('invoice_id', $this->entity_id)->first(), + 'quote' => $invitation = Quote::withTrashed()->where('quote_id', $this->entity_id)->first(), + 'credit' => $invitation = Credit::withTrashed()->where('credit_id', $this->entity_id)->first(), + 'recurring_invoice' => $invitation = RecurringInvoice::withTrashed()->where('recurring_invoice_id', $this->entity_id)->first(), + }; + + if($invitation) + return $invitation; + + $invitation = $this->stubInvitation(); + } + + public function stubInvitation() + { + $client = Client::query()->with('contacts', 'company', 'user')->withTrashed()->find($this->client_id); + $invitation = false; + + match($this->entity) { + 'invoice' => $invitation = InvoiceInvitation::factory()->make(), + 'quote' => $invitation = QuoteInvitation::factory()->make(), + 'credit' => $invitation = CreditInvitation::factory()->make(), + 'recurring_invoice' => $invitation = RecurringInvoiceInvitation::factory()->make(), + default => $invitation = InvoiceInvitation::factory()->make(), + }; + + $entity = $this->stubEntity($client); + + $invitation->make(); + $invitation->setRelation($this->entity, $entity); + $invitation->setRelation('contact', $client->contacts->first()->load('client.company')); + $invitation->setRelation('company', $client->company); + + return $invitation; + } + + private function stubEntity(Client $client) + { + $entity = false; + + match($this->entity){ + 'invoice' => $entity = Invoice::factory()->make(['client_id' => $client->id,'user_id' => $client->user_id, 'company_id' => $client->company_id]), + 'quote' => $entity = Quote::factory()->make(['client_id' => $client->id,'user_id' => $client->user_id, 'company_id' => $client->company_id]), + 'credit' => $entity = Credit::factory()->make(['client_id' => $client->id,'user_id' => $client->user_id, 'company_id' => $client->company_id]), + 'recurring_invoice' => $entity = RecurringInvoice::factory()->make(['client_id' => $client->id,'user_id' => $client->user_id, 'company_id' => $client->company_id]), + default => $entity = Invoice::factory()->make(['client_id' => $client->id,'user_id' => $client->user_id, 'company_id' => $client->company_id]), + }; + + $entity->setRelation('client', $client); + $entity->setRelation('company', $client->company); + $entity->setRelation('user', $client->user); + $entity->fill($this->all()); + + return $entity; + } + + private function convertEntityPlural(string $entity) :self + { + switch ($entity) { + case 'invoice': + $this->entity_plural = 'invoices'; + return $this; + case 'quote': + $this->entity_plural = 'quotes'; + return $this; + case 'credit': + $this->entity_plural = 'credits'; + return $this; + case 'recurring_invoice': + $this->entity_plural = 'invoices'; + return $this; + default: + $this->entity_plural = 'invoices'; + return $this; + } + } + } diff --git a/app/Jobs/Util/PreviewPdf.php b/app/Jobs/Util/PreviewPdf.php index 214201f1a660..0838379e9428 100644 --- a/app/Jobs/Util/PreviewPdf.php +++ b/app/Jobs/Util/PreviewPdf.php @@ -24,25 +24,14 @@ class PreviewPdf implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, PdfMaker, PageNumbering; - public $company; - - private $disk; - - public $design_string; - /** * Create a new job instance. * * @param $design_string * @param Company $company */ - public function __construct($design_string, Company $company) + public function __construct(public string $design_string, public Company $company) { - $this->company = $company; - - $this->design_string = $design_string; - - $this->disk = $disk ?? config('filesystems.default'); } public function handle() diff --git a/tests/Feature/LiveDesignTest.php b/tests/Feature/LiveDesignTest.php index 03ce04551e54..7dc750f10902 100644 --- a/tests/Feature/LiveDesignTest.php +++ b/tests/Feature/LiveDesignTest.php @@ -13,7 +13,9 @@ namespace Tests\Feature; use Tests\TestCase; use App\Models\Design; +use App\Utils\HtmlEngine; use Tests\MockAccountData; +use App\Models\InvoiceInvitation; use Illuminate\Routing\Middleware\ThrottleRequests; use Illuminate\Foundation\Testing\DatabaseTransactions; @@ -41,6 +43,30 @@ class LiveDesignTest extends TestCase } } + public function testSyntheticInvitations() + { + $this->assertGreaterThanOrEqual(1, $this->client->contacts->count()); + + $ii = InvoiceInvitation::factory() + ->for($this->invoice) + ->for($this->client->contacts->first(), 'contact') + ->for($this->company) + ->for($this->user) + ->make(); + + $this->assertInstanceOf(InvoiceInvitation::class, $ii); + + $engine = new HtmlEngine($ii); + + $this->assertNotNull($engine); + + $data = $engine->generateLabelsAndValues(); + + $this->assertIsArray($data); + + nlog($data); + } + public function testDesignRoute200() { $data = [ From e17af36cf4309526acaf188765361792fbdedccd Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 19 Oct 2023 07:34:40 +1100 Subject: [PATCH 2/4] Improvements for live preview --- app/Http/Controllers/PreviewController.php | 8 +++----- app/Http/Requests/Preview/PreviewInvoiceRequest.php | 8 ++++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index 6777af3f941d..2797bae0eea1 100644 --- a/app/Http/Controllers/PreviewController.php +++ b/app/Http/Controllers/PreviewController.php @@ -61,11 +61,11 @@ class PreviewController extends BaseController } private function purgeCache() - { nlog(auth()->user()->id); + { Cache::pull("preview_".auth()->user()->id); } - public function newLivePreview(PreviewInvoiceRequest $request) + public function live(PreviewInvoiceRequest $request) { $time = time(); @@ -153,8 +153,6 @@ class PreviewController extends BaseController $pdf = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle(); - nlog("merpy derp {$time}"); - return response()->streamDownload(function () use ($pdf) { echo $pdf; }, 'preview.pdf', ['Content-Type' => 'application/pdf','Cache-Control:' => 'no-cache']); @@ -273,7 +271,7 @@ class PreviewController extends BaseController return $response; } - public function live(PreviewInvoiceRequest $request) + public function livex(PreviewInvoiceRequest $request) { // if(Cache::has("preview_".auth()->user()->id)) diff --git a/app/Http/Requests/Preview/PreviewInvoiceRequest.php b/app/Http/Requests/Preview/PreviewInvoiceRequest.php index 50cc0a7fdf73..2188efa37c98 100644 --- a/app/Http/Requests/Preview/PreviewInvoiceRequest.php +++ b/app/Http/Requests/Preview/PreviewInvoiceRequest.php @@ -92,10 +92,10 @@ class PreviewInvoiceRequest extends Request return $this->stubInvitation(); match($this->entity){ - 'invoice' => $invitation = Invoice::withTrashed()->where('invoice_id', $this->entity_id)->first(), - 'quote' => $invitation = Quote::withTrashed()->where('quote_id', $this->entity_id)->first(), - 'credit' => $invitation = Credit::withTrashed()->where('credit_id', $this->entity_id)->first(), - 'recurring_invoice' => $invitation = RecurringInvoice::withTrashed()->where('recurring_invoice_id', $this->entity_id)->first(), + 'invoice' => $invitation = InvoiceInvitation::withTrashed()->where('invoice_id', $this->entity_id)->first(), + 'quote' => $invitation = QuoteInvitation::withTrashed()->where('quote_id', $this->entity_id)->first(), + 'credit' => $invitation = CreditInvitation::withTrashed()->where('credit_id', $this->entity_id)->first(), + 'recurring_invoice' => $invitation = RecurringInvoiceInvitation::withTrashed()->where('recurring_invoice_id', $this->entity_id)->first(), }; if($invitation) From ec04f3fd1e71a4601e4961c8c7ef45dff107665e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 19 Oct 2023 08:53:28 +1100 Subject: [PATCH 3/4] Refactor for live previews --- app/Http/Controllers/PreviewController.php | 45 ++++++++++++------- .../Preview/PreviewInvoiceRequest.php | 20 ++++++++- .../RecurringInvoiceInvitationFactory.php | 30 +++++++++++++ 3 files changed, 78 insertions(+), 17 deletions(-) create mode 100644 database/factories/RecurringInvoiceInvitationFactory.php diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index 2797bae0eea1..15c5f1612a24 100644 --- a/app/Http/Controllers/PreviewController.php +++ b/app/Http/Controllers/PreviewController.php @@ -68,24 +68,34 @@ class PreviewController extends BaseController public function live(PreviewInvoiceRequest $request) { - $time = time(); - nlog($time); + $start = microtime(true); +nlog("1 ".$start); $invitation = $request->resolveInvitation(); + $client = $request->getClient(); + $settings = $client->getMergedSettings(); App::forgetInstance('translator'); $t = app('translator'); App::setLocale($invitation->contact->preferredLocale()); - $t->replace(Ninja::transformTranslations($invitation->{$request->entity}->client->getMergedSettings())); - - $html = new HtmlEngine($invitation); - $variables = $html->generateLabelsAndValues(); + $t->replace(Ninja::transformTranslations($settings)); +nlog("2 ".microtime(true)); + $entity_prop = str_replace("recurring_", "", $request->entity); $entity_obj = $invitation->{$request->entity}; - // if (! $invitation->{$request->entity}->id ?? true) { - // $invitation->{$request->entity}->service()->fillDefaults(); - // } + if(!$entity_obj->id) { + $entity_obj->design_id = intval($this->decodePrimaryKey($settings->{$entity_prop."_design_id"})); + $entity_obj->footer = $settings->{$entity_prop."_footer"}; + $entity_obj->terms = $settings->{$entity_prop."_terms"}; + $entity_obj->public_notes = $request->getClient()->public_notes; + $invitation->{$request->entity} = $entity_obj; + } + + $html = new HtmlEngine($invitation); + $html->settings = $settings; + $variables = $html->generateLabelsAndValues(); +nlog("3 ".microtime(true)); $design = \App\Models\Design::withTrashed()->find($entity_obj->design_id ?? 2); @@ -105,18 +115,18 @@ class PreviewController extends BaseController $state = [ 'template' => $template->elements([ - 'client' => $entity_obj->client, + 'client' => $client, 'entity' => $entity_obj, - 'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables, + 'pdf_variables' => (array) $settings->pdf_variables, '$product' => $design->design->product, 'variables' => $variables, ]), 'variables' => $variables, 'options' => [ - 'all_pages_header' => $entity_obj->client->getSetting('all_pages_header'), - 'all_pages_footer' => $entity_obj->client->getSetting('all_pages_footer'), + 'all_pages_header' => $client->getSetting('all_pages_header'), + 'all_pages_footer' => $client->getSetting('all_pages_footer'), ], - 'process_markdown' => $entity_obj->client->company->markdown_enabled, + 'process_markdown' => $client->company->markdown_enabled, ]; $maker = new PdfMaker($state); @@ -125,6 +135,8 @@ class PreviewController extends BaseController ->design($template) ->build(); +nlog("4 ".microtime(true)); + if (request()->query('html') == 'true') { return $maker->getCompiledHTML(); } @@ -153,9 +165,12 @@ class PreviewController extends BaseController $pdf = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle(); +nlog("5 ".microtime(true)); +nlog("total = ".microtime(true)-$start); + return response()->streamDownload(function () use ($pdf) { echo $pdf; - }, 'preview.pdf', ['Content-Type' => 'application/pdf','Cache-Control:' => 'no-cache']); + }, 'preview.pdf', ['Content-Disposition' => 'inline', 'Content-Type' => 'application/pdf','Cache-Control:' => 'no-cache', 'Server-Timing' => microtime(true)-$start]); } diff --git a/app/Http/Requests/Preview/PreviewInvoiceRequest.php b/app/Http/Requests/Preview/PreviewInvoiceRequest.php index 2188efa37c98..57bd0cf22ef0 100644 --- a/app/Http/Requests/Preview/PreviewInvoiceRequest.php +++ b/app/Http/Requests/Preview/PreviewInvoiceRequest.php @@ -15,9 +15,7 @@ use App\Models\Quote; use App\Models\Client; use App\Models\Credit; use App\Models\Invoice; -use App\Libraries\MultiDB; use App\Http\Requests\Request; -use App\Models\CompanyGateway; use App\Models\QuoteInvitation; use App\Utils\Traits\MakesHash; use Illuminate\Validation\Rule; @@ -34,6 +32,8 @@ class PreviewInvoiceRequest extends Request private string $entity_plural = ''; + private ?Client $client = null; + /** * Determine if the user is authorized to make this request. * @@ -104,9 +104,25 @@ class PreviewInvoiceRequest extends Request $invitation = $this->stubInvitation(); } + public function getClient(): ?Client + { + if(!$this->client) + $this->client = Client::query()->with('contacts', 'company', 'user')->withTrashed()->find($this->client_id); + + return $this->client; + } + + public function setClient(Client $client): self + { + $this->client = $client; + + return $this; + } + public function stubInvitation() { $client = Client::query()->with('contacts', 'company', 'user')->withTrashed()->find($this->client_id); + $this->setClient($client); $invitation = false; match($this->entity) { diff --git a/database/factories/RecurringInvoiceInvitationFactory.php b/database/factories/RecurringInvoiceInvitationFactory.php new file mode 100644 index 000000000000..fe7db151f11d --- /dev/null +++ b/database/factories/RecurringInvoiceInvitationFactory.php @@ -0,0 +1,30 @@ + Str::random(40), + ]; + } +} From a93ff3e1e5c2154626ecd2be9ffd4d93f5a35192 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 19 Oct 2023 10:44:19 +1100 Subject: [PATCH 4/4] Clean up --- app/Http/Controllers/PreviewController.php | 141 +++++++++++------- .../Requests/Preview/DesignPreviewRequest.php | 13 +- 2 files changed, 99 insertions(+), 55 deletions(-) diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index 15c5f1612a24..596024f30368 100644 --- a/app/Http/Controllers/PreviewController.php +++ b/app/Http/Controllers/PreviewController.php @@ -64,26 +64,39 @@ class PreviewController extends BaseController { Cache::pull("preview_".auth()->user()->id); } - - public function live(PreviewInvoiceRequest $request) + + /** + * Refactor - 2023-10-19 + * + * New method does not require Transactions. + * + * @param PreviewInvoiceRequest $request + * @return mixed + */ + public function live(PreviewInvoiceRequest $request): mixed { + if (Ninja::isHosted() && !in_array($request->getHost(), ['preview.invoicing.co','staging.invoicing.co'])) { + return response()->json(['message' => 'This server cannot handle this request.'], 400); + } + $start = microtime(true); -nlog("1 ".$start); + /** Build models */ $invitation = $request->resolveInvitation(); $client = $request->getClient(); $settings = $client->getMergedSettings(); + /** Set translations */ App::forgetInstance('translator'); $t = app('translator'); App::setLocale($invitation->contact->preferredLocale()); $t->replace(Ninja::transformTranslations($settings)); -nlog("2 ".microtime(true)); $entity_prop = str_replace("recurring_", "", $request->entity); $entity_obj = $invitation->{$request->entity}; + /** Update necessary objecty props */ if(!$entity_obj->id) { $entity_obj->design_id = intval($this->decodePrimaryKey($settings->{$entity_prop."_design_id"})); $entity_obj->footer = $settings->{$entity_prop."_footer"}; @@ -92,10 +105,10 @@ nlog("2 ".microtime(true)); $invitation->{$request->entity} = $entity_obj; } + /** Generate variables */ $html = new HtmlEngine($invitation); $html->settings = $settings; $variables = $html->generateLabelsAndValues(); -nlog("3 ".microtime(true)); $design = \App\Models\Design::withTrashed()->find($entity_obj->design_id ?? 2); @@ -135,15 +148,15 @@ nlog("3 ".microtime(true)); ->design($template) ->build(); -nlog("4 ".microtime(true)); + /** Generate HTML */ + $html = $maker->getCompiledHTML(true); - if (request()->query('html') == 'true') { - return $maker->getCompiledHTML(); - } + if (request()->query('html') == 'true') + return $html; //if phantom js...... inject here.. if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true)); + return (new Phantom)->convertHtmlToPdf($html); } /** @var \App\Models\User $user */ @@ -152,34 +165,69 @@ nlog("4 ".microtime(true)); if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { - $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); - + $pdf = (new NinjaPdf())->build($html); $numbered_pdf = $this->pageNumbering($pdf, $company); - if ($numbered_pdf) { + if ($numbered_pdf) $pdf = $numbered_pdf; - } return $pdf; } - $pdf = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle(); + $pdf = (new PreviewPdf($html, $company))->handle(); -nlog("5 ".microtime(true)); -nlog("total = ".microtime(true)-$start); + if (Ninja::isHosted()) { + LightLogs::create(new LivePreview()) + ->increment() + ->batch(); + } + /** Return PDF */ return response()->streamDownload(function () use ($pdf) { echo $pdf; - }, 'preview.pdf', ['Content-Disposition' => 'inline', 'Content-Type' => 'application/pdf','Cache-Control:' => 'no-cache', 'Server-Timing' => microtime(true)-$start]); + }, 'preview.pdf', [ + 'Content-Disposition' => 'inline', + 'Content-Type' => 'application/pdf', + 'Cache-Control:' => 'no-cache', + 'Server-Timing' => microtime(true)-$start + ]); } + + /** + * Returns the mocked PDF for the invoice design preview. + * + * Only used in Settings > Invoice Design as a general overview + * + * @param DesignPreviewRequest $request + * @return mixed + */ + public function design(DesignPreviewRequest $request): mixed + { + $start = microtime(true); + + /** @var \App\Models\User $user */ + $user = auth()->user(); + + /** @var \App\Models\Company $company */ + $company = $user->company(); + + $pdf = (new PdfMock($request->all(), $company))->build()->getPdf(); + + $response = Response::make($pdf, 200); + $response->header('Content-Type', 'application/pdf'); + $response->header('Server-Timing', microtime(true)-$start); + + return $response; + } + /** * Returns a template filled with entity variables. - * + * + * Used in the Custom Designer to preview design changes * @return mixed */ - public function show() { if (request()->has('entity') && @@ -187,6 +235,7 @@ nlog("total = ".microtime(true)-$start); ! empty(request()->input('entity')) && ! empty(request()->input('entity_id')) && request()->has('body')) { + $design_object = json_decode(json_encode(request()->input('design'))); if (! is_object($design_object)) { @@ -243,56 +292,50 @@ nlog("total = ".microtime(true)-$start); return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true)); } - /** @var \App\Models\User $user */ $user = auth()->user(); - $company = $user->company(); 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, $company); - - if ($numbered_pdf) { + if ($numbered_pdf) $pdf = $numbered_pdf; - } return $pdf; + } - $file_path = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle(); + $pdf = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle(); + + return response()->streamDownload(function () use ($pdf) { + echo $pdf; + }, 'preview.pdf', [ + 'Content-Disposition' => 'inline', + 'Content-Type' => 'application/pdf', + 'Cache-Control:' => 'no-cache', + ]); + - return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true); } return $this->blankEntity(); } - public function design(DesignPreviewRequest $request) - { - /** @var \App\Models\User $user */ - $user = auth()->user(); - - /** @var \App\Models\Company $company */ - $company = $user->company(); - - $pdf = (new PdfMock($request->all(), $company))->build()->getPdf(); - - $response = Response::make($pdf, 200); - $response->header('Content-Type', 'application/pdf'); - - return $response; - } - + + + /** + * @deprecated due to usage of transactions + * + * @param mixed $request + * @return void + */ public function livex(PreviewInvoiceRequest $request) { - // if(Cache::has("preview_".auth()->user()->id)) - // return response()->json(['message' => 'Please wait a few seconds before trying again, this many requests are not good.'], 400); - - nlog("should not see a lot of these"); + if(Cache::has("preview_".auth()->user()->id)) + return response()->json(['message' => 'Please wait a few seconds before trying again, this many requests are not good.'], 400); if (Ninja::isHosted() && !in_array($request->getHost(), ['preview.invoicing.co','staging.invoicing.co'])) { return response()->json(['message' => 'This server cannot handle this request.'], 400); @@ -463,7 +506,6 @@ nlog("total = ".microtime(true)-$start); $response->header('Content-Type', 'application/pdf'); $response->header('Server-Timing', microtime(true)-$start); - nlog("returning a response"); $this->purgeCache(); return $response; } @@ -652,7 +694,6 @@ nlog("total = ".microtime(true)-$start); $response = Response::make($file_path, 200); $response->header('Content-Type', 'application/pdf'); - return $response; } } diff --git a/app/Http/Requests/Preview/DesignPreviewRequest.php b/app/Http/Requests/Preview/DesignPreviewRequest.php index f3706c9f5b20..ac2b72fb1ad9 100644 --- a/app/Http/Requests/Preview/DesignPreviewRequest.php +++ b/app/Http/Requests/Preview/DesignPreviewRequest.php @@ -32,11 +32,14 @@ class DesignPreviewRequest extends Request */ public function authorize() : bool { - return auth()->user()->can('create', Invoice::class) || - auth()->user()->can('create', Quote::class) || - auth()->user()->can('create', RecurringInvoice::class) || - auth()->user()->can('create', Credit::class) || - auth()->user()->can('create', PurchaseOrder::class); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->can('create', Invoice::class) || + $user->can('create', Quote::class) || + $user->can('create', RecurringInvoice::class) || + $user->can('create', Credit::class) || + $user->can('create', PurchaseOrder::class); } public function rules()