authorizeResource(Claim::class);
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \App\Models\User $userModel */
$userModel = auth('backend')->user();
/** @var \Illuminate\Database\Eloquent\Builder $query */
$query = Claim::query()
->when(
$request->filled('brand'),
fn($q) => $q->whereHas('brand', fn($q) => $q->where('id', $request->brand))
)->when(
$userModel->role->equals(RoleEnum::marketing),
fn($q) => $q->whereHas('brand', fn($q) => $q->whereIn('id', $userModel->brands->modelKeys()))
)
->when(
$request->filled('subject'),
fn($q) => $q->whereHas('claimSubject', fn($q) => $q->where('id', $request->subject))
)
->when(
$request->filled('status'),
fn($q) => $q->whereIn('status', $request->status),
fn($q) => $q->whereNotIn('status', [ClaimStatus::treated, ClaimStatus::refused])
);
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$query,
$search,
null,
'reference',
'first_name',
'last_name',
'email',
);
}
$searchFields = \implode(', ', [
trans('validation.custom.reference'),
trans('validation.attributes.first_name'),
trans('validation.attributes.last_name'),
trans('validation.attributes.email'),
]);
/** Sort columns with a query */
$this->sortQuery($query);
/** Custom pagination */
$claimModels = $query->simplePaginate($this->getPagination());
$claimSubjectModels = ClaimSubject::query()
->when(
$request->filled('brand'),
fn($q) => $q->whereHas('brand', fn($q) => $q->where('id', $request->brand)),
fn($q) => $q->whereHas('brand', fn($q) => $q->whereIn('id', $userModel->brands()->pluck('id')))
)
->get();
return view('bo.pages.claims.index', compact('claimModels', 'search', 'searchFields', 'claimSubjectModels'));
}
/**
* Show the form for editing the specified resource.
*
* @param App\Models\Claim $claim
* @return \Illuminate\Contracts\View\View
*/
public function edit(Claim $claim): \Illuminate\Contracts\View\View
{
$customerClaimModels = !is_null($claim->customer) ? Claim::query()
->whereNotNull('customer_id')
->whereHas('brand', fn($q) => $q->whereKey($claim->brand->getKey()))
->whereHas('customer', fn($q) => $q->whereKey($claim->customer->getKey()))
->whereNot('id', $claim->getKey())
->whereNot('status', ClaimStatus::treated)->paginate($this->jsonItemPerPagination) : collect();
$brandModels = CollectionHelper::paginate(
Brand::getUserAssociatedBrands(),
$this->jsonItemPerPagination,
);
$claimMessageModels = ClaimMessage::getAllModelsFromClaim($claim->getKey());
$claimSubjectModels = CollectionHelper::paginate(
ClaimSubject::getAllModelsFromBrand($claim->brand->getKey()),
$this->jsonItemPerPagination,
);
$claimTemplateModels = CollectionHelper::paginate(
ClaimMailTemplate::getAllModelsFromBrand($claim->brand->getKey()),
$this->jsonItemPerPagination,
);
$customerModel = CustomerInfo::query()->where('hashcode', $claim->hashcode)->first();
return view('bo.pages.claims.edit', [
'claimModel' => $claim->load(['brand', 'claimSubject']),
'customerClaimModels' => $customerClaimModels,
'brandModels' => $brandModels,
'claimMessageModels' => $claimMessageModels,
'claimSubjectModels' => $claimSubjectModels,
'claimTemplateModels' => $claimTemplateModels,
'customerModel' => $customerModel,
]);
}
/**
* Update the specified resource.
*
* @param \App\Http\Requests\Bo\Claims\UpdateClaimRequest $request
* @param \App\Models\Claim $claim
* @return \Illuminate\Http\JsonResponse
*/
public function update(UpdateClaimRequest $request, Claim $claim): \Illuminate\Http\JsonResponse
{
return DB::transaction(function () use ($request, $claim) {
$claim->fill($request->validated());
if ($claim->saveOrFail()) {
$claimMessageModels = ClaimMessage::getAllModelsFromClaim($claim->getKey());
return response()->json($claimMessageModels);
}
return response()->json([], 500);
});
}
/**
* Send the claim answer.
*
* @param \App\Http\Requests\Bo\Claims\SendClaimAnswerRequest $request
* @param \App\Models\Claim $claim
* @return \Illuminate\Http\JsonResponse
*/
public function answer(SendClaimAnswerRequest $request, Claim $claim): \Illuminate\Http\JsonResponse
{
return DB::transaction(function () use ($request, $claim) {
/** @var \App\Models\User $userModel */
$userModel = auth()->user();
$enum = ClaimResponseType::manual;
$claim->response_type = $enum;
$claim->status = ClaimStatus::treated;
if ($claim->saveOrFail()) {
// Send the response to the claim author.
Mail::to($claim->email)->send(new ClaimAnswerMail($claim, $request->validated()['content']));
// Create custom event with content message.
// @phpcs:disable Generic.Files.LineLength.TooLong
$content = sprintf(
"%s %s %s %s
%s",
$userModel->first_name,
$userModel->last_name,
trans('a modifié :field en', ['field' => trans('validation.custom.response_type')]),
$claim->response_type->label(),
clean($request->validated()['content']),
);
event(new ClaimModelChanged($claim, '', '', null, $content));
// @phpcs:enable
// Return conversation.
$claimMessageModels = ClaimMessage::getAllModelsFromClaim($claim->getKey());
return response()->json($claimMessageModels);
} //end if
return response()->json([], 500);
});
}
/**
* Get a paginate resource.
* Needed to overheight the controller method and add a brand condition.
*
* @param \App\Http\Requests\Bo\PaginateModelsRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function jsonClaimPaginateModels(PaginateModelsRequest $request): \Illuminate\Http\JsonResponse
{
$data = $request->validated();
$brandModelKey = $request->route()->parameter('brand')->getKey();
$models = CollectionHelper::paginateManually(
$data['targetModel']::getAllModelsFromBrand($brandModelKey),
$this->jsonItemPerPagination,
$request->validated('page'),
);
return \response()->json($models);
}
// * EXPORT
/**
* Export claim datas to a given format
*
* @param ExportClaimRequest $request
* @return \Illuminate\Http\Response|Symfony\Component\HttpFoundation\StreamedResponse|\Illuminate\Http\RedirectResponse
*/
public function export(ExportClaimRequest $request)
{
/** @var \Illuminate\Database\Eloquent\Collection $claims*/
$claims = Claim::query()->whereIn('id', $request->validated('claimList'))->get();
$format = ClaimExportFormat::from($request->validated('format'));
// * CSV
if ($format->equals(ClaimExportFormat::csvWithoutAttachment, ClaimExportFormat::csv)) {
$csv = $this->generateCsv($claims);
if (!$this->needsZip($format, $claims)) {
return new StreamedResponse(function () use ($format, $csv) {
echo $csv;
}, 200, $this->getFileHeaders($format));
}
return $this->returnStreamedZip($format, $claims, $csv);
// * PDF
} elseif ($format->equals(ClaimExportFormat::pdf)) {
$pdf = $this->generatePdf($claims);
if (!$this->needsZip(ClaimExportFormat::pdf, $claims)) {
return $pdf->download('export.pdf');
}
return $this->returnStreamedZip($format, $claims, $pdf->output());
} else {
return back()->with(
'error',
__('Une erreur est survenue lors de l\'export. Veuillez réessayer en rafraîchissant la page.')
);
}; //end if
}
/**
* Check if the export needs to be in a .zip file.
*
* @param ClaimExportFormat $format
* @param \Illuminate\Database\Eloquent\Collection $claims
* @return boolean
*/
private function needsZip(ClaimExportFormat $format, \Illuminate\Database\Eloquent\Collection $claims): bool
{
/**
* .zip is needed for :
* - csv with any files
* - pdf with files other than pictures
*/
if ($format->equals(ClaimExportFormat::csv)) {
return $claims->whereNotNull('file')->pluck('file')->isNotEmpty();
} elseif ($format->equals(ClaimExportFormat::pdf)) {
return $claims
->whereNotNull('file')
->whereNotIn('file_extension', ['jpg', 'jpeg', 'png'])
->isNotEmpty();
}
return false;
}
/**
* Create the zip folder for the export with the main file inside.
*
* @param ZipArchive $zip
* @param string $file
* @param ClaimExportFormat $format
* @return string|false
*
* @throws \Exception If the zip opening failed.
*/
private function createExportZip(ZipArchive &$zip, string $file, ClaimExportFormat $format): string|false
{
$zipName = tempnam(Storage::disk('private')->path('export'), 'zip');
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
* @param \Illuminate\Database\Eloquent\Collection $claims
* @param ClaimExportFormat $format
* @return void
*/
private function addAttachmentsToZip(
ZipArchive &$zip,
\Illuminate\Database\Eloquent\Collection $claims,
ClaimExportFormat $format
) {
$claimsWithFilesToZip = $claims->whereNotNull('file')
->when($format->equals(ClaimExportFormat::pdf), function ($collection) {
return $collection->whereNotIn('file_extension', ['jpg', 'jpeg', 'png']);
});
foreach ($claimsWithFilesToZip as $claim) {
$filePath = $this->getPrivateFilePath($claim->file);
if (Storage::disk('private')->exists($filePath)) {
$zip->addFile(
Storage::disk('private')->path($filePath),
$claim->export_file_name
);
}
}
}
/**
* Return a StreamedResponse with a zip file.
*
* @param ClaimExportFormat $format
* @param \Illuminate\Database\Eloquent\Collection $claims
* @param string $file
* @return \Symfony\Component\HttpFoundation\StreamedResponse
*/
private function returnStreamedZip(
ClaimExportFormat $format,
\Illuminate\Database\Eloquent\Collection $claims,
string $file
): \Symfony\Component\HttpFoundation\StreamedResponse {
return new StreamedResponse(function () use ($file, $claims, $format) {
// Create zip.
$zip = new ZipArchive();
$zipName = $this->createExportZip($zip, $file, $format);
// Add attachments.
$this->addAttachmentsToZip($zip, $claims, $format);
$zip->close();
readfile($zipName);
unlink($zipName);
}, 200, $this->getFileHeaders($format, true));
}
/**
* Generate PDF with claims.
*
* @param \Illuminate\Database\Eloquent\Collection $claims
* @return \Barryvdh\DomPDF\PDF
*/
private function generatePdf(\Illuminate\Database\Eloquent\Collection $claims): \Barryvdh\DomPDF\PDF
{
// Convert pictures to base64.
foreach ($claims->whereIn('file_extension', ['jpg', 'jpeg', 'png']) as $claim) {
$filePath = $this->getPrivateFilePath($claim->file);
$claim->fileBase64 = sprintf(
'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');
}
return Pdf::setOptions(['tempDir' => Storage::disk('private')->path('temp')])
->loadView('bo.pages.claims.pdf', [
'claimModels' => $claims
]);
}
/**
* Generate claims CSV
*
* @param \Illuminate\Database\Eloquent\Collection $claims
* @return string|false
*/
private function generateCsv(\Illuminate\Database\Eloquent\Collection $claims)
{
// CSV Header.
$csvlines[] = [
'Référence',
'Date de création',
'Prénom NOM',
'Email',
'Adresse',
'Statut',
'Sujet',
'Type',
'Marque',
'Message',
'Pièce jointe',
];
foreach ($claims as $claim) {
$csvlines[] = $this->fillLine($claim);
}
return $this->array2csv($csvlines, ';');
}
/**
* 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;
}
/**
* Fill one line for csv.
*
* @param \App\Models\Claim $claim
* @return array
*/
private function fillLine(Claim $claim)
{
$oneline = [
$claim->reference,
$claim->created_at->toDateTimeString(),
sprintf('%s %s', $claim->first_name, Str::upper($claim->last_name)),
$claim->email,
sprintf('%s, %s, %s', $claim->address, $claim->postcode, $claim->city),
$claim->status->label(),
$claim->claimSubject->label,
$claim->type->label(),
$claim->brand->name,
$claim->message,
$claim->export_file_name,
];
return $oneline;
}
/**
* Get file path in private storage from stored path.
*
* @param string $basePath
* @return string
*/
private function getPrivateFilePath(string $basePath): string
{
return str($basePath)->replaceFirst('storage/app/private', '');
}
/**
* Get file headers to use with StreamedResponse.
*
* @param ClaimExportFormat $format
* @param boolean $isZip
* @return array
*/
private function getFileHeaders(ClaimExportFormat $format, bool $isZip = false): array
{
if ($isZip) {
return [
'Content-Type' => 'application/zip',
'Content-Disposition' => 'attachment; filename="export.zip"',
];
};
return $format->fileHeaders();
}
}