vcard4reseller/backend/src/Controller/PublicProfileController.php
Thomas Peterson fa321fb6a5 Profil + vCard mit neuen Mitarbeiterfeldern; Modal breiter
- VCardBuilder: Titel (N-Präfix), Abteilung (ORG), Privat-E-Mail, Mobil,
  Zentrale, Fax, Website, Geschäfts-/Privatadresse, Foto (PHOTO URI)
- Profilseite: Foto über echte URL, Kontakt-Sektion (alle Nummern/Mails/Web),
  Adresse geschäftlich+privat, Branding via BrandingService (Reseller-Vererbung)
- Mitarbeiter-Formular nutzt das breite Modal (kein Overflow mehr)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 19:33:46 +02:00

106 lines
3.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Controller;
use App\Entity\Employee;
use App\Repository\EmployeeRepository;
use App\Service\BrandingService;
use App\Service\ResolvedTenant;
use App\Service\VCardBuilder;
use App\Service\WalletService;
use Endroid\QrCode\Builder\Builder;
use Endroid\QrCode\Encoding\Encoding;
use Endroid\QrCode\ErrorCorrectionLevel;
use Endroid\QrCode\Writer\PngWriter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
/**
* Öffentliche, serverseitig gerenderte Profilseiten (SSR, SEO).
* Nicht mandantengefiltert und ohne Auth bewusst öffentlich.
*/
final class PublicProfileController extends AbstractController
{
public function __construct(private readonly EmployeeRepository $employees)
{
}
#[Route('/p/{companySlug}/{slug}', name: 'public_profile', methods: ['GET'])]
public function show(string $companySlug, string $slug, WalletService $wallet, BrandingService $branding): Response
{
$employee = $this->resolve($companySlug, $slug);
$company = $employee->getCompany();
$tenant = new ResolvedTenant(ResolvedTenant::KIND_COMPANY, $company->getReseller(), $company);
return $this->render('public/profile.html.twig', [
'e' => $employee,
'branding' => $branding->forTenant($tenant)['branding'],
'profileUrl' => $this->profileUrl($employee),
'shareUrl' => $this->shareUrl($employee),
'walletEnabled' => null !== $employee->getShortCode()
&& ($wallet->isAppleConfigured() || $wallet->isGoogleConfigured()),
]);
}
#[Route('/p/{companySlug}/{slug}/vcard.vcf', name: 'public_profile_vcard', methods: ['GET'])]
public function vcard(string $companySlug, string $slug, VCardBuilder $builder): Response
{
$employee = $this->resolve($companySlug, $slug);
return new Response($builder->build($employee), 200, [
'Content-Type' => 'text/vcard; charset=utf-8',
'Content-Disposition' => sprintf('attachment; filename="%s.vcf"', $employee->getSlug()),
]);
}
#[Route('/p/{companySlug}/{slug}/qr.png', name: 'public_profile_qr', methods: ['GET'])]
public function qr(string $companySlug, string $slug): Response
{
$employee = $this->resolve($companySlug, $slug);
$result = (new Builder(
writer: new PngWriter(),
data: $this->shareUrl($employee),
encoding: new Encoding('UTF-8'),
errorCorrectionLevel: ErrorCorrectionLevel::Medium,
size: 320,
margin: 12,
))->build();
return new Response($result->getString(), 200, ['Content-Type' => $result->getMimeType()]);
}
private function resolve(string $companySlug, string $slug): Employee
{
$employee = $this->employees->findPublic($companySlug, $slug);
if (null === $employee) {
throw $this->createNotFoundException('Profil nicht gefunden.');
}
return $employee;
}
private function profileUrl(Employee $employee): string
{
return $this->generateUrl('public_profile', [
'companySlug' => $employee->getCompany()->getSlug(),
'slug' => $employee->getSlug(),
], UrlGeneratorInterface::ABSOLUTE_URL);
}
/**
* Stabile Teilen-URL: bevorzugt den Kurz-Code (/t/{code}, NFC/QR-tauglich),
* fällt sonst auf die Profil-URL zurück.
*/
private function shareUrl(Employee $employee): string
{
if (null !== $employee->getShortCode()) {
return $this->generateUrl('short_link', ['code' => $employee->getShortCode()], UrlGeneratorInterface::ABSOLUTE_URL);
}
return $this->profileUrl($employee);
}
}