2025-12-01 16:23:45 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Traits\Controllers;
|
|
|
|
|
|
2025-12-02 09:48:28 +01:00
|
|
|
use App\Enums\ExportFormat;
|
2025-12-01 16:23:45 +01:00
|
|
|
use App\Lib\Helpers\FileHelper;
|
|
|
|
|
use Barryvdh\DomPDF\Facade\Pdf;
|
|
|
|
|
use Illuminate\Support\Facades\File;
|
|
|
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
|
|
|
|
use ZipArchive;
|
|
|
|
|
|
|
|
|
|
trait ExportDatas
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* Check if the export needs to be in a .zip file.
|
|
|
|
|
* .zip is needed for :
|
|
|
|
|
* - csv with any files
|
|
|
|
|
* - pdf with files other than pictures
|
|
|
|
|
*
|
2025-12-02 09:48:28 +01:00
|
|
|
* @param ExportFormat $format Requested format.
|
2025-12-03 10:31:18 +01:00
|
|
|
* @param \Illuminate\Database\Eloquent\Collection $models Collection of models to export.
|
2025-12-01 16:23:45 +01:00
|
|
|
* @param string $fileFieldName Name of the field containing the file.
|
|
|
|
|
* @return boolean
|
|
|
|
|
*/
|
|
|
|
|
public function needsZip(
|
2025-12-02 09:48:28 +01:00
|
|
|
ExportFormat $format,
|
|
|
|
|
\Illuminate\Database\Eloquent\Collection $models,
|
2025-12-01 16:23:45 +01:00
|
|
|
string $fileFieldName = 'file'
|
|
|
|
|
): bool {
|
2025-12-02 09:48:28 +01:00
|
|
|
if ($format->equals(ExportFormat::csv)) {
|
|
|
|
|
return $models->whereNotNull($fileFieldName)->pluck($fileFieldName)->isNotEmpty();
|
|
|
|
|
} elseif ($format->equals(ExportFormat::pdf)) {
|
|
|
|
|
return $models
|
2025-12-01 16:23:45 +01:00
|
|
|
->whereNotNull($fileFieldName)
|
|
|
|
|
->whereNotIn('file_extension', ['jpg', 'jpeg', 'png'])
|
|
|
|
|
->isNotEmpty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return a StreamedResponse with a zip file.
|
|
|
|
|
*
|
2025-12-02 09:48:28 +01:00
|
|
|
* @param ExportFormat $format
|
|
|
|
|
* @param \Illuminate\Database\Eloquent\Collection $models
|
2025-12-01 16:23:45 +01:00
|
|
|
* @param string $file
|
|
|
|
|
* @return \Symfony\Component\HttpFoundation\StreamedResponse
|
|
|
|
|
*/
|
|
|
|
|
public function returnExportZip(
|
2025-12-02 09:48:28 +01:00
|
|
|
ExportFormat $format,
|
|
|
|
|
\Illuminate\Database\Eloquent\Collection $models,
|
2025-12-01 16:23:45 +01:00
|
|
|
string $file
|
|
|
|
|
): \Symfony\Component\HttpFoundation\StreamedResponse {
|
2025-12-02 09:48:28 +01:00
|
|
|
return new StreamedResponse(function () use ($file, $models, $format) {
|
2025-12-01 16:23:45 +01:00
|
|
|
// Create zip.
|
|
|
|
|
$zip = new ZipArchive();
|
|
|
|
|
$zipName = $this->createExportZip($zip, $file, $format);
|
|
|
|
|
|
|
|
|
|
// Add attachments.
|
2025-12-02 09:48:28 +01:00
|
|
|
$this->addAttachmentsToZip($zip, $models, $format);
|
2025-12-01 16:23:45 +01:00
|
|
|
|
|
|
|
|
$zip->close();
|
|
|
|
|
readfile($zipName);
|
|
|
|
|
unlink($zipName);
|
|
|
|
|
}, 200, $this->getFileHeaders($format, true));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create the zip folder for the export with the main file inside.
|
|
|
|
|
*
|
2025-12-02 09:48:28 +01:00
|
|
|
* @param ZipArchive $zip
|
|
|
|
|
* @param string $file
|
|
|
|
|
* @param ExportFormat $format
|
2025-12-01 16:23:45 +01:00
|
|
|
* @return string|false
|
|
|
|
|
*
|
|
|
|
|
* @throws \Exception If the zip opening failed.
|
|
|
|
|
*/
|
2025-12-02 09:48:28 +01:00
|
|
|
private function createExportZip(ZipArchive &$zip, string $file, ExportFormat $format): string|false
|
2025-12-01 16:23:45 +01:00
|
|
|
{
|
2025-12-16 19:59:05 +01:00
|
|
|
$tempDir = Storage::disk('private')->path('export');
|
|
|
|
|
if (!file_exists($tempDir)) {
|
|
|
|
|
Storage::disk('private')->makeDirectory('export', 0755, true);
|
|
|
|
|
}
|
2026-03-31 15:25:55 +02:00
|
|
|
|
2025-12-16 19:59:05 +01:00
|
|
|
$zipName = tempnam($tempDir, 'zip');
|
|
|
|
|
|
2025-12-01 16:23:45 +01:00
|
|
|
if (!$zip->open($zipName, ZipArchive::OVERWRITE)) {
|
|
|
|
|
throw new \Exception(__('Couldn\'t open zip file.'));
|
|
|
|
|
};
|
|
|
|
|
$zip->addFromString($format->fileName(), $file);
|
|
|
|
|
|
|
|
|
|
return $zipName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add attachments to zip
|
|
|
|
|
*
|
|
|
|
|
* @param ZipArchive $zip
|
2025-12-02 09:48:28 +01:00
|
|
|
* @param \Illuminate\Database\Eloquent\Collection $models
|
|
|
|
|
* @param ExportFormat $format
|
2025-12-01 16:23:45 +01:00
|
|
|
* @param string $fileFieldName Name of the field containing the file.
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
private function addAttachmentsToZip(
|
|
|
|
|
ZipArchive &$zip,
|
2025-12-02 09:48:28 +01:00
|
|
|
\Illuminate\Database\Eloquent\Collection $models,
|
|
|
|
|
ExportFormat $format,
|
2025-12-01 16:23:45 +01:00
|
|
|
string $fileFieldName = 'file'
|
|
|
|
|
) {
|
2025-12-02 09:48:28 +01:00
|
|
|
$modelsWithFilesToZip = $models->whereNotNull($fileFieldName)
|
|
|
|
|
->when($format->equals(ExportFormat::pdf), function ($models) {
|
|
|
|
|
return $models->whereNotIn('file_extension', ['jpg', 'jpeg', 'png']);
|
2025-12-01 16:23:45 +01:00
|
|
|
});
|
2025-12-02 09:48:28 +01:00
|
|
|
foreach ($modelsWithFilesToZip as $model) {
|
|
|
|
|
$filePath = FileHelper::getPrivateFilePath($model->{$fileFieldName});
|
2025-12-01 16:23:45 +01:00
|
|
|
if (Storage::disk('private')->exists($filePath)) {
|
|
|
|
|
$zip->addFile(
|
|
|
|
|
Storage::disk('private')->path($filePath),
|
2025-12-02 09:48:28 +01:00
|
|
|
$model->export_file_name
|
2025-12-01 16:23:45 +01:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get file headers to use with StreamedResponse.
|
|
|
|
|
*
|
2025-12-02 09:48:28 +01:00
|
|
|
* @param ExportFormat $format
|
|
|
|
|
* @param boolean $isZip
|
2025-12-01 16:23:45 +01:00
|
|
|
* @return array
|
|
|
|
|
*/
|
2025-12-02 09:48:28 +01:00
|
|
|
public function getFileHeaders(ExportFormat $format, bool $isZip = false): array
|
2025-12-01 16:23:45 +01:00
|
|
|
{
|
|
|
|
|
if ($isZip) {
|
|
|
|
|
return [
|
|
|
|
|
'Content-Type' => 'application/zip',
|
|
|
|
|
'Content-Disposition' => 'attachment; filename="export.zip"',
|
|
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
return $format->fileHeaders();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Transform Array into CSV.
|
|
|
|
|
*
|
|
|
|
|
* @param array $lines
|
|
|
|
|
* @param string $delimiter
|
|
|
|
|
* @param string $enclosure
|
|
|
|
|
* @param string $escape_char
|
|
|
|
|
* @return string|false
|
|
|
|
|
*/
|
|
|
|
|
private function array2csv(
|
|
|
|
|
array $lines,
|
|
|
|
|
string $delimiter = ',',
|
|
|
|
|
string $enclosure = '"',
|
|
|
|
|
string $escape_char = "\\"
|
|
|
|
|
) {
|
|
|
|
|
$f = fopen('php://temp', 'r+');
|
|
|
|
|
|
|
|
|
|
// UTF-8 BOM.
|
|
|
|
|
$BOM = "\xEF\xBB\xBF";
|
|
|
|
|
fwrite($f, $BOM);
|
|
|
|
|
|
|
|
|
|
foreach ($lines as $item) {
|
|
|
|
|
fputcsv($f, $item, $delimiter, $enclosure, $escape_char);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rewind($f);
|
|
|
|
|
$csvContent = stream_get_contents($f);
|
|
|
|
|
fclose($f);
|
|
|
|
|
|
|
|
|
|
return $csvContent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-12-02 09:48:28 +01:00
|
|
|
* Generate CSV with given models.
|
2025-12-01 16:23:45 +01:00
|
|
|
*
|
|
|
|
|
* @param \Illuminate\Database\Eloquent\Collection $models
|
|
|
|
|
* @return string|false
|
|
|
|
|
*/
|
|
|
|
|
private function generateCsv(\Illuminate\Database\Eloquent\Collection $models)
|
|
|
|
|
{
|
|
|
|
|
// CSV Header.
|
|
|
|
|
$class = get_class($models->first());
|
|
|
|
|
$csvlines[] = $class::getCsvHeaders();
|
|
|
|
|
|
|
|
|
|
foreach ($models as $model) {
|
|
|
|
|
$csvlines[] = $model->getFormattedCsvLine();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->array2csv($csvlines, ';');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Generate PDF with models datas.
|
|
|
|
|
*
|
|
|
|
|
* @param \Illuminate\Database\Eloquent\Collection $models
|
2025-12-02 09:48:28 +01:00
|
|
|
* @param string $fileFieldName
|
2025-12-01 16:23:45 +01:00
|
|
|
* @return \Barryvdh\DomPDF\PDF
|
|
|
|
|
*/
|
2025-12-02 09:48:28 +01:00
|
|
|
private function generatePdf(
|
|
|
|
|
\Illuminate\Database\Eloquent\Collection $models,
|
|
|
|
|
string $fileFieldName = 'file'
|
|
|
|
|
): \Barryvdh\DomPDF\PDF {
|
2025-12-01 16:23:45 +01:00
|
|
|
// Convert pictures to base64.
|
2025-12-02 09:48:28 +01:00
|
|
|
foreach ($models->whereIn('file_extension', ['jpg', 'jpeg', 'png']) as $model) {
|
|
|
|
|
$filePath = FileHelper::getPrivateFilePath($model->{$fileFieldName});
|
|
|
|
|
$model->fileBase64 = sprintf(
|
2025-12-01 16:23:45 +01:00
|
|
|
'data:%s;base64,%s',
|
|
|
|
|
File::mimeType(Storage::disk('private')->path($filePath)),
|
|
|
|
|
base64_encode(Storage::disk('private')->get($filePath))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create temp folder if it doesn't exists. Needed to allow base64 pictures to be displayed in pdf.
|
|
|
|
|
if (!Storage::disk('private')->exists('temp')) {
|
|
|
|
|
Storage::disk('private')->makeDirectory('temp');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$modelClassName = class_basename($models->first());
|
|
|
|
|
return Pdf::setOptions(['tempDir' => Storage::disk('private')->path('temp')])
|
|
|
|
|
->loadView(
|
|
|
|
|
sprintf(
|
|
|
|
|
'export.pdf',
|
|
|
|
|
str($modelClassName)->lower()->plural()
|
|
|
|
|
),
|
|
|
|
|
[
|
|
|
|
|
'models' => $models,
|
|
|
|
|
'modelName' => str($modelClassName)->lower()
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|