feat: implement record limit check and enhance image handling in PDF export actions

This commit is contained in:
Yoga Pangestu 2026-06-04 21:56:54 +07:00
parent 23f32b3cf1
commit beae82a898
3 changed files with 143 additions and 17 deletions

View File

@ -7,11 +7,14 @@
use App\Settings\GeneralSettings;
use Barryvdh\DomPDF\Facade\Pdf;
use Filament\Actions\Action;
use Filament\Notifications\Notification;
use Filament\Tables\Table;
use Illuminate\Support\Facades\Blade;
class ExportAllReportsPdfAction extends Action
{
private const int MAX_RECORDS = 500;
public static function getDefaultName(): ?string
{
return 'export_all_pdf';
@ -25,21 +28,43 @@ protected function setUp(): void
->color('info')
->icon('heroicon-o-arrow-down-tray')
->action(function (Table $table) {
$settings = app(GeneralSettings::class);
$records = $table->getLivewire()->getFilteredTableQuery()->get();
$query = $table->getLivewire()->getFilteredTableQuery();
$count = (clone $query)->count();
$logoPath = $settings->site_icon ? storage_path('app/public/'.$settings->site_icon) : null;
$logoBase64 = null;
if ($logoPath && file_exists($logoPath)) {
$logoData = base64_encode(file_get_contents($logoPath));
$logoBase64 = 'data:image/'.pathinfo($logoPath, PATHINFO_EXTENSION).';base64,'.$logoData;
if ($count > self::MAX_RECORDS) {
Notification::make()
->title('Data terlalu banyak')
->body('Maksimal '.self::MAX_RECORDS.' laporan per unduhan. Gunakan filter tabel untuk mempersempit data.')
->warning()
->send();
return;
}
$records->each(function ($record) {
$record->imageBase64 = MediaDataUri::fromMedia($record->getFirstMedia('reports'));
});
$settings = app(GeneralSettings::class);
$records = $query
->with([
'mediaTaskAssignment.partnerMedia',
'media',
])
->get();
$logoPath = $settings->site_icon ? storage_path('app/public/'.$settings->site_icon) : null;
$logoBase64 = MediaDataUri::fromPath($logoPath, maxWidth: 60, maxHeight: 60);
foreach ($records as $record) {
$record->imageBase64 = MediaDataUri::thumbnailForPdf(
$record->getFirstMedia('reports'),
maxWidth: 150,
maxHeight: 80,
);
}
return response()->streamDownload(function () use ($records, $settings, $logoBase64) {
@ini_set('memory_limit', '512M');
set_time_limit(120);
echo Pdf::loadHtml(
Blade::render('filament.manage.reports.print-all', [
'records' => $records,

View File

@ -28,16 +28,17 @@ protected function setUp(): void
$settings = app(GeneralSettings::class);
$logoPath = $settings->site_icon ? storage_path('app/public/'.$settings->site_icon) : null;
$logoBase64 = MediaDataUri::fromPath($logoPath, maxWidth: 80, maxHeight: 100);
$logoBase64 = null;
if ($logoPath && file_exists($logoPath)) {
$logoData = base64_encode(file_get_contents($logoPath));
$logoBase64 = 'data:image/'.pathinfo($logoPath, PATHINFO_EXTENSION).';base64,'.$logoData;
}
$imageBase64 = MediaDataUri::fromMedia($record->getFirstMedia('reports'));
$imageBase64 = MediaDataUri::thumbnailForPdf(
$record->getFirstMedia('reports'),
maxWidth: 800,
maxHeight: 400,
);
return response()->streamDownload(function () use ($record, $settings, $logoBase64, $imageBase64) {
set_time_limit(60);
echo Pdf::loadHtml(
Blade::render('filament.manage.reports.print', [
'record' => $record,

View File

@ -24,4 +24,104 @@ public static function fromMedia(?Media $media): ?string
return 'data:'.$mime.';base64,'.base64_encode($disk->get($path));
}
public static function fromPath(?string $absolutePath, int $maxWidth = 80, int $maxHeight = 80): ?string
{
if (! $absolutePath || ! file_exists($absolutePath)) {
return null;
}
$contents = file_get_contents($absolutePath);
if ($contents === false) {
return null;
}
return self::encodeResizedImage($contents, $maxWidth, $maxHeight)
?? self::encodeRawFile($absolutePath, $contents);
}
public static function thumbnailForPdf(?Media $media, int $maxWidth = 150, int $maxHeight = 80): ?string
{
if (! $media) {
return null;
}
$disk = Storage::disk($media->disk);
$path = $media->getPathRelativeToRoot();
if (! $disk->exists($path)) {
return null;
}
$mime = $media->mime_type ?? '';
if (! str_starts_with($mime, 'image/')) {
return null;
}
$contents = $disk->get($path);
return self::encodeResizedImage($contents, $maxWidth, $maxHeight)
?? self::fromMedia($media);
}
private static function encodeResizedImage(string $contents, int $maxWidth, int $maxHeight): ?string
{
if (! extension_loaded('gd')) {
return null;
}
$image = @imagecreatefromstring($contents);
if ($image === false) {
return null;
}
$width = imagesx($image);
$height = imagesy($image);
$ratio = min($maxWidth / $width, $maxHeight / $height, 1);
$newWidth = max(1, (int) round($width * $ratio));
$newHeight = max(1, (int) round($height * $ratio));
$thumb = imagecreatetruecolor($newWidth, $newHeight);
if ($thumb === false) {
imagedestroy($image);
return null;
}
imagecopyresampled($thumb, $image, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
imagedestroy($image);
ob_start();
imagejpeg($thumb, quality: 75);
imagedestroy($thumb);
$jpeg = ob_get_clean();
if ($jpeg === false || $jpeg === '') {
return null;
}
return 'data:image/jpeg;base64,'.base64_encode($jpeg);
}
private static function encodeRawFile(string $absolutePath, string $contents): ?string
{
$extension = strtolower(pathinfo($absolutePath, PATHINFO_EXTENSION));
$mime = match ($extension) {
'png' => 'image/png',
'jpg', 'jpeg' => 'image/jpeg',
'gif' => 'image/gif',
'webp' => 'image/webp',
default => null,
};
if ($mime === null) {
return null;
}
return 'data:'.$mime.';base64,'.base64_encode($contents);
}
}