142 lines
4.2 KiB
PHP
142 lines
4.2 KiB
PHP
<?php
|
|
|
|
namespace Modules\User\Services;
|
|
|
|
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
|
|
use BaconQrCode\Renderer\ImageRenderer;
|
|
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
|
|
use BaconQrCode\Writer;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Facades\View;
|
|
use Modules\Auth\Entities\User;
|
|
use Spatie\Browsershot\Browsershot;
|
|
|
|
class MemberDigitalCardService
|
|
{
|
|
public const CARD_WIDTH = 840;
|
|
|
|
public const CARD_HEIGHT = 540;
|
|
|
|
public function renderPng(User $user, string $side): string
|
|
{
|
|
$view = $side === 'belakang'
|
|
? 'user::digital-card.back'
|
|
: 'user::digital-card.front';
|
|
|
|
$html = View::make($view, $this->prepareViewData($user))->render();
|
|
|
|
return $this->makeBrowsershot($html)->screenshot();
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
protected function prepareViewData(User $user): array
|
|
{
|
|
$user = app(PublicMemberProfileService::class)->loadVerifiableRelations($user);
|
|
$user->ensurePublicProfileToken();
|
|
|
|
$currentEmployment = $user->employments->firstWhere('is_current', true)
|
|
?? $user->employments->first();
|
|
|
|
$logoPath = config('user.digital_card_logo_path');
|
|
$profileUrl = rtrim(config('user.frontend_url'), '/').'/v/'.$user->public_profile_token;
|
|
|
|
return [
|
|
'memberNumber' => $this->displayCardValue($user->member_number),
|
|
'memberName' => $user->name ?: '-',
|
|
'memberType' => $this->displayCardValue($user->member_type),
|
|
'companyName' => $this->displayCardValue($currentEmployment?->company_name),
|
|
'avatarDataUri' => $this->resolveStorageDataUri($user->image_url),
|
|
'avatarInitials' => $this->avatarInitials($user->name),
|
|
'logoDataUri' => $this->resolveFileDataUri($logoPath),
|
|
'profileUrl' => $profileUrl,
|
|
'qrDataUri' => $this->generateQrDataUri($profileUrl),
|
|
];
|
|
}
|
|
|
|
protected function makeBrowsershot(string $html): Browsershot
|
|
{
|
|
$browsershot = Browsershot::html($html)
|
|
->setNodeModulePath(config('browsershot.node_module_path'))
|
|
->timeout(config('browsershot.timeout'))
|
|
->windowSize(self::CARD_WIDTH, self::CARD_HEIGHT)
|
|
->deviceScaleFactor(2)
|
|
->showBackground();
|
|
|
|
if ($nodeBinary = config('browsershot.node_binary')) {
|
|
$browsershot->setNodeBinary($nodeBinary);
|
|
}
|
|
|
|
if ($npmBinary = config('browsershot.npm_binary')) {
|
|
$browsershot->setNpmBinary($npmBinary);
|
|
}
|
|
|
|
if ($chromePath = config('browsershot.chrome_path')) {
|
|
$browsershot->setChromePath($chromePath);
|
|
}
|
|
|
|
if (config('browsershot.no_sandbox')) {
|
|
$browsershot->noSandbox();
|
|
}
|
|
|
|
return $browsershot;
|
|
}
|
|
|
|
protected function generateQrDataUri(string $content): string
|
|
{
|
|
$renderer = new ImageRenderer(
|
|
new RendererStyle(220, 1),
|
|
new SvgImageBackEnd
|
|
);
|
|
$writer = new Writer($renderer);
|
|
$svg = $writer->writeString($content);
|
|
|
|
return 'data:image/svg+xml;base64,'.base64_encode($svg);
|
|
}
|
|
|
|
protected function resolveFileDataUri(?string $path): ?string
|
|
{
|
|
if (empty($path) || ! file_exists($path)) {
|
|
return null;
|
|
}
|
|
|
|
$mime = mime_content_type($path) ?: 'image/svg+xml';
|
|
|
|
return 'data:'.$mime.';base64,'.base64_encode(file_get_contents($path));
|
|
}
|
|
|
|
protected function resolveStorageDataUri(?string $path): ?string
|
|
{
|
|
if (blank($path)) {
|
|
return null;
|
|
}
|
|
|
|
$fullPath = Storage::disk('public')->path($path);
|
|
|
|
return $this->resolveFileDataUri($fullPath);
|
|
}
|
|
|
|
protected function avatarInitials(?string $name): string
|
|
{
|
|
$trimmed = trim((string) $name);
|
|
|
|
if ($trimmed === '') {
|
|
return '--';
|
|
}
|
|
|
|
return strtoupper(mb_substr($trimmed, 0, 2));
|
|
}
|
|
|
|
protected function displayCardValue(mixed $value): string
|
|
{
|
|
if ($value === null || $value === '') {
|
|
return '-';
|
|
}
|
|
|
|
$trimmed = trim((string) $value);
|
|
|
|
return $trimmed !== '' ? $trimmed : '-';
|
|
}
|
|
}
|