Wasserzeichen fuer den kostenlosen Tarif
Mandanten im Tarif "free" (bis 10 Teilnehmer) zeigen unten rechts eine kleine Marke mit Logo und Adresse der Kaffeeliste; zahlende Mandanten behalten ihre Oberflaeche ohne fremdes Logo. Die Sichtbarkeit haengt allein an tenant_billing.plan_code und wird als reine Leseabfrage geprueft - billing_fetch_or_init() waere ein Schreibzugriff im Seitenaufbau. Laesst sich der Tarif nicht lesen, bleibt die Marke aus. Das Logo ist ein Inline-SVG, das Linkziel kommt aus APP_MARKETING_URL bzw. aus APP_HOST ohne App-Subdomain. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -99,6 +99,33 @@ function app_primary_url(string $path): string
|
||||
return $scheme . '://' . $host . '/' . ltrim($path, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adresse der Marketing-Domain (z. B. https://kaffeeliste.de) - Ziel des
|
||||
* Wasserzeichens im kostenlosen Tarif. Explizit ueber APP_MARKETING_URL
|
||||
* setzbar; sonst wird sie aus APP_HOST abgeleitet, indem die
|
||||
* App-Subdomain wegfaellt (app.kaffeeliste.de -> kaffeeliste.de). In der
|
||||
* lokalen Entwicklung ohne APP_HOST bleibt der aktuelle Host uebrig, im
|
||||
* CLI-Betrieb ein leerer String.
|
||||
*/
|
||||
function app_marketing_url(): string
|
||||
{
|
||||
$configured = app_env('APP_MARKETING_URL');
|
||||
if ($configured !== null) {
|
||||
return rtrim($configured, '/');
|
||||
}
|
||||
|
||||
$scheme = app_is_https() ? 'https' : 'http';
|
||||
$host = app_primary_host();
|
||||
|
||||
if ($host !== null) {
|
||||
return $scheme . '://' . preg_replace('/^app\./i', '', $host);
|
||||
}
|
||||
|
||||
$requestHost = (string)($_SERVER['HTTP_HOST'] ?? '');
|
||||
|
||||
return $requestHost !== '' ? $scheme . '://' . $requestHost : '';
|
||||
}
|
||||
|
||||
function app_start_session(): void
|
||||
{
|
||||
if (PHP_SAPI === 'cli' || session_status() === PHP_SESSION_ACTIVE) {
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/bootstrap.php';
|
||||
|
||||
/**
|
||||
* Wasserzeichen des kostenlosen Tarifs: eine kleine Marke unten rechts, die
|
||||
* auf die Kaffeeliste verweist. Sie erscheint nur im Tarif "free" - wer
|
||||
* zahlt, bekommt seine Oberflaeche ohne fremdes Logo.
|
||||
*
|
||||
* Bewusst getrennt von app/branding.php: dort geht es um das Logo *des
|
||||
* Mandanten*, hier um das Logo *des Anbieters*. Beide koennen gleichzeitig
|
||||
* sichtbar sein (Kopfzeile oben, Wasserzeichen unten rechts).
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tarif eines Mandanten, ohne Seiteneffekt. Absichtlich nicht
|
||||
* billing_fetch_or_init(): das Wasserzeichen wird auf jeder Seite geprueft
|
||||
* und darf dabei keine Zeile anlegen. Fehlt der Datensatz, gilt - wie beim
|
||||
* Anlegen - "free".
|
||||
*/
|
||||
function plan_watermark_plan_code(PDO $pdo, int $tenantId): string
|
||||
{
|
||||
static $cache = [];
|
||||
|
||||
if (array_key_exists($tenantId, $cache)) {
|
||||
return $cache[$tenantId];
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare('SELECT plan_code FROM tenant_billing WHERE tenant_id = ?');
|
||||
$stmt->execute([$tenantId]);
|
||||
$planCode = $stmt->fetchColumn();
|
||||
$cache[$tenantId] = $planCode === false ? 'free' : (string)$planCode;
|
||||
} catch (Throwable $e) {
|
||||
// Laesst sich der Tarif nicht lesen, bleibt die Oberflaeche sauber -
|
||||
// ein Wasserzeichen bei einem zahlenden Kunden waere der schlimmere
|
||||
// Fehler als ein fehlendes bei einem kostenlosen.
|
||||
$cache[$tenantId] = 'unknown';
|
||||
}
|
||||
|
||||
return $cache[$tenantId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt dieser Mandant das Wasserzeichen? Nur der kostenlose Tarif tut das.
|
||||
*/
|
||||
function plan_watermark_visible(PDO $pdo, int $tenantId): bool
|
||||
{
|
||||
if ($tenantId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return plan_watermark_plan_code($pdo, $tenantId) === 'free';
|
||||
}
|
||||
|
||||
/**
|
||||
* Wortmarke der Kaffeeliste als Inline-SVG. Als Inline-Grafik statt als
|
||||
* Bilddatei, damit das Wasserzeichen ohne zusaetzlichen Request auskommt,
|
||||
* auf jedem Bildschirm scharf bleibt und die Farbe der Umgebung uebernimmt.
|
||||
*/
|
||||
function plan_watermark_logo_svg(): string
|
||||
{
|
||||
return '<svg class="plan-watermark-logo" viewBox="0 0 24 24" width="18" height="18" '
|
||||
. 'aria-hidden="true" focusable="false" fill="none" stroke="currentColor" '
|
||||
. 'stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">'
|
||||
. '<path d="M4 9h13v6a4 4 0 0 1-4 4H8a4 4 0 0 1-4-4V9Z"/>'
|
||||
. '<path d="M17 10.5h1.5a2.5 2.5 0 0 1 0 5H17"/>'
|
||||
. '<path d="M8 5.5c0-1 1-1.4 1-2.5M12 5.5c0-1 1-1.4 1-2.5"/>'
|
||||
. '</svg>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Markup des Wasserzeichens. Ohne erreichbare Marketing-Domain (CLI,
|
||||
* ungewoehnliche Hosts) bleibt es bei Logo und Namen ohne Link - ein
|
||||
* Verweis ins Leere waere schlechter als keiner.
|
||||
*/
|
||||
function plan_watermark_html(): string
|
||||
{
|
||||
$url = app_marketing_url();
|
||||
$label = $url !== '' ? preg_replace('#^https?://#i', '', $url) : 'Kaffeeliste';
|
||||
$inner = plan_watermark_logo_svg()
|
||||
. '<span class="plan-watermark-text">' . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . '</span>';
|
||||
|
||||
if ($url === '') {
|
||||
return '<div class="plan-watermark" title="Kostenloser Tarif der Kaffeeliste">' . $inner . '</div>';
|
||||
}
|
||||
|
||||
return '<a class="plan-watermark" href="' . htmlspecialchars($url, ENT_QUOTES, 'UTF-8') . '"'
|
||||
. ' target="_blank" rel="noopener noreferrer"'
|
||||
. ' title="Diese Kaffeeliste läuft im kostenlosen Tarif">' . $inner . '</a>';
|
||||
}
|
||||
Reference in New Issue
Block a user