vcard4reseller/backend/src/Service/BrandingService.php
Thomas Peterson a233c34599 White-Label Phase 3: Branding-Verwaltung
- BrandingAdminController: GET/PUT /api/my-branding (Farben, Tagline),
  Logo-Upload/-Löschen, öffentliche Logo-Auslieferung /api/branding/logo/...
- BrandingService liefert logoUrl aus S3-logoKey (Firma → Reseller → Default)
- BrandingView (Reseller-Topnav + Firmen-Sidebar): Farbwähler, Slogan,
  Logo-Upload mit Live-Vorschau, Anzeige der eigenen Adresse

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

100 lines
3.3 KiB
PHP

<?php
namespace App\Service;
use App\Entity\Company;
use App\Entity\Reseller;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
/**
* Baut aus einem aufgelösten Tenant das Branding (Name, Farben, Logo) für die SPA.
* Fallback-Kette: Firma → Reseller → Plattform-Standard. So erbt eine Firma ohne
* eigenes Branding automatisch das ihres Resellers.
*/
final class BrandingService
{
public const SCOPE_RESELLER = 'reseller';
public const SCOPE_COMPANY = 'company';
/** Plattform-Standard (vcard4reseller-Orange). */
private const COLOR_DEFAULTS = [
'primaryColor' => '#f58220',
'primaryColorDark' => '#d96500',
'primaryColorSoft' => '#fff2e7',
'tagline' => null,
];
public function __construct(private readonly UrlGeneratorInterface $urls)
{
}
/**
* @return array{level:string,name:string,reseller:?string,customDomain:bool,branding:array<string,mixed>}
*/
public function forTenant(ResolvedTenant $tenant): array
{
$branding = self::COLOR_DEFAULTS;
foreach ($this->configChain($tenant) as $config) {
foreach (self::COLOR_DEFAULTS as $key => $_) {
if (isset($config[$key]) && '' !== $config[$key]) {
$branding[$key] = $config[$key];
}
}
}
$branding['logoUrl'] = $this->logoUrl($tenant);
return [
'level' => $tenant->kind,
'name' => $this->name($tenant),
'reseller' => $tenant->reseller?->getName(),
'customDomain' => $tenant->customDomain,
'branding' => $branding,
];
}
/** Öffentliche Logo-URL eines Tenants (am spezifischsten zuerst), oder null. */
public function logoUrl(ResolvedTenant $tenant): ?string
{
if (null !== $tenant->company && '' !== (string) ($tenant->company->getBrandingConfig()['logoKey'] ?? '')) {
return $this->logoRoute(self::SCOPE_COMPANY, (string) $tenant->company->getId(), $tenant->company->getBrandingConfig()['logoKey']);
}
if (null !== $tenant->reseller && '' !== (string) ($tenant->reseller->getBrandingConfig()['logoKey'] ?? '')) {
return $this->logoRoute(self::SCOPE_RESELLER, (string) $tenant->reseller->getId(), $tenant->reseller->getBrandingConfig()['logoKey']);
}
return null;
}
/** @return array<int, array<string,mixed>> */
private function configChain(ResolvedTenant $tenant): array
{
$chain = [];
if (null !== $tenant->reseller) {
$chain[] = $tenant->reseller->getBrandingConfig();
}
if (null !== $tenant->company) {
$chain[] = $tenant->company->getBrandingConfig();
}
return $chain;
}
private function logoRoute(string $scope, string $id, mixed $logoKey): string
{
return $this->urls->generate('branding_logo', ['scope' => $scope, 'id' => $id])
.'?v='.substr(sha1((string) $logoKey), 0, 8);
}
private function name(ResolvedTenant $tenant): string
{
if ($tenant->isCompany() && $tenant->company instanceof Company) {
return $tenant->company->getName();
}
if ($tenant->isReseller() && $tenant->reseller instanceof Reseller) {
return $tenant->reseller->getName();
}
return 'vcard4reseller';
}
}