From d862ee05e95c1ee100e478c05baebe8b16119ae3 Mon Sep 17 00:00:00 2001 From: Darko Gjorgjijoski <5760249+gdarko@users.noreply.github.com> Date: Mon, 13 Jan 2025 01:20:13 +0100 Subject: [PATCH] Refactor Custom Invoice/Estimate PDF Templates (#277) * Add utility class for managing templates * Register custom pdf template views location * Update the make:template command to make use of PdfTemplateUtils * Update PDF invoice/estimate template controllers * Register pdf_templates filesystem disk * Remove unused leftovers * Reformat with pint --- .../Commands/CreateTemplateCommand.php | 53 +++++-- .../Estimate/EstimateTemplatesController.php | 6 +- .../Invoice/InvoiceTemplatesController.php | 10 +- app/Models/Estimate.php | 26 +--- app/Models/Invoice.php | 25 +--- app/Providers/AppServiceProvider.php | 3 + app/Space/PdfTemplateUtils.php | 140 ++++++++++++++++++ config/filesystems.php | 5 + storage/app/.gitignore | 1 + storage/app/templates/.gitignore | 3 + storage/app/templates/pdf/.gitignore | 2 + 11 files changed, 221 insertions(+), 53 deletions(-) create mode 100644 app/Space/PdfTemplateUtils.php create mode 100644 storage/app/templates/.gitignore create mode 100644 storage/app/templates/pdf/.gitignore diff --git a/app/Console/Commands/CreateTemplateCommand.php b/app/Console/Commands/CreateTemplateCommand.php index a4f22104..18c2b5b0 100644 --- a/app/Console/Commands/CreateTemplateCommand.php +++ b/app/Console/Commands/CreateTemplateCommand.php @@ -2,8 +2,11 @@ namespace App\Console\Commands; +use App\Space\PdfTemplateUtils; use Illuminate\Console\Command; +use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Str; class CreateTemplateCommand extends Command { @@ -37,25 +40,53 @@ class CreateTemplateCommand extends Command public function handle(): int { $templateName = $this->argument('name'); - $type = $this->option('type'); + $templateType = $this->option('type'); - if (! $type) { - $type = $this->choice('Create a template for?', ['invoice', 'estimate']); + if (! $templateType) { + $templateType = $this->choice('Create a template for?', ['invoice', 'estimate']); } - if (Storage::disk('views')->exists("/app/pdf/{$type}/{$templateName}.blade.php")) { + if (PdfTemplateUtils::customTemplateFileExists($templateType, sprintf('%s.blade.php', $templateName))) { $this->info('Template with given name already exists.'); - return 0; + return self::INVALID; } - Storage::disk('views')->copy("/app/pdf/{$type}/{$type}1.blade.php", "/app/pdf/{$type}/{$templateName}.blade.php"); - copy(resource_path("static/img/PDF/{$type}1.png"), resource_path("static/img/PDF/{$templateName}.png")); + if (! PdfTemplateUtils::toCustomTemplateMarkupFile( + Str::replace( + sprintf('app.pdf.%s', $templateType), + sprintf('pdf_templates::%s', $templateType), + Storage::disk('views')->get("/app/pdf/{$templateType}/{$templateType}1.blade.php"), + ), + $templateType, + $templateName + )) { + $this->error(sprintf('Unable to create %s template.', ucfirst($templateType))); - $path = resource_path("views/app/pdf/{$type}/{$templateName}.blade.php"); - $type = ucfirst($type); - $this->info("{$type} Template created successfully at ".$path); + return self::FAILURE; + } - return 0; + PdfTemplateUtils::toCustomTemplateImageFile( + File::get(resource_path("static/img/PDF/{$templateType}1.png")), + $templateType, + $templateName, + ); + + if (! PdfTemplateUtils::customTemplateFileExists($templateType, 'partials/table.blade.php')) { + PdfTemplateUtils::toCustomTemplateFile( + Storage::disk('views')->get("/app/pdf/{$templateType}/partials/table.blade.php"), + $templateType, + 'partials/table.blade.php' + ); + } + + $this->info( + sprintf('%s Template created successfully at %s', + ucfirst($templateType), + PdfTemplateUtils::getCustomTemplateFilePath($templateType, sprintf('%s.blade.php', $templateName)) + ) + ); + + return self::SUCCESS; } } diff --git a/app/Http/Controllers/V1/Admin/Estimate/EstimateTemplatesController.php b/app/Http/Controllers/V1/Admin/Estimate/EstimateTemplatesController.php index 6a4b1aa8..d76cedd4 100644 --- a/app/Http/Controllers/V1/Admin/Estimate/EstimateTemplatesController.php +++ b/app/Http/Controllers/V1/Admin/Estimate/EstimateTemplatesController.php @@ -4,6 +4,8 @@ namespace App\Http\Controllers\V1\Admin\Estimate; use App\Http\Controllers\Controller; use App\Models\Estimate; +use App\Space\PdfTemplateUtils; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; class EstimateTemplatesController extends Controller @@ -11,13 +13,13 @@ class EstimateTemplatesController extends Controller /** * Handle the incoming request. * - * @return \Illuminate\Http\Response + * @return JsonResponse */ public function __invoke(Request $request) { $this->authorize('viewAny', Estimate::class); - $estimateTemplates = Estimate::estimateTemplates(); + $estimateTemplates = PdfTemplateUtils::getFormattedTemplates('estimate'); return response()->json([ 'estimateTemplates' => $estimateTemplates, diff --git a/app/Http/Controllers/V1/Admin/Invoice/InvoiceTemplatesController.php b/app/Http/Controllers/V1/Admin/Invoice/InvoiceTemplatesController.php index 2bc845aa..c618d93d 100644 --- a/app/Http/Controllers/V1/Admin/Invoice/InvoiceTemplatesController.php +++ b/app/Http/Controllers/V1/Admin/Invoice/InvoiceTemplatesController.php @@ -4,6 +4,9 @@ namespace App\Http\Controllers\V1\Admin\Invoice; use App\Http\Controllers\Controller; use App\Models\Invoice; +use App\Space\PdfTemplateUtils; +use Illuminate\Auth\Access\AuthorizationException; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; class InvoiceTemplatesController extends Controller @@ -11,13 +14,16 @@ class InvoiceTemplatesController extends Controller /** * Handle the incoming request. * - * @return \Illuminate\Http\Response + * + * @return JsonResponse + * + * @throws AuthorizationException */ public function __invoke(Request $request) { $this->authorize('viewAny', Invoice::class); - $invoiceTemplates = Invoice::invoiceTemplates(); + $invoiceTemplates = PdfTemplateUtils::getFormattedTemplates('invoice'); return response()->json([ 'invoiceTemplates' => $invoiceTemplates, diff --git a/app/Models/Estimate.php b/app/Models/Estimate.php index 747ea60b..ab2fd66a 100644 --- a/app/Models/Estimate.php +++ b/app/Models/Estimate.php @@ -5,6 +5,7 @@ namespace App\Models; use App; use App\Mail\SendEstimateMail; use App\Services\SerialNumberFormatter; +use App\Space\PdfTemplateUtils; use App\Traits\GeneratesPdfTrait; use App\Traits\HasCustomFieldsTrait; use Barryvdh\DomPDF\Facade\Pdf as PDF; @@ -14,8 +15,6 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphMany; -use Illuminate\Support\Facades\Storage; -use Illuminate\Support\Facades\Vite; use Illuminate\Support\Str; use Spatie\MediaLibrary\HasMedia; use Spatie\MediaLibrary\InteractsWithMedia; @@ -424,11 +423,14 @@ class Estimate extends Model implements HasMedia 'taxes' => $taxes, ]); + $template = PdfTemplateUtils::findFormattedTemplate('estimate', $estimateTemplate, ''); + $templatePath = $template['custom'] ? sprintf('pdf_templates::estimate.%s', $estimateTemplate) : sprintf('app.pdf.estimate.%s', $estimateTemplate); + if (request()->has('preview')) { - return view('app.pdf.estimate.'.$estimateTemplate); + return view($templatePath); } - return PDF::loadView('app.pdf.estimate.'.$estimateTemplate); + return PDF::loadView($templatePath); } public function getCompanyAddress() @@ -499,27 +501,13 @@ class Estimate extends Model implements HasMedia ]; } - public static function estimateTemplates() - { - $templates = Storage::disk('views')->files('/app/pdf/estimate'); - $estimateTemplates = []; - - foreach ($templates as $key => $template) { - $templateName = Str::before(basename($template), '.blade.php'); - $estimateTemplates[$key]['name'] = $templateName; - $estimateTemplates[$key]['path'] = Vite::asset('resources/static/img/PDF/'.$templateName.'.png'); - } - - return $estimateTemplates; - } - public function getInvoiceTemplateName() { $templateName = Str::replace('estimate', 'invoice', $this->template_name); $name = []; - foreach (Invoice::invoiceTemplates() as $template) { + foreach (PdfTemplateUtils::getFormattedTemplates('invoice') as $template) { $name[] = $template['name']; } diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 29cfd437..8f27c010 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -5,7 +5,7 @@ namespace App\Models; use App; use App\Mail\SendInvoiceMail; use App\Services\SerialNumberFormatter; -use App\Space\ImageUtils; +use App\Space\PdfTemplateUtils; use App\Traits\GeneratesPdfTrait; use App\Traits\HasCustomFieldsTrait; use Barryvdh\DomPDF\Facade\Pdf as PDF; @@ -15,8 +15,6 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphMany; -use Illuminate\Support\Facades\Storage; -use Illuminate\Support\Str; use Nwidart\Modules\Facades\Module; use Spatie\MediaLibrary\HasMedia; use Spatie\MediaLibrary\InteractsWithMedia; @@ -582,11 +580,14 @@ class Invoice extends Model implements HasMedia 'taxes' => $taxes, ]); + $template = PdfTemplateUtils::findFormattedTemplate('invoice', $invoiceTemplate, ''); + $templatePath = $template['custom'] ? sprintf('pdf_templates::invoice.%s', $invoiceTemplate) : sprintf('app.pdf.invoice.%s', $invoiceTemplate); + if (request()->has('preview')) { - return view('app.pdf.invoice.'.$invoiceTemplate); + return view($templatePath); } - return PDF::loadView('app.pdf.invoice.'.$invoiceTemplate); + return PDF::loadView($templatePath); } public function getEmailAttachmentSetting() @@ -657,20 +658,6 @@ class Invoice extends Model implements HasMedia ]; } - public static function invoiceTemplates() - { - $templates = Storage::disk('views')->files('/app/pdf/invoice'); - $invoiceTemplates = []; - - foreach ($templates as $key => $template) { - $templateName = Str::before(basename($template), '.blade.php'); - $invoiceTemplates[$key]['name'] = $templateName; - $invoiceTemplates[$key]['path'] = ImageUtils::toBase64Src(resource_path('static/img/PDF/'.$templateName.'.png')); - } - - return $invoiceTemplates; - } - public function addInvoicePayment($amount) { $this->due_amount += $amount; diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index ff36060f..a3e2174c 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -25,6 +25,7 @@ use Illuminate\Support\Facades\Broadcast; use Illuminate\Support\ServiceProvider; use Silber\Bouncer\Database\Models as BouncerModels; use Silber\Bouncer\Database\Role; +use View; class AppServiceProvider extends ServiceProvider { @@ -58,6 +59,8 @@ class AppServiceProvider extends ServiceProvider Gate::policy(Role::class, RolePolicy::class); + View::addNamespace('pdf_templates', storage_path('app/templates/pdf')); + $this->bootAuth(); $this->bootBroadcast(); diff --git a/app/Space/PdfTemplateUtils.php b/app/Space/PdfTemplateUtils.php new file mode 100644 index 00000000..1359730b --- /dev/null +++ b/app/Space/PdfTemplateUtils.php @@ -0,0 +1,140 @@ + $file, + 'custom' => false, + ]; + }, Storage::disk('views')->files(sprintf('/app/pdf/%s', $templateType))); + + $files_custom = array_map(function ($file) { + return [ + 'path' => $file, + 'custom' => true, + ]; + }, Storage::disk('pdf_templates')->files(sprintf('/%s', $templateType))); + + $files = array_merge($files_native, $files_custom); + $files = array_filter($files, function ($file) { + return Str::endsWith($file['path'], '.blade.php'); + }); + + return array_map(function ($file) use ($templateType, $imageFormat) { + $templateName = Str::before(basename($file['path']), '.blade.php'); + + if ($file['custom']) { + $imagePath = self::getCustomTemplateFilePath($templateType, sprintf('%s.png', $templateName)); + $isCustomTemplate = true; + } else { + $imagePath = resource_path('static/img/PDF/'.$templateName.'.png'); + $isCustomTemplate = false; + } + + if (empty($imageFormat)) { + $imageValue = ''; + } elseif ($imageFormat == 'path') { + $imageValue = $imagePath; + } else { + $imageValue = File::exists($imagePath) ? ImageUtils::toBase64Src($imagePath) : ''; + } + + return [ + 'name' => $templateName, + 'path' => $imageValue, + 'custom' => $isCustomTemplate, + ]; + }, $files); + } + + /** + * Returns custom template path + * + * @param string $fileName + */ + public static function getCustomTemplateFilePath($templateType, $fileName = ''): string + { + $path = ! empty($fileName) ? sprintf('/%s/%s', $templateType, $fileName) : sprintf('/%s/', $templateType); + + return Storage::disk('pdf_templates')->path($path); + } + + /** + * Check if custom template exists. + * + * @param $templateName + * @return string + */ + public static function customTemplateFileExists($templateType, $fileName) + { + return Storage::disk('pdf_templates')->exists(sprintf('/%s/%s', $templateType, $fileName)); + } + + /** + * Save template markup file + * + * @return bool|string + */ + public static function toCustomTemplateMarkupFile($contents, $templateType, $templateName) + { + return self::toCustomTemplateFile($contents, $templateType, $templateName.'.blade.php'); + } + + /** + * Save template image file + * + * + * @return bool|string + */ + public static function toCustomTemplateImageFile($contents, $templateType, $templateName, $imageType = 'png') + { + return self::toCustomTemplateFile($contents, $templateType, $templateName.'.'.$imageType); + } + + /** + * Save file contents into a template file of specific template type. + * + * + * @return bool|string + */ + public static function toCustomTemplateFile($contents, $templateType, $fileName) + { + return Storage::disk('pdf_templates')->put( + sprintf('/%s/%s', $templateType, $fileName), + $contents + ); + } +} diff --git a/config/filesystems.php b/config/filesystems.php index 2ffeec09..a92d8e13 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -55,6 +55,11 @@ return [ 'driver' => 'local', 'root' => resource_path('views'), ], + + 'pdf_templates' => [ + 'driver' => 'local', + 'root' => storage_path('app/templates/pdf'), + ], ], ]; diff --git a/storage/app/.gitignore b/storage/app/.gitignore index 8f4803c0..711d1cbd 100644 --- a/storage/app/.gitignore +++ b/storage/app/.gitignore @@ -1,3 +1,4 @@ * !public/ +!templates/ !.gitignore diff --git a/storage/app/templates/.gitignore b/storage/app/templates/.gitignore new file mode 100644 index 00000000..2e581302 --- /dev/null +++ b/storage/app/templates/.gitignore @@ -0,0 +1,3 @@ +* +!pdf/ +!.gitignore diff --git a/storage/app/templates/pdf/.gitignore b/storage/app/templates/pdf/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/app/templates/pdf/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore