Funktions-Schalter je Mandant (app/features.php, tenant_features): der Betreiber schaltet FAQ, Selbsteintrag, PDF, PayPal-Eingang, CSV-Import, Mailversand, Jahresabschluss, Datenexport und eigenes Design pro Mandant frei. Gesperrte Funktionen verschwinden aus Menue und Schaltflaechen, ihre Seiten weisen Aufrufe und POSTs ab. Das Back-Office ist jetzt fuer Platform-Admins im Menue verlinkt statt nur per URL erreichbar. Eigenes Design je Mandant: Akzentfarbe und Logo in den Mandant- Einstellungen, eingebettet ueber app/branding.php; das Logo liegt geschuetzt in var/tenant_logos und wird nur ueber tenant-logo-anzeigen.php an den eigenen Mandanten ausgeliefert. Weniger Startinformationen: Startpaket neuer Mandanten auf zwei Beispielfragen gekuerzt, Anleitung von ~1400 auf ~750 Woerter gestrafft und um Abschnitte zu gesperrten Funktionen bereinigt. Vorder-/Rueckseite erst bei mehr als 50 Personen (vorher schon ab 50). Die Schwelle liegt jetzt gemeinsam in app/ledger.php und gilt auch fuer die Vorder-/Rueckseiten-Auswahl beim Erfassen, wo sie bisher unabhaengig von der Teamgroesse angeboten wurde. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
106 lines
3.9 KiB
PHP
106 lines
3.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/bootstrap.php';
|
|
require_once __DIR__ . '/saas-auth.php';
|
|
require_once __DIR__ . '/features.php';
|
|
|
|
/**
|
|
* Logo und Akzentfarbe des Mandanten, an dem der angemeldete Nutzer haengt.
|
|
* Ohne Anmeldung, ohne freigeschaltete Funktion „Eigenes Design" oder ohne
|
|
* hinterlegte Werte kommt ueberall ein leerer String zurueck - der Aufrufer
|
|
* bleibt dann beim Standarddesign.
|
|
*
|
|
* Das Ergebnis wird pro Request gecached: header.php laeuft auf jeder Seite
|
|
* und soll dafuer nicht mehrfach die Einstellungen laden.
|
|
*
|
|
* @return array{color: string, logo_url: string, tenant_name: string}
|
|
*/
|
|
function app_branding_for_current_user(PDO $pdo): array
|
|
{
|
|
static $cached = null;
|
|
if ($cached !== null) {
|
|
return $cached;
|
|
}
|
|
|
|
$leer = ['color' => '', 'logo_url' => '', 'tenant_name' => ''];
|
|
|
|
try {
|
|
$user = saas_current_user($pdo);
|
|
if ($user === null) {
|
|
return $cached = $leer;
|
|
}
|
|
|
|
$tenantId = (int)$user['tenant_id'];
|
|
if (!app_feature_enabled($pdo, $tenantId, 'branding')) {
|
|
return $cached = $leer;
|
|
}
|
|
|
|
$settings = saas_fetch_tenant_settings($pdo, $tenantId);
|
|
if ($settings === null) {
|
|
return $cached = $leer;
|
|
}
|
|
|
|
$color = (string)($settings['brand_color'] ?? '');
|
|
// Zweite Pruefung neben der Formularvalidierung: was hier nicht wie
|
|
// ein Hex-Wert aussieht, wird nie in ein Stylesheet geschrieben.
|
|
if (!preg_match('/^#[0-9a-fA-F]{6}$/', $color)) {
|
|
$color = '';
|
|
}
|
|
|
|
return $cached = [
|
|
'color' => strtolower($color),
|
|
'logo_url' => trim((string)($settings['brand_logo'] ?? '')) !== '' ? 'tenant-logo-anzeigen.php' : '',
|
|
'tenant_name' => (string)($settings['name'] ?? ''),
|
|
];
|
|
} catch (Throwable $e) {
|
|
// Branding ist Beiwerk: faellt es aus (z. B. Migration noch nicht
|
|
// eingespielt), laedt die Seite trotzdem im Standarddesign.
|
|
return $cached = $leer;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Erzeugt die Farbueberschreibungen fuer das Template-Stylesheet. main.css
|
|
* verwendet durchgaengig genau einen Akzentton (#38761d) - hier werden die
|
|
* sichtbaren Stellen davon auf die Mandantenfarbe umgestellt.
|
|
*/
|
|
function app_branding_css(string $color): string
|
|
{
|
|
$c = preg_match('/^#[0-9a-f]{6}$/', strtolower($color)) ? strtolower($color) : '#38761d';
|
|
|
|
return <<<CSS
|
|
:root { --brand-color: {$c}; }
|
|
a { color: var(--brand-color); }
|
|
a:hover { border-bottom-color: var(--brand-color); color: var(--brand-color) !important; }
|
|
header.major > :last-child { border-bottom-color: var(--brand-color); }
|
|
input[type="text"]:focus, input[type="password"]:focus, input[type="email"]:focus,
|
|
input[type="tel"]:focus, input[type="search"]:focus, input[type="url"]:focus,
|
|
input[type="number"]:focus, input[type="date"]:focus, input[type="datetime-local"]:focus,
|
|
select:focus, textarea:focus,
|
|
input[type="checkbox"]:focus + label:before, input[type="radio"]:focus + label:before {
|
|
border-color: var(--brand-color);
|
|
box-shadow: 0 0 0 1px var(--brand-color);
|
|
}
|
|
input[type="checkbox"]:checked + label:before, input[type="radio"]:checked + label:before {
|
|
background: var(--brand-color);
|
|
border-color: var(--brand-color);
|
|
}
|
|
input[type="submit"], input[type="reset"], input[type="button"], button, .button {
|
|
box-shadow: inset 0 0 0 2px var(--brand-color);
|
|
color: var(--brand-color) !important;
|
|
}
|
|
input[type="submit"].primary, input[type="reset"].primary, input[type="button"].primary,
|
|
button.primary, .button.primary {
|
|
background-color: var(--brand-color);
|
|
box-shadow: none;
|
|
color: #ffffff !important;
|
|
}
|
|
#header { border-bottom-color: var(--brand-color); }
|
|
#menu ul a:hover, #menu ul span:hover { color: var(--brand-color); }
|
|
.tenant-brand-line { border-bottom: solid 1px rgba(0, 0, 0, 0.08); }
|
|
|
|
CSS;
|
|
}
|