= __('kiosk_unknown') ?>
+= __('kiosk_unknown_hint') ?>
+diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..7e20944
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,15 @@
+# Zeilenenden nicht anfassen – einige Dateien liegen bewusst mit CRLF vor.
+* -text
+
+# Dateien, die nicht ins Release-ZIP gehoeren.
+# `git archive` (siehe .github/workflows/release.yml) wertet export-ignore aus.
+/.gitattributes export-ignore
+/.gitignore export-ignore
+/.dockerignore export-ignore
+/.github export-ignore
+/docs export-ignore
+/tests export-ignore
+/tools export-ignore
+/phpunit.xml.dist export-ignore
+/phpstan.neon export-ignore
+/composer.lock export-ignore
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..3fc3001
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,152 @@
+name: Release-Paket
+
+# Bei jedem Merge nach main entsteht ein installierbares ZIP und landet als
+# Vorab-Release "latest-main" im Repository. Wird ein Tag v* gepusht, wird
+# daraus ein regulaeres Release mit derselben Mechanik.
+on:
+ push:
+ branches: [ main ]
+ tags: [ 'v*' ]
+ workflow_dispatch:
+
+jobs:
+ package:
+ name: ZIP bauen und veröffentlichen
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Version und Dateinamen bestimmen
+ id: meta
+ run: |
+ set -eu
+ VERSION="$(tr -d ' \r\n' < VERSION)"
+ SHORT_SHA="$(git rev-parse --short HEAD)"
+ BUILD_DATE="$(date -u +%Y-%m-%d)"
+
+ if [ "${GITHUB_REF_TYPE:-branch}" = "tag" ]; then
+ TAG="${GITHUB_REF_NAME}"
+ NAME="unifi-voucher-tool-${TAG}"
+ TITLE="Version ${TAG}"
+ PRERELEASE="false"
+ else
+ TAG="latest-main"
+ NAME="unifi-voucher-tool-${VERSION}+${BUILD_DATE}.${SHORT_SHA}"
+ TITLE="Aktueller Stand von main – ${VERSION} (${SHORT_SHA})"
+ PRERELEASE="true"
+ fi
+
+ {
+ echo "version=${VERSION}"
+ echo "short_sha=${SHORT_SHA}"
+ echo "build_date=${BUILD_DATE}"
+ echo "tag=${TAG}"
+ echo "name=${NAME}"
+ echo "title=${TITLE}"
+ echo "prerelease=${PRERELEASE}"
+ } >> "$GITHUB_OUTPUT"
+
+ - name: ZIP erzeugen
+ run: |
+ set -eu
+ mkdir -p dist
+ # git archive wertet die export-ignore-Regeln aus .gitattributes aus,
+ # docs/, tests/, tools/ und CI-Dateien bleiben also draußen.
+ git archive --format=zip -9 \
+ --prefix="unifi-voucher-tool/" \
+ -o "dist/${{ steps.meta.outputs.name }}.zip" HEAD
+ cd dist
+ sha256sum "${{ steps.meta.outputs.name }}.zip" > "${{ steps.meta.outputs.name }}.zip.sha256"
+ ls -lh
+
+ - name: Inhalt kurz prüfen
+ run: |
+ set -eu
+ # Ein paar Dateien muessen enthalten sein, sonst ist das Paket kaputt.
+ for required in \
+ unifi-voucher-tool/index.php \
+ unifi-voucher-tool/install.php \
+ unifi-voucher-tool/database.sql \
+ unifi-voucher-tool/assets/global.css \
+ unifi-voucher-tool/assets/vendor/inter/inter.css \
+ unifi-voucher-tool/includes/Ui.php
+ do
+ if ! unzip -l "dist/${{ steps.meta.outputs.name }}.zip" | grep -q "$required"; then
+ echo "Fehlt im Paket: $required" >&2
+ exit 1
+ fi
+ done
+ echo "Paket vollständig."
+
+ - name: Als Build-Artefakt sichern
+ uses: actions/upload-artifact@v3
+ continue-on-error: true # Artefakt-Speicher ist optional
+ with:
+ name: ${{ steps.meta.outputs.name }}
+ path: dist/*
+ retention-days: 30
+
+ - name: Release anlegen bzw. auffrischen
+ env:
+ # Forgejo stellt den Token automatisch bereit; FORGEJO_TOKEN
+ # (persönlicher Token) dient als Ausweichweg.
+ TOKEN: ${{ secrets.GITHUB_TOKEN || secrets.FORGEJO_TOKEN }}
+ API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
+ TAG: ${{ steps.meta.outputs.tag }}
+ NAME: ${{ steps.meta.outputs.name }}
+ TITLE: ${{ steps.meta.outputs.title }}
+ PRERELEASE: ${{ steps.meta.outputs.prerelease }}
+ VERSION: ${{ steps.meta.outputs.version }}
+ SHORT_SHA: ${{ steps.meta.outputs.short_sha }}
+ BUILD_DATE: ${{ steps.meta.outputs.build_date }}
+ run: |
+ set -eu
+ if [ -z "${TOKEN:-}" ]; then
+ echo "Kein Token vorhanden – Release wird übersprungen." >&2
+ exit 0
+ fi
+ AUTH="Authorization: token ${TOKEN}"
+
+ # Rollendes Vorab-Release durch ein frisches ersetzen, damit der
+ # Download-Link stabil bleibt und auf den aktuellen Stand zeigt.
+ if [ "$TAG" = "latest-main" ]; then
+ OLD_ID="$(curl -sf -H "$AUTH" "${API}/releases/tags/${TAG}" \
+ | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 || true)"
+ if [ -n "${OLD_ID:-}" ]; then
+ curl -sf -X DELETE -H "$AUTH" "${API}/releases/${OLD_ID}" || true
+ curl -sf -X DELETE -H "$AUTH" "${API}/tags/${TAG}" || true
+ fi
+ fi
+
+ # Release-Text bewusst ohne Anfuehrungszeichen und Backslashes,
+ # damit er ohne jq direkt in den JSON-Body passt (\n bleibt literal).
+ BODY="Automatisch gebaut aus Commit ${SHORT_SHA}.\n\n"
+ BODY="${BODY}| | |\n|---|---|\n"
+ BODY="${BODY}| Version | ${VERSION} |\n"
+ BODY="${BODY}| Commit | ${SHORT_SHA} |\n"
+ BODY="${BODY}| Gebaut am | ${BUILD_DATE} |\n\n"
+ BODY="${BODY}**Neuinstallation:** ZIP entpacken, Dateien auf den Webserver legen, install.php aufrufen.\n\n"
+ BODY="${BODY}**Update einer bestehenden Installation:** config.php, uploads/ und updater/storage/ nicht ueberschreiben "
+ BODY="${BODY}- oder gleich den eingebauten Updater unter Administration, System-Update verwenden.\n\n"
+ BODY="${BODY}Pruefsumme: siehe beigelegte .sha256-Datei."
+
+ RELEASE_ID="$(curl -sf -X POST -H "$AUTH" -H 'Content-Type: application/json' \
+ -d "{\"tag_name\":\"${TAG}\",\"target_commitish\":\"${GITHUB_SHA}\",\"name\":\"${TITLE}\",\"body\":\"${BODY}\",\"draft\":false,\"prerelease\":${PRERELEASE}}" \
+ "${API}/releases" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)"
+
+ if [ -z "${RELEASE_ID:-}" ]; then
+ echo "Release konnte nicht angelegt werden." >&2
+ exit 1
+ fi
+
+ for file in "dist/${NAME}.zip" "dist/${NAME}.zip.sha256"; do
+ curl -sf -X POST -H "$AUTH" \
+ -F "attachment=@${file}" \
+ "${API}/releases/${RELEASE_ID}/assets?name=$(basename "$file")" > /dev/null
+ echo "Angehängt: $(basename "$file")"
+ done
+
+ echo "Release ${TITLE} steht bereit: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${TAG}"
diff --git a/Readme.md b/Readme.md
index e6f1973..46a8a84 100644
--- a/Readme.md
+++ b/Readme.md
@@ -10,8 +10,9 @@ Entwickelt von **[Loheide.eu](https://loheide.eu)**



-
-
+
+
+[](https://git.loheide.cloud/friloo/Unifi-Voucher-Tool)
@@ -27,6 +28,8 @@ Entwickelt von **[Loheide.eu](https://loheide.eu)**
## ✨ Features
- 🎟️ **Voucher-Erstellung** mit sofortiger QR-Code-Anzeige, Druckvorlage und E-Mail-Versand
+- 🖥️ **Display-Seiten (Kiosk)** – öffentliche Seite je Site, an der Gäste sich mit einem Klick selbst einen Zugang holen
+- 🎨 **Jede Display-Seite eigenständig gestaltbar** – Logo, Hintergrundbild, Akzentfarbe, helle oder dunkle Karte
- 📦 **Bulk-Erstellung** – bis zu 20 Vouchers auf einmal, inkl. Sammeldruck-Layout
- 🧩 **Voucher-Profile/Templates** – vordefinierte Laufzeiten & Gerätelimits per Schnellauswahl
- 🏢 **Multi-Site-Support** – beliebig viele UniFi-Standorte zentral verwalten
@@ -82,6 +85,18 @@ Entwickelt von **[Loheide.eu](https://loheide.eu)**
+### Display-Seite für Gäste
+
+
+
+
+
+
@@ -142,8 +157,8 @@ Entwickelt von **[Loheide.eu](https://loheide.eu)**
## 🚀 Installation
```bash
-git clone https://github.com/friloo/unifi-voucher-tool.git
-cd unifi-voucher-tool
+git clone https://git.loheide.cloud/friloo/Unifi-Voucher-Tool.git
+cd Unifi-Voucher-Tool
```
1. Dateien auf den Webserver hochladen
@@ -213,6 +228,64 @@ Während eines Updates wird die Anwendung kurz in den **Wartungsmodus** versetzt
---
+## 🖥️ Display-Seiten für Gäste
+
+Für Empfang, Lobby oder Tagungsraum lässt sich je Site eine **öffentliche Seite**
+anlegen, die auf einem Bildschirm oder Tablet läuft. Gäste tippen auf einen
+Knopf und bekommen sofort einen eigenen Zugangscode – ohne Anmeldung, ohne
+Personal am Tresen.
+
+**Anlegen:** Administration → **Display-Seiten** → *Display-Seite anlegen*
+
+
+= __('kiosks_subtitle') ?>
+= __('kiosks_no_sites') ?>
+ + = __('sites_add') ?> + += __('kiosks_empty') ?>
+ +' . htmlspecialchars($label) . ' ' + $prefix = ''; + if ($withVersion && self::version() !== '') { + $prefix = 'v' . htmlspecialchars(self::version()) . ' · '; + } + + return '
' . $prefix . htmlspecialchars($label) . ' ' . '' . self::CREDIT_NAME . '
'; } diff --git a/includes/Upload.php b/includes/Upload.php index 39713bf..2a97b90 100644 --- a/includes/Upload.php +++ b/includes/Upload.php @@ -120,6 +120,39 @@ class Upload return 'uploads/' . $name; } + /** + * Neuer Wert eines Bildfeldes aus dem Formular. + * + * Reihenfolge: hochgeladene Datei schlaegt alles, danach der + * Entfernen-Schalter, sonst gilt das URL-Feld. Wird eine zuvor + * hochgeladene Datei ersetzt oder entfernt, verschwindet sie auch + * von der Platte. + * + * @param string $name Feldname (erwartet= __('kiosk_unknown_hint') ?>
+= __('kiosk_ready') ?>
+= __('kiosk_scan_code') ?>
++ = str_replace('{seconds}', '' . $display . '', __('kiosk_reset_in')) ?> +
+ = __('kiosk_done') ?> += htmlspecialchars($subline) ?>
+ + += __('kiosk_phone_hint') ?>
+