diff --git a/README.md b/README.md index 24261af..82f700c 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,42 @@ Das AddOn ermöglicht es Administratoren, das Frontend und/oder des Backend von * Optionales Sperren des Frontends auch für REDAXO-Benutzer (außer Admins) * Optionale Weiterleitung zu einer festgelegten URL, z.B. REDAXO-Login * Festlegen des HTTP-Statuscodes (z.B. 503 Service Unavailable) +* **Silent Mode**: Nur HTTP-Status ohne HTML-Content (ideal für Staging/Development) * Anpassen der Sperrseite durch eigenes Fragment (`maintenance/frontend.php`) * Definieren von Ausnahmen, die dennoch Zugriff erhalten, z.B. für * IP-Adressen * Hosts * YRewrite-Domains (neu in Version 3.0.0) * Meldung und Zeitraum zur Ankündigung eines Wartungsfensters definieren (neu in Version 3.0.0) +* **Zeitgesteuerte Wartung**: Automatische Aktivierung/Deaktivierung zu festgelegten Zeiten (neu in Version 3.5.0) + +### Zeitgesteuerte Wartung + +Die **zeitgesteuerte Wartung** ermöglicht es, den Wartungsmodus automatisch zu einem bestimmten Zeitpunkt zu aktivieren und zu deaktivieren: + +* **Cronjob-basiert**: Die Ausführung erfolgt ausschließlich über den Cronjob "Geplante Wartung prüfen" +* **Automatische Bereinigung**: Nach erfolgreicher Deaktivierung werden die geplanten Zeiten automatisch gelöscht + +**Verwendung:** + +1. **Cronjob einrichten** (erforderlich): + - Im REDAXO-Backend zu **System > Cronjobs** navigieren + - Auf **"+"** klicken, um einen neuen Cronjob zu erstellen + - Folgende Einstellungen vornehmen: + - **Name**: z.B. "Geplante Wartung prüfen" + - **Typ**: `Geplante Wartung prüfen` (aus Dropdown wählen) + - **Ausführungsart**: z.B. "Jede Minute" oder "Alle 5 Minuten" + - **Umgebung**: "Frontend, Backend, Skript" + - Speichern + +2. In den Frontend-Einstellungen unter "Zeitgesteuerte Wartung": + - **Startzeitpunkt** eingeben (z.B. `2025-12-31 02:00:00`) + - **Endzeitpunkt** eingeben (z.B. `2025-12-31 06:00:00`) +3. Speichern - der Wartungsmodus wird zur konfigurierten Zeit automatisch aktiviert und deaktiviert + +**Format**: `YYYY-MM-DD HH:MM:SS` (z.B. `2025-12-31 23:59:59`) + +**Wichtig**: Ohne eingerichteten Cronjob funktioniert die zeitgesteuerte Wartung nicht! ### Sperren des REDAXO-Backends @@ -46,6 +76,17 @@ Diese kann durch eine eigene HTML-Seite ersetzt werden. Dazu muss im Projekt-Add So kann bspw. eigener Text, Logo oder komplett andere Gestaltung erfolgen. +### Silent Mode für Staging/Development-Umgebungen + +Der **Silent Mode** ist ideal für Staging-Systeme und Development-Umgebungen, die permanent gesperrt sein sollen: + +* Sendet nur den HTTP-Status-Code (z.B. 503 oder 403) +* Zeigt **keine** HTML-Wartungsseite an +* Verhindert Rückschlüsse auf das verwendete CMS +* Perfekt für Produktiv-Vorschau-Systeme, die nur nach Login zugänglich sein sollen + +**Aktivierung:** In den erweiterten Einstellungen (Einstellungen) unter "HTTP-Einstellungen" die Option "Silent Mode" aktivieren. + ## Anzeige des aktuellen Status im REDAXO-Hauptmenü Der Menüeintrag erhält bei Aktivierung einer der Wartungsmodi ein zusätzliches Tag. @@ -66,40 +107,91 @@ Dazu einfach die aktuelle IP des Servers, auf dem REDAXO installiert ist und von ## Konsole -Es wird die im Backend ausgewählte Sperrseite angezeigt. +Das Addon bietet verschiedene Konsolen-Befehle zur Verwaltung des Wartungsmodus: -Aktivieren des Wartungsmodus: +### Status anzeigen + +Zeigt den aktuellen Status aller Wartungsmodi an: ```bash -php redaxo/bin/console maintenance:mode on +php redaxo/bin/console maintenance:mode status +``` + +### Frontend-Wartungsmodus + +Aktivieren: +```bash +php redaxo/bin/console maintenance:mode frontend on +``` + +Deaktivieren: +```bash +php redaxo/bin/console maintenance:mode frontend off +``` + +### Backend-Wartungsmodus + +Aktivieren: +```bash +php redaxo/bin/console maintenance:mode backend on +``` + +Deaktivieren: +```bash +php redaxo/bin/console maintenance:mode backend off +``` + +### Alle Modi gleichzeitig + +Alle Wartungsmodi (Frontend, Backend und alle Domains) aktivieren: +```bash +php redaxo/bin/console maintenance:mode all on +``` + +Alle Wartungsmodi deaktivieren: +```bash +php redaxo/bin/console maintenance:mode all off ``` -Deaktivieren des Wartungsmodus: +### Domain-spezifische Wartung (YRewrite) +Einzelne Domain sperren: ```bash +php redaxo/bin/console maintenance:mode domain example.com --lock +``` + +Einzelne Domain entsperren: +```bash +php redaxo/bin/console maintenance:mode domain example.com --unlock +``` + +### Legacy-Unterstützung + +Die alten Befehle funktionieren weiterhin (steuern nur den Frontend-Modus): + +```bash +php redaxo/bin/console maintenance:mode on php redaxo/bin/console maintenance:mode off ``` ## Autor -### FriendsOfREDAXO +**Thomas Skerbis** – [KLXM Crossmedia](https://klxm.de) -* -* +## Projekt-Lead -## Projekt-Team +* [Thomas Skerbis](https://github.com/skerbis) -* [Alexander Walther](https://github.com/alxndr-w) -* [Simon Krull](https://github.com/crydotsnake) ## Credits Danke an: -* [KLXM Crossmedia / Thomas Skerbis](https://klxm.de) // former lead * [Christian Gehrke](https://github.com/chrison94) // first version * [Thorben](https://github.com/eaCe) * [Oliver Kreischer](https://github.com/olien) +* [Alexander Walther](https://www.alexplus.de) +* [Simon Krull](https://github.com/crydotsnake) * u.v.a diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..94d1b5f --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,53 @@ +# Release Notes - Maintenance AddOn 4.0.0-beta1 + +## Was ist beim Update zu beachten? + +**⚠️ Breaking Change:** Die manuelle Domain-Whitelist (`allowed_yrewrite_domains`) wurde entfernt. Domain-basierte Wartung läuft jetzt ausschließlich über die neue Seite *Maintenance > Domains*. + +## Was ist neu? + +**Zeitgesteuerte Wartung** – Der Wartungsmodus kann jetzt automatisch zu bestimmten Zeiten aktiviert und deaktiviert werden (nur über Cronjob). + +**Silent Mode** – Sendet nur HTTP-Status-Code ohne HTML-Inhalt, ideal für Staging-Umgebungen. + +**Planungs-Seite** – Neue Übersichtsseite unter *Maintenance > Frontend > Planung* für zeitgesteuerte Wartung und Wartungsankündigungen. + +**Mehrsprachige Sperrseite** – Language-Switcher (DE/EN) mit SessionStorage-Unterstützung. + +**Domain-Verwaltung** – YRewrite-Domains können jetzt direkt über *Maintenance > Domains* verwaltet werden (keine manuelle Eingabe mehr nötig). + +**Erweiterte Console-Kommandos** – Wartungsmodus kann jetzt granular über die Konsole gesteuert werden: +```bash +php redaxo/bin/console maintenance:mode status +php redaxo/bin/console maintenance:mode frontend on|off +php redaxo/bin/console maintenance:mode backend on|off +php redaxo/bin/console maintenance:mode all on|off +php redaxo/bin/console maintenance:mode domain example.com --lock|--unlock +``` + +## Was hat sich geändert? + +### UX-Verbesserungen +- Einstellungen reorganisiert: Frontend > Planung, Frontend > Erweitert +- IP-Whitelist vereinfacht: Click-to-Add-Buttons, komma-getrennte Liste +- Sidebar mit Quick-Links und Bypass-URLs (nur für gesperrte Domains) +- Toggle-Buttons für Domain-Verwaltung +- Moderne Card-basierte UI mit Dark Mode Support + +### Performance & Code-Qualität +- YRewrite-Check nur einmal pro Request +- Redundante Checks entfernt +- PHP CS Fixer, externe Assets, REDAXO-Standards + +**Nach dem Update:** + +**Cronjob einrichten** (nur für zeitgesteuerte Wartung): + - System > Cronjobs > Neuen Cronjob erstellen + - Typ: "Geplante Wartung prüfen" + - Ausführungsart: "Jede Minute" oder "Alle 5 Minuten" + +3. **Domain-Einstellungen prüfen** (falls YRewrite verwendet): + - Öffnen Sie *Maintenance > Domains* + - Aktivieren/Deaktivieren Sie Domains per Toggle + +Die alte Konfiguration wird automatisch migriert. diff --git a/assets/css/ip-addresses.css b/assets/css/ip-addresses.css new file mode 100644 index 0000000..c9278ef --- /dev/null +++ b/assets/css/ip-addresses.css @@ -0,0 +1,22 @@ +/** + * IP Address Whitelist Styling + */ +.ip-addresses { + margin-top: 5px; +} + +.ip-address-row { + margin-bottom: 5px; + display: flex; + align-items: center; +} + +.ip-label { + min-width: 120px; +} + +.ip-code { + margin: 0 10px; + display: inline-block; + min-width: 100px; +} diff --git a/assets/css/maintenance-icons.css b/assets/css/maintenance-icons.css new file mode 100644 index 0000000..d56ffdd --- /dev/null +++ b/assets/css/maintenance-icons.css @@ -0,0 +1,97 @@ +/** + * Maintenance AddOn - Icon Styles + * Font Awesome Icons als Ersatz für Emojis + */ + +/* Select-Optionen mit Icons */ +.rex-page-maintenance select option[value="1"] { + font-weight: bold; +} + +/* Status-Indikatoren für aktivierte/deaktivierte Zustände */ +.maintenance-status-active::before { + font-family: 'FontAwesome'; + content: '\f06d'; /* fa-fire */ + color: #d9534f; + margin-right: 5px; +} + +.maintenance-status-inactive::before { + font-family: 'FontAwesome'; + content: '\f058'; /* fa-check-circle */ + color: #5cb85c; + margin-right: 5px; +} + +.maintenance-status-ok::before { + font-family: 'FontAwesome'; + content: '\f00c'; /* fa-check */ + color: #5cb85c; + margin-right: 5px; +} + +.maintenance-status-blocked::before { + font-family: 'FontAwesome'; + content: '\f05e'; /* fa-ban */ + color: #d9534f; + margin-right: 5px; +} + +/* Icon für Passwort-Authentifizierung */ +.maintenance-auth-password::before { + font-family: 'FontAwesome'; + content: '\f084'; /* fa-key */ + margin-right: 5px; +} + +/* Icon für URL-Authentifizierung */ +.maintenance-auth-url::before { + font-family: 'FontAwesome'; + content: '\f0c1'; /* fa-link */ + margin-right: 5px; +} + +/* Wartungsmodus Backend Labels mit Icons versehen */ +.rex-page-maintenance .form-group label[for*="block_backend"] + .rex-select-style option:first-child::before, +.rex-page-maintenance .form-group label[for*="block_frontend"] + .rex-select-style option:first-child::before { + font-family: 'FontAwesome'; + content: '\f058 '; /* fa-check-circle (grün) */ +} + +.rex-page-maintenance .form-group label[for*="block_backend"] + .rex-select-style option:last-child::before, +.rex-page-maintenance .form-group label[for*="block_frontend"] + .rex-select-style option:last-child::before { + font-family: 'FontAwesome'; + content: '\f06d '; /* fa-fire (rot) */ +} + +/* Bootstrap Select Pills für bessere Übersichtlichkeit */ +.maintenance-select-pill { + display: inline-block; + padding: 0.25em 0.6em; + font-size: 85%; + font-weight: 700; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0.25rem; +} + +.maintenance-select-pill.active { + background-color: #d9534f; + color: white; +} + +.maintenance-select-pill.inactive { + background-color: #5cb85c; + color: white; +} + +/* Domain-Tabelle Styling */ +.rex-page-maintenance table.table td { + vertical-align: middle; +} + +.rex-page-maintenance table.table .text-muted { + font-size: 0.9em; +} diff --git a/assets/js/domains.js b/assets/js/domains.js new file mode 100644 index 0000000..622d204 --- /dev/null +++ b/assets/js/domains.js @@ -0,0 +1,12 @@ +/** + * Domain Management - Toggle Individual Domains + */ +$(document).on('rex:ready', function() { + $('#all-domains-locked').on('change', function() { + if ($(this).val() == '1') { + $('#individual-domains').slideUp(); + } else { + $('#individual-domains').slideDown(); + } + }); +}); diff --git a/assets/js/frontend-advanced.js b/assets/js/frontend-advanced.js new file mode 100644 index 0000000..a1a877f --- /dev/null +++ b/assets/js/frontend-advanced.js @@ -0,0 +1,52 @@ +/** + * Frontend Advanced Settings - IP Whitelist Management + */ +$(document).on('rex:ready', function() { + // Funktion zum Hinzufügen einer IP-Adresse zum Whitelist-Feld + function addIpToWhitelist(ip) { + var ipField = $('input[name="config[maintenance][allowed_ips]"]'); + + if (!ipField.length) { + console.error('IP field not found'); + return; + } + + if (ipField.val().trim() === '') { + // Wenn das Feld leer ist, einfach die IP hinzufügen + ipField.val(ip); + } else { + // IP-Adressen als Array verarbeiten und alle Leerzeichen entfernen + var ips = ipField.val().split(',').map(function(ip) { + return ip.trim(); + }).filter(function(ip) { + // Leere Einträge filtern + return ip !== ''; + }); + + // Prüfen, ob IP bereits enthalten ist + if (ips.indexOf(ip) === -1) { + ips.push(ip); + // Saubere Komma-getrennte Liste ohne unnötige Leerzeichen + ipField.val(ips.join(',')); + } + } + } + + // Client-IP-Adresse hinzufügen + $(document).on('click', '#maintenance-add-ip', function(e) { + e.preventDefault(); + var currentIp = $(this).data('ip'); + if (currentIp) { + addIpToWhitelist(currentIp); + } + }); + + // Server-IP-Adresse hinzufügen + $(document).on('click', '#maintenance-add-server-ip', function(e) { + e.preventDefault(); + var serverIp = $(this).data('ip'); + if (serverIp) { + addIpToWhitelist(serverIp); + } + }); +}); diff --git a/boot.php b/boot.php index 677bdeb..6df48f0 100644 --- a/boot.php +++ b/boot.php @@ -15,10 +15,15 @@ if (rex::isSetup()) { return; } + +// Register cronjob type +if (rex_addon::get('cronjob')->isAvailable()) { + rex_cronjob_manager::registerType(rex_cronjob_scheduled_maintenance::class); +} rex_extension::register('PACKAGES_INCLUDED', static function () { $addon = rex_addon::get('maintenance'); - if (rex::isFrontend() && (bool) $addon->getConfig('block_frontend')) { + if (rex::isFrontend() && ((bool) $addon->getConfig('block_frontend') || Maintenance::isDomainInMaintenance())) { Maintenance::checkFrontend(); } if (rex::isBackend() && (bool) $addon->getConfig('block_backend')) { @@ -27,13 +32,13 @@ if (rex::isBackend()) { Maintenance::setIndicators(); - rex_view::addJsFile($addon->getAssetsUrl('dist/bootstrap-tokenfield.js')); - rex_view::addJsFile($addon->getAssetsUrl('dist/init_bootstrap-tokenfield.js')); - rex_view::addCssFile($addon->getAssetsUrl('dist/css/bootstrap-tokenfield.css')); rex_view::addCssFile($addon->getAssetsUrl('css/maintenance.css')); + rex_view::addCssFile($addon->getAssetsUrl('css/maintenance-icons.css')); - if ('maintenance/frontend' === rex_be_controller::getCurrentPage()) { + if ('maintenance/frontend' === rex_be_controller::getCurrentPage() + || 'maintenance/frontend/index' === rex_be_controller::getCurrentPage() + || 'maintenance/frontend/scheduled' === rex_be_controller::getCurrentPage()) { rex_extension::register('OUTPUT_FILTER', static function (rex_extension_point $ep) { $suchmuster = 'class="###maintenance-settings-editor###"'; $ersetzen = (string) rex_config::get('maintenance', 'editor'); // @phpstan-ignore-line diff --git a/fragments/maintenance/frontend.php b/fragments/maintenance/frontend.php index 3c92daf..b56765f 100644 --- a/fragments/maintenance/frontend.php +++ b/fragments/maintenance/frontend.php @@ -1,37 +1,509 @@ - + - + + <?php - if (rex_addon::get('yrewrite')->isAvailable() && null !== rex_yrewrite::getCurrentDomain()?->getName()) { + if (rex_addon::exists('yrewrite') && rex_addon::get('yrewrite')->isAvailable() && null !== rex_yrewrite::getCurrentDomain()?->getName()) { echo rex_yrewrite::getCurrentDomain()->getName(); } else { echo rex::getServerName(); } ?> - Maintenance - +
-
-

-

This website is temporarily unavailable.

-

Diese Website ist vorübergehend nicht erreichbar.

+ +
+
+ +
+ + +
+
+ + + + + + + + isAvailable() && null !== rex_yrewrite::getCurrentDomain()?->getName()) { + $domain = rex_yrewrite::getCurrentDomain()->getName(); + } else { + $domain = rex::getServerName(); + } + ?> + +

+ + Maintenance + Wartung + + + +

+ + +

+ +

+ + + +
+ +
+ + + +
+ +
+ + + subfragment('maintenance/announcement.php'); echo $this->subfragment('maintenance/login.php'); echo $this->subfragment('maintenance/reload.php'); ?>
+ + + + diff --git a/fragments/maintenance/login.php b/fragments/maintenance/login.php index 91b651a..2ce3725 100644 --- a/fragments/maintenance/login.php +++ b/fragments/maintenance/login.php @@ -1,10 +1,17 @@ +$authentication_mode = rex_config::get('maintenance', 'authentication_mode', ''); + +// Debug output - remove this later +// echo ''; + +// Show login form only if password mode is selected +// (This fragment is only loaded when maintenance mode is active) +if ('password' === $authentication_mode) { ?> diff --git a/lang/de_de.lang b/lang/de_de.lang index 28ed37f..f2be68b 100644 --- a/lang/de_de.lang +++ b/lang/de_de.lang @@ -1,68 +1,83 @@ // Addon-Package -maintenance_title = Wartungsmodus +maintenance_title = Wartung maintenance_frontend_title = Website-Frontend +maintenance_frontend_general_title = Wartung maintenance_backend_title = REDAXO-Backend maintenance_docs_title = Hilfe +maintenance_planning_title = Planung +maintenance_planning_page_title = Wartungsplanung maintenance_update_interval_field_label = Aktualisierungsintervall maintenance_update_interval_field_notice = Geben Sie die Sekunden ein, nach denen die Seite automatisch neu geladen werden soll. Setzen Sie 0, um die automatische Aktualisierung zu deaktivieren. Standardwert: 60 Sekunden // Console-Commands -maintenance_mode_command_description = Schaltet den Wartungsmodus ein, oder aus -maintenance_mode_command_state_description = Wartungsmodus einstellen: ein oder aus +maintenance_mode_command_description = Schaltet die Wartung ein, oder aus +maintenance_mode_command_state_description = Wartung einstellen: ein oder aus -maintenance_mode_activated = Wartungsmodus Aktiviert -maintenance_mode_deactivated = Wartungsmodus deaktiviert -maintenance_mode_already_activated = Der Wartungsmodus ist bereits aktiviert -maintenance_mode_already_deactivated = Der Wartungsmodus ist bereits deaktiviert -maintenance_mode_invalid = Ungültiges Argument! Benutze „on“ oder „off“ +maintenance_mode_activated = Wartung Aktiviert +maintenance_mode_deactivated = Wartung deaktiviert +maintenance_mode_already_activated = Die Wartung ist bereits aktiviert +maintenance_mode_already_deactivated = Die Wartung ist bereits deaktiviert +maintenance_mode_invalid = Ungültiges Argument! Benutze „on" oder „off" + +// Cronjob + +maintenance_cronjob_scheduled_name = Geplante Wartung prüfen + +// Allgemein // Einstellungsseite Backend maintenance_backend_headline_label = Backend-Überschrift -maintenance_backend_headline_notice = Geben Sie eine beliebige Überschrift für die Wartungsmodusmaske im Backend ein. Die Standardüberschrift lautet: "Maintenance / Wartung". +maintenance_backend_headline_notice = Geben Sie eine beliebige Überschrift für die Wartungsmaske im Backend ein. Die Standardüberschrift lautet: "Maintenance / Wartung". -maintenance_settings_backend_title = Wartungsmodus-Einstellungen für das REDAXO-Backend +maintenance_settings_backend_title = Wartungs-Einstellungen für das REDAXO-Backend -maintenance_block_backend_label = Wartungsmodus aktivieren -maintenance_block_backend_true = 🔴 Wartungsmodus aktiviert -maintenance_block_backend_false = 🟢 Wartungsmodus deaktiviert +maintenance_block_backend_label = Wartung aktivieren +maintenance_block_backend_true = Wartung aktiviert +maintenance_block_backend_false = Wartung deaktiviert maintenance_redirect_backend_to_url_label = Weiterleitung zu (optional) -maintenance_redirect_backend_to_url_notice = URL angeben, zu der die Benutzer weitergeleitet werden, wenn das REDAXO-Backend im Wartungsmodus ist. +maintenance_redirect_backend_to_url_notice = URL angeben, zu der die Benutzer weitergeleitet werden, wenn das REDAXO-Backend in Wartung ist. // Einstellungsseite Frontend maintenance_frontend_headline_label = Frontend-Überschrift -maintenance_frontend_headline_notice = Geben Sie eine beliebige Überschrift für die Wartungsmodusmaske im Frontend ein. Die Standardüberschrift lautet: "Maintenance / Wartung". +maintenance_frontend_headline_notice = Geben Sie eine beliebige Überschrift für die Wartungsmaske im Frontend ein. Die Standardüberschrift lautet: "Maintenance / Wartung". + +maintenance_text_en_label = Wartungstext (Englisch) +maintenance_text_en_notice = Text, der Besuchern auf Englisch angezeigt wird. -maintenance_settings_frontend_title = Wartungsmodus-Einstellungen für das Website-Frontend +maintenance_text_de_label = Wartungstext (Deutsch) +maintenance_text_de_notice = Text, der Besuchern auf Deutsch angezeigt wird. + +maintenance_settings_frontend_title = Wartungs-Einstellungen für das Website-Frontend maintenance_general_title = Allgemein -maintenance_block_frontend_label = Wartungsmodus aktivieren -maintenance_block_frontend_false = 🟢 Wartungsmodus deaktiviert -maintenance_block_frontend_true = 🔴 Wartungsmodus aktiviert +maintenance_block_frontend_label = Wartung aktivieren +maintenance_block_frontend_false = Wartung deaktiviert +maintenance_block_frontend_true = Wartung aktiviert maintenance_block_frontend_rex_user_label = REDAXO-Benutzer zusätzlich ausperren -maintenance_block_frontend_rex_user_false = 🆗 Eingeloggte REDAXO-Benutzer haben weiterhin Zugriff -maintenance_block_frontend_rex_user_rex_user = 🚷 Auch REDAXO-Benutzer ausperren +maintenance_block_frontend_rex_user_false = Eingeloggte REDAXO-Benutzer haben weiterhin Zugriff +maintenance_block_frontend_rex_user_rex_user = Auch REDAXO-Benutzer ausperren maintenance_block_frontend_rex_user_notice = Wenn aktiviert, haben nur Administratoren und Personen mit Password oder Link Zugriff auf das Frontend. -maintenance_authentification_mode_label = Authentifizierung -maintenance_authentification_mode_notice = Authentifizierungsmethode auswählen, um Zugriff zu erhalten. +maintenance_authentication_mode_label = Authentifizierung +maintenance_authentication_mode_notice = Authentifizierungsmethode auswählen, um Zugriff zu erhalten. -maintenance_authentification_mode_none = Keine Authentifizierung -maintenance_authentification_mode_password = 🔑 Passwort -maintenance_authentification_mode_url = 🔗 Geheim-URL (Standard) +maintenance_authentication_mode_none = Keine Authentifizierung +maintenance_authentication_mode_password = Passwort +maintenance_authentication_mode_url = Geheim-URL (Standard) maintenance_secret_label = Passwort maintenance_secret_notice = Tragen Sie hier einen beliebigen Text ein (Passwort, Passphrase, z.B. {0}), das zum Aufrufen der Seite per Geheim-URL oder Passwort-Eingabe genutzt werden soll. -maintenance_redirect_frontend_to_url_label = Weiterleitung zu (optional) -maintenance_redirect_frontend_to_url_notice = Nur bei Geheim-URL: URL angeben, zu der die Besucher weitergeleitet werden, wenn die Website im Wartungsmodus ist. +maintenance_redirect_frontend_to_url = Weiterleitungs-URL +maintenance_redirect_frontend_to_url_notice = Nur bei Geheim-URL: URL angeben, zu der die Besucher weitergeleitet werden, wenn die Website in Wartung ist. maintenance_block_frontend_session_label = Eingeloggte User ausperren maintenance_block_frontend_session_notice = @@ -72,16 +87,26 @@ maintenance_http_response_code_label = Gewünschter Antwort-Header maintenance_http_response_code_503 = 503 - HTTP_SERVICE_UNAVAILABLE (empfohlen, wenn die Website temporär nicht verfügbar ist) maintenance_http_response_code_403 = 403 - HTTP_FORBIDDEN (Der Zugriff auf eine Ressource ist dauerhaft verboten) +maintenance_silent_mode_label = Silent Mode (Staging/Development) +maintenance_silent_mode_enable = Nur HTTP-Status senden, keine Inhalte anzeigen +maintenance_silent_mode_notice = Ideal für Staging-Umgebungen: Sendet nur den HTTP-Status-Code ohne HTML-Inhalt. Verhindert Rückschlüsse auf das verwendete CMS und zeigt keine Wartungsmeldungen an. + +maintenance_ip_added = hinzugefügt +maintenance_my_ip_title = Meine IP-Adresse +maintenance_add_my_ip = Meine IP zur Whitelist hinzufügen + maintenance_announcement_title = Ankündigung der Wartungsarbeiten maintenance_editor_label = Editor -maintenance_editor_notice = Editor anhand Attribute für das obige Textfeld "Ankündigung der Wartungsarbeiten" festlegen, z.B. class="form-control redactor-editor--default" für Redactor, passende Attribute auch für TinyMCE, CKEditor, etc. +maintenance_editor_notice = Editor anhand Attribute für das Textfeld "Ankündigung der Wartungsarbeiten" festlegen, z.B. class="form-control redactor-editor--default" für Redactor, passende Attribute auch für TinyMCE, CKEditor, etc. maintenance_announcement_label = Text -maintenance_announcement_notice = Besucher*innen mitteilen, wann das Wartungsfenster den Wartungsmodus gesetzt wird. Ausgabe über \FriendsOfRedaxo\Maintenance\Maintenance::showAnnouncement(). +maintenance_announcement_title = Wartungsankündigung +maintenance_announcement_notice = Besucher*innen mitteilen, wann das Wartungsfenster die Wartung gesetzt wird. Ausgabe über \FriendsOfRedaxo\Maintenance\Maintenance::showAnnouncement(). maintenance_announcement_start_date_label = Startdatum -maintenance_announcement_start_date_notice = Datum, ab wann die Ankündigung gezeigt werden soll. Nicht, ab wann die Website in den Wartungsmodus wechseln soll. Aktuelle Serverzeit: {0}. +maintenance_announcement_start_date = Ankündigung ab +maintenance_announcement_start_date_notice = Datum, ab wann die Ankündigung gezeigt werden soll. Nicht, ab wann die Website in Wartung wechseln soll. Aktuelle Serverzeit: {0}. maintenance_announcement_end_date_label = Enddatum maintenance_announcement_end_date_notice = Datum, bis wann die Ankündigung gezeigt werden soll. Aktuelle Serverzeit: {0}. @@ -89,7 +114,12 @@ maintenance_announcement_end_date_notice = Datum, bis wann die Ankündig maintenance_allowed_access_title = Ausnahmen maintenance_allowed_ips_label = Liste erlaubter IP-Adressen -maintenance_allowed_ips_notice = IP-Adresse(n) ein, die weiterhin Zugriff auf das Frontend haben soll(en). Deine aktuelle IP-Adresse lautet {0}, die des Servers lautet {1}. +maintenance_allowed_ips_notice = IP-Adresse(n) eingeben, die weiterhin Zugriff auf das Frontend haben soll(en). Mehrere IPs mit Komma trennen. +maintenance_add_ip_hint = hinzufügen +maintenance_your_ip = Ihre IP +maintenance_server_ip = Server-IP +maintenance_add_ip = Hinzufügen +maintenance_add_server_ip = Server-IP hinzufügen maintenance_allowed_domains_label = Liste erlaubter Domains maintenance_allowed_domains_notice = Domains eintragen, die nicht gesperrt werden sollen. @@ -98,6 +128,62 @@ maintenance_allowed_yrewrite_domains_label = Liste erlaubter YRewrite-Domains maintenance_allowed_yrewrite_domains_notice = YRewrite-Domains eintragen, die nicht gesperrt werden sollen. maintenance_copy_url_title = URL kopieren +maintenance_copy = Kopieren +maintenance_copied = Kopiert! maintenance_preview_title = Vorschau maintenance_preview = Vorschau in neuem Fenster anzeigen + +// Domain-Verwaltung + +maintenance_domains_title = YRewrite-Domains +maintenance_domains_management = Domain-Verwaltung +maintenance_domain = Domain +maintenance_maintenance_active = Wartung +maintenance_domain_inactive = Inaktiv +maintenance_domain_active = Aktiv +maintenance_lock_all_domains = Alle Domains sperren +maintenance_domains_notice = Hier können Sie die Wartung für einzelne YRewrite-Domains aktivieren oder deaktivieren. Diese Einstellung überschreibt die allgemeine Frontend-Einstellung für die jeweilige Domain. +maintenance_yrewrite_not_available = Das YRewrite-AddOn ist nicht installiert oder aktiviert. Die Domain-Verwaltung ist nicht verfügbar. +maintenance_settings_saved = Die Einstellungen wurden gespeichert. +maintenance_yes = Ja +maintenance_no = Nein +maintenance_save = Speichern + +// Erweiterte Einstellungen + +maintenance_advanced_title = Einstellungen +maintenance_settings_title = Einstellungen +maintenance_advanced_settings_title = Erweiterte Einstellungen +maintenance_editor_settings_title = Editor-Einstellungen +maintenance_access_control_title = Zugriffskontrolle +maintenance_authentication_title = Authentifizierung +maintenance_http_settings_title = HTTP-Einstellungen +maintenance_admin_only = Nur für Administratoren verfügbar +maintenance_announcement_settings = Ankündigung +maintenance_announcement_info_title = Verwendung +maintenance_announcement_info_text = Die Wartungsankündigung kann im Template mit der folgenden Funktion ausgegeben werden +maintenance_announcement_usage = Code-Beispiel + +// Scheduled Maintenance + +maintenance_scheduled_title = Zeitgesteuerte Wartung +maintenance_scheduled_info = Die zeitgesteuerte Wartung aktiviert und deaktiviert den Wartungsmodus automatisch zu den konfigurierten Zeiten. Die Ausführung erfolgt ausschließlich über einen Cronjob. +maintenance_scheduled_start_label = Startzeitpunkt +maintenance_scheduled_start_notice = Wartungsmodus wird automatisch aktiviert (Format: YYYY-MM-DD HH:MM:SS) +maintenance_scheduled_end_label = Endzeitpunkt +maintenance_scheduled_end_notice = Wartungsmodus wird automatisch deaktiviert und geplante Zeiten werden zurückgesetzt +maintenance_scheduled_active = Geplante Wartung aktiv +maintenance_scheduled_starts_at = Startet am +maintenance_scheduled_ends_at = Endet am +maintenance_scheduled_example = Beispiel: 2025-12-31 02:00:00 +maintenance_scheduled_cronjob_missing = Cronjob für zeitgesteuerte Wartung ist nicht eingerichtet! Ohne Cronjob funktioniert die zeitgesteuerte Wartung nicht. +maintenance_scheduled_cronjob_create = Cronjob jetzt erstellen +maintenance_scheduled_cronjob_active = Cronjob ist eingerichtet und aktiv + +// Quick Links + +maintenance_quick_links = Schnellzugriff +maintenance_advanced_settings = Erweiterte Einstellungen +maintenance_domain_settings = Domain-Verwaltung +maintenance_bypass_url_default = Bypass-URL (Standard-Domain) diff --git a/lang/en_gb.lang b/lang/en_gb.lang index 361f6df..47780f8 100644 --- a/lang/en_gb.lang +++ b/lang/en_gb.lang @@ -86,7 +86,11 @@ maintenance_announcement_end_date_notice = Date until when the announcem maintenance_allowed_access_title = Exceptions maintenance_allowed_ips_label = List of allowed IP addresses -maintenance_allowed_ips_notice = Enter IP address(es) that should still have access to the frontend. The current IP address is {0}, the IP address of the server is {1}. +maintenance_allowed_ips_notice = Enter IP address(es) that should still have access to the frontend. Separate multiple IPs with commas. +maintenance_your_ip = Your IP +maintenance_server_ip = Server IP +maintenance_add_ip = Add +maintenance_add_server_ip = Add Server IP maintenance_allowed_domains_label = List of allowed domains maintenance_allowed_domains_notice = Enter domains that should not be blocked. diff --git a/lib/Maintenance.php b/lib/Maintenance.php index df2e1ae..c0e6ce2 100644 --- a/lib/Maintenance.php +++ b/lib/Maintenance.php @@ -17,6 +17,7 @@ use rex_yrewrite; use Throwable; +use function count; use function in_array; use const FILTER_VALIDATE_IP; @@ -121,6 +122,36 @@ public static function isYrewriteDomainAllowed(): bool return false; } + /** + * Checks if the current YRewrite domain is in maintenance mode. + * @api + */ + public static function isDomainInMaintenance(): bool + { + // Check if all domains are locked globally + $allDomainsLocked = (bool) self::getConfig('all_domains_locked', false); + if ($allDomainsLocked) { + return true; + } + + // Check individual domain status + if (!rex_addon::exists('yrewrite') || !rex_addon::get('yrewrite')->isAvailable()) { + return false; + } + + if ($ydomain = rex_yrewrite::getDomainByArticleId(rex_article::getCurrentId(), rex_clang::getCurrentId())) { + $domainName = $ydomain->getName(); + $domainStatus = (array) self::getConfig('domain_status', []); + + // Check if this specific domain is in maintenance mode + if (isset($domainStatus[$domainName]) && $domainStatus[$domainName]) { + return true; + } + } + + return false; + } + /** * Checks if the maintenance secret is valid. * @api @@ -135,11 +166,11 @@ public static function isSecretAllowed(): bool } $maintenance_secret = rex_request('maintenance_secret', 'string', ''); - $authentification_mode = (string) self::getConfig('authentification_mode', ''); + $authentication_mode = (string) self::getConfig('authentication_mode', ''); // Authentifizierung prüfen - für URL-Parameter und auch bei leerem Modus - $authentification_mode = (string) self::getConfig('authentification_mode', ''); - if (('' === $authentification_mode || 'URL' === $authentification_mode || 'password' === $authentification_mode) + $authentication_mode = (string) self::getConfig('authentication_mode', ''); + if (('' === $authentication_mode || 'URL' === $authentication_mode || 'password' === $authentication_mode) && '' !== $config_secret && $maintenance_secret === $config_secret) { rex_set_session('maintenance_secret', $maintenance_secret); @@ -206,6 +237,45 @@ private static function is2FARequiredAndNotCompleted(): bool return false; } + /** + * Checks and applies scheduled maintenance mode (automatic activation/deactivation). + * Called on every request to check if maintenance should be auto-enabled or auto-disabled. + */ + public static function checkScheduledMaintenance(): void + { + $addon = self::getAddOn(); + $now = time(); + + $scheduledStart = (string) self::getConfig('scheduled_start', ''); + $scheduledEnd = (string) self::getConfig('scheduled_end', ''); + + // Parse scheduled times + $startTime = '' !== $scheduledStart ? strtotime($scheduledStart) : false; + $endTime = '' !== $scheduledEnd ? strtotime($scheduledEnd) : false; + + $blockFrontend = self::getBoolConfig('block_frontend', false); + + // Auto-activate: If we're past start time and before end time, enable maintenance + if (false !== $startTime && $now >= $startTime) { + if (false === $endTime || $now < $endTime) { + // Should be in maintenance mode + if (!$blockFrontend) { + $addon->setConfig('block_frontend', true); + } + } + } + + // Auto-deactivate: If we're past end time, disable maintenance + if (false !== $endTime && $now >= $endTime) { + if ($blockFrontend) { + $addon->setConfig('block_frontend', false); + // Clear scheduled times after successful deactivation + $addon->setConfig('scheduled_start', ''); + $addon->setConfig('scheduled_end', ''); + } + } + } + /** * Checks frontend access and shows maintenance page if necessary. */ @@ -213,13 +283,30 @@ public static function checkFrontend(): void { rex_login::startSession(); + // Check if the current domain is in maintenance mode (new domain-based logic) + $domainInMaintenance = self::isDomainInMaintenance(); + $blockFrontend = self::getBoolConfig('block_frontend', false); + + // If neither frontend blocking nor domain-based maintenance is active, allow access + if (!$blockFrontend && !$domainInMaintenance) { + return; + } + + // Silent mode check early: Only send HTTP status, no further processing + $silentMode = (bool) self::getConfig('silent_mode', false); + if ($silentMode) { + $responsecode = (int) self::getConfig('http_response_code', 503); + header('HTTP/1.1 ' . $responsecode); + exit; + } + // If the IP address is allowed, do not block the request if (self::isIpAllowed()) { return; } // If YRewrite is installed and the domain is allowed, do not block the request - if (rex_addon::get('yrewrite')->isAvailable() && self::isYrewriteDomainAllowed()) { + if (rex_addon::exists('yrewrite') && rex_addon::get('yrewrite')->isAvailable() && self::isYrewriteDomainAllowed()) { return; } @@ -264,12 +351,14 @@ public static function checkFrontend(): void $redirect_url = (string) self::getConfig('redirect_frontend_to_url', ''); $responsecode = (int) self::getConfig('http_response_code', 503); - $mpage = new rex_fragment(); + // Redirect if configured if ('' !== $redirect_url) { rex_response::setStatus(rex_response::HTTP_MOVED_TEMPORARILY); rex_response::sendRedirect($redirect_url); } + // Show maintenance page + $mpage = new rex_fragment(); header('HTTP/1.1 ' . $responsecode); exit($mpage->parse('maintenance/frontend.php')); } @@ -309,6 +398,17 @@ public static function setIndicators(): void $page['icon'] .= ' fa-toggle-on block_frontend'; } + // Check for domain-based maintenance + $domainStatus = (array) self::getConfig('domain_status', []); + $allDomainsLocked = (bool) self::getConfig('all_domains_locked', false); + $activeDomains = array_filter($domainStatus); + + if ($allDomainsLocked || !empty($activeDomains)) { + $count = $allDomainsLocked ? 'All' : count($activeDomains); + $page['title'] .= ' D:' . $count . ''; + $page['icon'] .= ' fa-sitemap'; + } + self::getAddOn()->setProperty('page', $page); } diff --git a/lib/command/maintenance_mode.php b/lib/command/maintenance_mode.php index e7ba736..d01e5ad 100644 --- a/lib/command/maintenance_mode.php +++ b/lib/command/maintenance_mode.php @@ -3,6 +3,7 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class rex_maintenance_mode_command extends rex_console_command @@ -13,10 +14,27 @@ protected function configure(): void $this ->setName('maintenance:mode') ->setDescription(rex_i18n::msg('maintenance_mode_command_description')) + ->addArgument( + 'action', + InputArgument::REQUIRED, + 'Action: status, frontend, backend, domain, all', + ) ->addArgument( 'state', InputArgument::OPTIONAL, - rex_i18n::msg('maintenance_mode_command_state_description'), + 'State: on/off (for frontend, backend, all) or domain name (for domain)', + ) + ->addOption( + 'lock', + 'l', + InputOption::VALUE_NONE, + 'Lock domain (use with domain action)', + ) + ->addOption( + 'unlock', + 'u', + InputOption::VALUE_NONE, + 'Unlock domain (use with domain action)', ); } @@ -26,32 +44,155 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io = $this->getStyle($input, $output); $io->title(rex_i18n::msg('maintenance_title')); + $action = $input->getArgument('action'); $state = $input->getArgument('state'); - $addon = rex_addon::get('maintenance'); - $currentBlockFrontendConfiguration = $addon->getConfig(); - if (1 === $currentBlockFrontendConfiguration['block_frontend'] && 'on' === $state) { - $io->info(rex_i18n::msg('maintenance_mode_already_activated')); + switch ($action) { + case 'status': + return $this->showStatus($io, $addon); + + case 'frontend': + return $this->toggleFrontend($io, $addon, $state); + + case 'backend': + return $this->toggleBackend($io, $addon, $state); + + case 'domain': + return $this->handleDomain($io, $addon, $state, $input->getOption('lock'), $input->getOption('unlock')); + + case 'all': + return $this->toggleAll($io, $addon, $state); + + // Legacy support: "on" and "off" as direct arguments + case 'on': + case 'off': + return $this->toggleFrontend($io, $addon, $action); + + default: + $io->error('Invalid action. Use: status, frontend, backend, domain, all'); + return Command::INVALID; + } + } + + private function showStatus(OutputInterface $io, rex_addon $addon): int + { + $blockFrontend = $addon->getConfig('block_frontend', 0); + $blockBackend = $addon->getConfig('block_backend', 0); + $allDomainsLocked = $addon->getConfig('all_domains_locked', false); + $domainStatus = $addon->getConfig('domain_status', []); + + $io->section('Current Status'); + + $io->text([ + 'Frontend: ' . ($blockFrontend ? 'LOCKED' : 'Open'), + 'Backend: ' . ($blockBackend ? 'LOCKED' : 'Open'), + 'All Domains: ' . ($allDomainsLocked ? 'LOCKED' : 'Open'), + ]); + + if (!empty($domainStatus) && rex_addon::exists('yrewrite') && rex_addon::get('yrewrite')->isAvailable()) { + $io->section('Domain Status'); + $lockedDomains = []; + foreach ($domainStatus as $domain => $locked) { + if ($locked) { + $lockedDomains[] = $domain; + } + } + if (!empty($lockedDomains)) { + $io->listing($lockedDomains); + } else { + $io->text('No domains locked'); + } + } + + return Command::SUCCESS; + } + + private function toggleFrontend(OutputInterface $io, rex_addon $addon, ?string $state): int + { + if (!$state || !in_array($state, ['on', 'off'])) { + $io->error('Invalid state. Use: on or off'); + return Command::INVALID; + } + + $currentState = $addon->getConfig('block_frontend', 0); + $newState = ('on' === $state) ? 1 : 0; + + if ($currentState === $newState) { + $io->info('Frontend maintenance is already ' . $state); + return Command::FAILURE; + } + + $addon->setConfig('block_frontend', $newState); + $io->success('Frontend maintenance ' . ($newState ? 'activated' : 'deactivated')); + return Command::SUCCESS; + } + + private function toggleBackend(OutputInterface $io, rex_addon $addon, ?string $state): int + { + if (!$state || !in_array($state, ['on', 'off'])) { + $io->error('Invalid state. Use: on or off'); + return Command::INVALID; + } + + $currentState = $addon->getConfig('block_backend', 0); + $newState = ('on' === $state) ? 1 : 0; + + if ($currentState === $newState) { + $io->info('Backend maintenance is already ' . $state); return Command::FAILURE; } - if (0 === $currentBlockFrontendConfiguration['block_frontend'] && 'off' === $state) { - $io->info(rex_i18n::msg('maintenance_mode_already_deactivated')); + $addon->setConfig('block_backend', $newState); + $io->success('Backend maintenance ' . ($newState ? 'activated' : 'deactivated')); + return Command::SUCCESS; + } + + private function handleDomain(OutputInterface $io, rex_addon $addon, ?string $domain, bool $lock, bool $unlock): int + { + if (!rex_addon::exists('yrewrite') || !rex_addon::get('yrewrite')->isAvailable()) { + $io->error('YRewrite addon is not installed or not available'); return Command::FAILURE; } - if ('on' === $state) { - $addon->setConfig('block_frontend', 1); - $io->success(rex_i18n::msg('maintenance_mode_activated')); - return Command::SUCCESS; + if (!$domain) { + $io->error('Domain name required'); + return Command::INVALID; + } + + if ($lock === $unlock) { + $io->error('Use either --lock or --unlock option'); + return Command::INVALID; + } + + $domains = rex_yrewrite::getDomains(); + if (!isset($domains[$domain])) { + $io->error('Domain "' . $domain . '" not found'); + return Command::FAILURE; } - if ('off' === $state) { - $addon->setConfig('block_frontend', 0); - $io->success(rex_i18n::msg('maintenance_mode_deactivated')); - return Command::SUCCESS; + + $domainStatus = $addon->getConfig('domain_status', []); + $domainStatus[$domain] = $lock ? 1 : 0; + $addon->setConfig('domain_status', $domainStatus); + + $io->success('Domain "' . $domain . '" ' . ($lock ? 'locked' : 'unlocked')); + return Command::SUCCESS; + } + + private function toggleAll(OutputInterface $io, rex_addon $addon, ?string $state): int + { + if (!$state || !in_array($state, ['on', 'off'])) { + $io->error('Invalid state. Use: on or off'); + return Command::INVALID; } - $io->error(rex_i18n::msg('maintenance_mode_invalid')); - return Command::INVALID; + + $newState = ('on' === $state) ? 1 : 0; + + $addon->setConfig('block_frontend', $newState); + $addon->setConfig('block_backend', $newState); + $addon->setConfig('all_domains_locked', (bool) $newState); + + $io->success('All maintenance modes ' . ($newState ? 'activated' : 'deactivated')); + return Command::SUCCESS; } } diff --git a/lib/cronjob.php b/lib/cronjob.php new file mode 100644 index 0000000..8e7277d --- /dev/null +++ b/lib/cronjob.php @@ -0,0 +1,59 @@ +getConfig('scheduled_start', ''); + $scheduledEnd = $addon->getConfig('scheduled_end', ''); + + if ('' === $scheduledStart && '' === $scheduledEnd) { + $this->setMessage('Keine geplante Wartung konfiguriert'); + return true; + } + + $now = time(); + $start = $scheduledStart ? strtotime($scheduledStart) : null; + $end = $scheduledEnd ? strtotime($scheduledEnd) : null; + $blockFrontend = (bool) $addon->getConfig('block_frontend', false); + + // Check if we should activate maintenance + if ($start && $end && $now >= $start && $now < $end && !$blockFrontend) { + $addon->setConfig('block_frontend', true); + $this->setMessage('Wartungsmodus aktiviert (bis: ' . $scheduledEnd . ')'); + return true; + } + + // Check if we should deactivate maintenance + if ($end && $now >= $end && $blockFrontend) { + $addon->setConfig('block_frontend', false); + $addon->setConfig('scheduled_start', ''); + $addon->setConfig('scheduled_end', ''); + $this->setMessage('Wartungsmodus deaktiviert (geplante Wartung beendet)'); + return true; + } + + // No change needed + if ($blockFrontend) { + $this->setMessage('Wartungsmodus läuft (bis: ' . $scheduledEnd . ')'); + } else { + $this->setMessage('Wartungsmodus inaktiv (geplanter Start: ' . $scheduledStart . ')'); + } + + return true; + } + + public function getTypeName() + { + return rex_i18n::msg('maintenance_cronjob_scheduled_name'); + } + + public function getParamFields() + { + return []; + } +} diff --git a/package.yml b/package.yml index 74b7746..5ccf1ba 100644 --- a/package.yml +++ b/package.yml @@ -1,5 +1,5 @@ package: maintenance -version: '3.4.0' +version: '4.0.0-beta1' author: Friends Of REDAXO supportpage: https://github.com/FriendsOfREDAXO/maintenance title: translate:maintenance_title @@ -15,10 +15,27 @@ page: title: translate:maintenance_frontend_title icon: rex-icon fa-user perm: maintenance[frontend] + subpages: + index: + title: translate:maintenance_frontend_general_title + icon: rex-icon fa-sliders + perm: maintenance[frontend] + advanced: + title: translate:maintenance_settings_title + icon: rex-icon fa-cog + perm: admin[] + scheduled: + title: translate:maintenance_planning_title + icon: rex-icon fa-calendar-alt + perm: admin[] backend: title: translate:maintenance_backend_title icon: rex-icon fa-user-gear perm: admin[] + domains: + title: translate:maintenance_domains_title + icon: rex-icon fa-sitemap + perm: maintenance[frontend] preview: title: translate:maintenance_preview_title icon: rex-icon fa-eye @@ -42,23 +59,31 @@ permissions: maintenance[frontend]: translate:maintenance[frontend] # Recht für Frontend-Einstellungen console_commands: - maintenance:mode: rex_maintenance_mode_command + maintenance:mode: rex_maintenance_mode_command default_config: http_response_code: 503 # 503, 403 allowed_ips: '' - authentification_mode: 'URL' # `URL`, `password` + allowed_yrewrite_domains: '' + authentication_mode: 'URL' # `URL`, `password` block_frontend: 0 block_frontend_rex_user: 0 block_backend: 0 redirect_frontend_to_url: '' # `https://example.com/maintenance.html` redirect_backend_to_url: '' # `https://example.com/maintenance.html` - secret: '' - editor: 'class="form-control redactor-editor--default"' + maintenance_secret: '' + editor: 'class="form-control cke5-editor" data-profile="default"' maintenance_frontend_headline: 'Maintenance / Wartung' + maintenance_text_en: 'This website is temporarily unavailable.' + maintenance_text_de: 'Diese Website ist vorübergehend nicht erreichbar.' maintenance_backend_headline: 'Maintenance / Wartung' maintenance_frontend_update_interval: 60 maintenance_backend_update_interval: 60 + domain_status: [] + all_domains_locked: false + silent_mode: false # If true, only send HTTP status without content + scheduled_start: '' # Automatic activation datetime (Y-m-d H:i:s) + scheduled_end: '' # Automatic deactivation datetime (Y-m-d H:i:s) installer_ignore: - .git diff --git a/pages/domains.php b/pages/domains.php new file mode 100644 index 0000000..a69d7b2 --- /dev/null +++ b/pages/domains.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$addon = rex_addon::get('maintenance'); + +// Load page-specific assets +rex_view::addJsFile($addon->getAssetsUrl('js/domains.js')); + +// Wenn YRewrite nicht installiert oder verfügbar ist, Hinweis anzeigen +if (!rex_addon::exists('yrewrite') || !rex_addon::get('yrewrite')->isAvailable()) { + echo rex_view::info($addon->i18n('maintenance_yrewrite_not_available')); + return; +} + +// Domains aus YRewrite holen +$domains = rex_yrewrite::getDomains(); + +// Aktuelle Einstellungen laden +$domainStatus = (array) $addon->getConfig('domain_status', []); +$allDomainsLocked = (bool) $addon->getConfig('all_domains_locked', false); + +// Formular abgesendet? +if (rex_post('save', 'bool')) { + // Globale Option für alle Domains speichern + $allDomainsLocked = rex_post('all_domains_locked', 'bool', false); + $addon->setConfig('all_domains_locked', $allDomainsLocked); + + // Nur wenn nicht alle Domains gesperrt sind, individuelle Einstellungen speichern + if (!$allDomainsLocked) { + // Einstellungen speichern + $domainStatus = []; + foreach ($domains as $domain) { + $name = $domain->getName(); + if ('default' !== $name) { + $domainStatus[$name] = rex_post('domain_' . md5($name), 'bool', false); + } + } + + // Konfiguration speichern + $addon->setConfig('domain_status', $domainStatus); + } + + // Erfolgsmeldung + echo rex_view::success($addon->i18n('maintenance_settings_saved')); +} + +// Tabelle mit Domains erstellen +$content = '
'; + +// Option für alle Domains +$content .= '
'; +$content .= ''; +$content .= '
'; +$content .= ''; +$content .= '
'; +$content .= '
'; + +// Individuelle Domain-Einstellungen +$content .= '
'; +$content .= ''; +$content .= ''; +$content .= ''; +$content .= ''; +$content .= ''; +$content .= ''; + +foreach ($domains as $domain) { + $name = $domain->getName(); + if ('default' !== $name) { + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + } +} + +$content .= '
' . $addon->i18n('maintenance_domain') . '' . $addon->i18n('maintenance_maintenance_active') . '
' . htmlspecialchars($name) . '
' . htmlspecialchars($domain->getUrl()) . '
'; + $content .= '
'; + $content .= ''; + $content .= '
'; + $content .= '
'; +$content .= '
'; + +$content .= '
'; +$content .= ''; +$content .= '
'; +$content .= '
'; + +// Hinweis zur Domain-Konfiguration +$notice = '
'; +$notice .= '

' . $addon->i18n('maintenance_domains_notice') . '

'; +$notice .= '
'; + +// Fragment erstellen und ausgeben +$fragment = new rex_fragment(); +$fragment->setVar('class', 'edit', false); +$fragment->setVar('title', $addon->i18n('maintenance_domains_management'), false); +$fragment->setVar('body', $notice . $content, false); +?> + +
+
+ parse('core/page/section.php') ?> +
+
+ +
+
diff --git a/pages/frontend.advanced.php b/pages/frontend.advanced.php new file mode 100644 index 0000000..129fe5a --- /dev/null +++ b/pages/frontend.advanced.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$addon = rex_addon::get('maintenance'); + +// Load page-specific assets +rex_view::addJsFile($addon->getAssetsUrl('js/frontend-advanced.js')); +rex_view::addCssFile($addon->getAssetsUrl('css/ip-addresses.css')); + +// Nur für Admins zugänglich +if (!rex::getUser()->isAdmin()) { + echo rex_view::error($addon->i18n('maintenance_admin_only')); + return; +} + +$form = rex_config_form::factory($addon->getName()); + +// Editor-Einstellungen +$form->addFieldset($addon->i18n('maintenance_editor_settings_title')); + +// Editor festlegen für Benachrichtigungstext +$field = $form->addTextField('editor'); +$field->setLabel($addon->i18n('maintenance_editor_label')); +$field->setNotice($addon->i18n('maintenance_editor_notice')); + +// Zugriffskontrolle +$form->addFieldset($addon->i18n('maintenance_access_control_title')); + +// Liste der erlaubten IP-Adressen +$field = $form->addTextField('allowed_ips'); +$field->setLabel($addon->i18n('maintenance_allowed_ips_label')); +$field->setAttribute('class', 'form-control'); +$field->setAttribute('id', 'maintenance-allowed-ips'); + +// Aktuelle IP-Adresse anzeigen +$clientIp = rex_server('REMOTE_ADDR', 'string', ''); +$serverIp = rex_server('SERVER_ADDR', 'string', ''); + +// IP-Adressen als formatierte Liste mit Buttons +$notice = '
'; +$notice .= '
' . $addon->i18n('maintenance_your_ip') . ': ' . $clientIp . ''; +$notice .= '
'; +if ($serverIp && $serverIp !== $clientIp) { + $notice .= '
' . $addon->i18n('maintenance_server_ip') . ': ' . $serverIp . ''; + $notice .= '
'; +} +$notice .= '
'; +$notice .= '
' . $addon->i18n('maintenance_allowed_ips_notice') . '
'; +$field->setNotice($notice); + +// HTTP-Einstellungen +$form->addFieldset($addon->i18n('maintenance_http_settings_title')); + +// Silent Mode (nur HTTP-Status, kein Content) +$field = $form->addCheckboxField('silent_mode'); +$field->setLabel($addon->i18n('maintenance_silent_mode_label')); +$field->addOption($addon->i18n('maintenance_silent_mode_enable'), 1); +$field->setNotice($addon->i18n('maintenance_silent_mode_notice')); + +// Antwortcode +$field = $form->addSelectField('http_response_code'); +$field->setLabel($addon->i18n('maintenance_http_response_code_label')); +$select = $field->getSelect(); +$select->addOption($addon->i18n('maintenance_http_response_code_503'), '503'); +$select->addOption($addon->i18n('maintenance_http_response_code_403'), '403'); + +$fragment = new rex_fragment(); +$fragment->setVar('class', 'edit'); +$fragment->setVar('title', $addon->i18n('maintenance_settings_title')); +$fragment->setVar('body', $form->get(), false); +?> + +
+
+ parse('core/page/section.php') ?> +
+
+ +
+
diff --git a/pages/frontend.index.php b/pages/frontend.index.php new file mode 100644 index 0000000..8a3a4d7 --- /dev/null +++ b/pages/frontend.index.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$addon = rex_addon::get('maintenance'); + +$form = rex_config_form::factory($addon->getName()); + +$form->addFieldset($addon->i18n('maintenance_general_title')); + +// Aktivierung/Deaktivierung des Wartungsmodus im Frontend +$field = $form->addSelectField('block_frontend'); +$field->setLabel($addon->i18n('maintenance_block_frontend_label')); +$select = $field->getSelect(); +$select->addOption($addon->i18n('maintenance_block_frontend_false'), 0); +$select->addOption($addon->i18n('maintenance_block_frontend_true'), 1); + +// Überschrift für den Wartungsmodus +$field = $form->addTextField('maintenance_frontend_headline'); +$field->setLabel($addon->i18n('maintenance_frontend_headline_label')); +$field->setNotice($addon->i18n('maintenance_frontend_headline_notice')); + +// Wartungstext Englisch +$field = $form->addTextField('maintenance_text_en'); +$field->setLabel($addon->i18n('maintenance_text_en_label')); +$field->setNotice($addon->i18n('maintenance_text_en_notice')); + +// Wartungstext Deutsch +$field = $form->addTextField('maintenance_text_de'); +$field->setLabel($addon->i18n('maintenance_text_de_label')); +$field->setNotice($addon->i18n('maintenance_text_de_notice')); + +// Automatische Aktualisierung der Seite +$field = $form->addInputField('number', 'maintenance_frontend_update_interval'); +$field->setLabel($addon->i18n('maintenance_update_interval_field_label')); +$field->setNotice($addon->i18n('maintenance_update_interval_field_notice')); +$field->setAttribute('class', 'form-control'); + +// Passwort zum Umgehen des Wartungsmodus +$field = $form->addTextField('maintenance_secret'); +$field->setLabel($addon->i18n('maintenance_secret_label')); +$field->setNotice($addon->i18n('maintenance_secret_notice', bin2hex(random_bytes(16)))); +$field->setAttribute('type', 'password'); + +// Umgehung der Wartung durch GET-Parameter (URL) oder Passwort +$field = $form->addSelectField('authentication_mode'); +$field->setLabel($addon->i18n('maintenance_authentication_mode_label')); +$select = $field->getSelect(); +$select->addOption($addon->i18n('maintenance_authentication_mode_url'), 'URL'); +$select->addOption($addon->i18n('maintenance_authentication_mode_password'), 'password'); + +// Blockiere auch für angemeldete REDAXO-Benutzer das Frontend +$field = $form->addSelectField('block_frontend_rex_user'); +$field->setLabel($addon->i18n('maintenance_block_frontend_rex_user_label')); +$field->setNotice($addon->i18n('maintenance_block_frontend_rex_user_notice')); +$select = $field->getSelect(); +$select->addOption($addon->i18n('maintenance_block_frontend_rex_user_false'), 0); +$select->addOption($addon->i18n('maintenance_block_frontend_rex_user_rex_user'), 1); + +// Ziel der Umleitung +$field = $form->addTextField('redirect_frontend_to_url'); +$field->setLabel($addon->i18n('maintenance_redirect_frontend_to_url_label')); +$field->setNotice($addon->i18n('maintenance_redirect_frontend_to_url_notice')); +$field->setAttribute('type', 'url'); + +$fragment = new rex_fragment(); +$fragment->setVar('class', 'edit', false); +$fragment->setVar('title', $addon->i18n('maintenance_settings_frontend_title'), false); +$fragment->setVar('body', $form->get(), false); +?> + +
+
+ parse('core/page/section.php') ?> +
+
+ +
+
diff --git a/pages/frontend.php b/pages/frontend.php deleted file mode 100644 index 3ab941e..0000000 --- a/pages/frontend.php +++ /dev/null @@ -1,197 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -$addon = rex_addon::get('maintenance'); -$isAdmin = rex::getUser()->isAdmin(); - -$form = rex_config_form::factory($addon->getName()); - -$form->addFieldset($addon->i18n('maintenance_general_title')); - -// Überschrift für den Wartungsmodus -$field = $form->addTextField('maintenance_frontend_headline'); -$field->setLabel($addon->i18n('maintenance_frontend_headline_label')); -$field->setNotice($addon->i18n('maintenance_frontend_headline_notice')); - -// Automatische Aktualisierung der Seite -$field = $form->addInputField('number', 'maintenance_frontend_update_interval'); -$field->setLabel($addon->i18n('maintenance_update_interval_field_label')); -$field->setNotice($addon->i18n('maintenance_update_interval_field_notice')); -$field->setAttribute('class', 'form-control'); - -// Aktivierung/Deaktivierung des Wartungsmodus im Frontend - für alle Benutzer verfügbar -$field = $form->addSelectField('block_frontend'); -$field->setLabel($addon->i18n('maintenance_block_frontend_label')); -$select = $field->getSelect(); -$select->addOption($addon->i18n('maintenance_block_frontend_false'), 0); -$select->addOption($addon->i18n('maintenance_block_frontend_true'), 1); - -// Passwort zum Umgehen des Wartungsmodus - für alle Benutzer verfügbar -$field = $form->addTextField('maintenance_secret'); -$field->setLabel($addon->i18n('maintenance_secret_label')); -$field->setNotice($addon->i18n('maintenance_secret_notice', bin2hex(random_bytes(16)))); -$field->setAttribute('type', 'password'); - -if ($isAdmin) { - // Erlaubte IP-Adressen - nur für Admins - $form->addFieldset($addon->i18n('maintenance_allowed_access_title')); - - $field = $form->addTextField('allowed_ips'); - $field->setLabel($addon->i18n('maintenance_allowed_ips_label')); - $field->setNotice($addon->i18n('maintenance_allowed_ips_notice', rex_server('REMOTE_ADDR', 'string', ''), rex_server('SERVER_ADDR', 'string', ''))); - $field->setAttribute('class', 'form-control'); - $field->setAttribute('data-maintenance', 'tokenfield'); - $field->setAttribute('data-beautify', 'false'); -} - -// Wenn YRewrite installiert, dann erlaubte YRewrite-Domains auswählen - für alle Benutzer -if (rex_addon::get('yrewrite')->isAvailable() && count(rex_yrewrite::getDomains()) > 1) { - if (!$isAdmin) { - $form->addFieldset($addon->i18n('maintenance_allowed_access_title')); - } - - $field = $form->addSelectField('allowed_yrewrite_domains'); - $field->setAttribute('multiple', 'multiple'); - $field->setAttribute('class', 'form-control selectpicker'); - $field->setAttribute('data-live-search', 'true'); - $field->setLabel($addon->i18n('maintenance_allowed_yrewrite_domains_label')); - $field->setNotice($addon->i18n('maintenance_allowed_yrewrite_domains_notice')); - $select = $field->getSelect(); - foreach (rex_yrewrite::getDomains() as $key => $domain) { - $select->addOption($key, $key); - } -} - -// Ab hier nur für Admins sichtbare Optionen -if ($isAdmin) { - // Erlaubte Domains - $field = $form->addTextField('allowed_domains'); - $field->setLabel($addon->i18n('maintenance_allowed_domains_label')); - $field->setNotice($addon->i18n('maintenance_allowed_domains_notice')); - $field->setAttribute('class', 'form-control'); - $field->setAttribute('data-maintenance', 'tokenfield'); - $field->setAttribute('data-beautify', 'false'); - - // Umgehung der Wartung durch GET-Parameter (URL) oder Passwort - $field = $form->addSelectField('authentification_mode'); - $field->setLabel($addon->i18n('maintenance_authentification_mode_label')); - $select = $field->getSelect(); - $select->addOption($addon->i18n('maintenance_authentification_mode_url'), 'URL'); - $select->addOption($addon->i18n('maintenance_authentification_mode_password'), 'password'); - - // Blockere auch für angemeldete REDAXO-Benutzer das Frontend - $field = $form->addSelectField('block_frontend_rex_user'); - $field->setLabel($addon->i18n('maintenance_block_frontend_rex_user_label')); - $select = $field->getSelect(); - $select->addOption($addon->i18n('maintenance_block_frontend_rex_user_false'), 0); - $select->addOption($addon->i18n('maintenance_block_frontend_rex_user_rex_user'), 1); - - // Ziel der Umleitung - $field = $form->addTextField('redirect_frontend_to_url'); - $field->setLabel($addon->i18n('maintenance_redirect_frontend_to_url_label')); - $field->setNotice($addon->i18n('maintenance_redirect_frontend_to_url_notice')); - $field->setAttribute('type', 'url'); - - // Antwortcode - $field = $form->addSelectField('http_response_code'); - $field->setLabel($addon->i18n('maintenance_http_response_code_label')); - $select = $field->getSelect(); - $select->addOption($addon->i18n('maintenance_http_response_code_503'), '503'); - $select->addOption($addon->i18n('maintenance_http_response_code_403'), '403'); - - // Wartungsfenster-Ankündigung - $form->addFieldset($addon->i18n('maintenance_announcement_title')); - - // Benachrichtigungstext - $field = $form->addTextAreaField('announcement'); - $field->setLabel($addon->i18n('maintenance_announcement_label')); - $field->setNotice($addon->i18n('maintenance_announcement_notice')); - if ('' !== (string) rex_config::get('maintenance', 'editor')) { - $field->setAttribute('class', '###maintenance-settings-editor###'); - } - - // Editor festlegen für Benachrichtigungstext - $field = $form->addTextField('editor'); - $field->setLabel($addon->i18n('maintenance_editor_label')); - $field->setNotice($addon->i18n('maintenance_editor_notice')); - - // Start- und Endzeitpunkt der Wartungsankündigung - $field = $form->addTextField('announcement_start_date'); - $field->setLabel($addon->i18n('maintenance_announcement_start_date_label')); - $field->setNotice(rex_i18n::rawMsg('maintenance_announcement_start_date_notice', date('Y-m-d H:i:s'))); - $field->setAttribute('type', 'datetime-local'); - - $field = $form->addTextField('announcement_end_date'); - $field->setLabel($addon->i18n('maintenance_announcement_end_date_label')); - $field->setNotice(rex_i18n::rawMsg('maintenance_announcement_end_date_notice', date('Y-m-d H:i:s'))); - $field->setAttribute('type', 'datetime-local'); -} - -$fragment = new rex_fragment(); -$fragment->setVar('class', 'edit'); -$fragment->setVar('title', $addon->i18n('maintenance_settings_frontend_title')); -$fragment->setVar('body', $form->get(), false); -?> - -
-
- parse('core/page/section.php') ?> -
- -
- ' . rex_i18n::msg('maintenance_preview') . ''; - - $fragment = new rex_fragment(); - $fragment->setVar('class', 'info', false); - $fragment->setVar('title', rex_i18n::msg('maintenance_preview_title'), false); - $fragment->setVar('body', $preview, false); - echo $fragment->parse('core/page/section.php'); - - /* Kopieren der URL für den Wartungsmodus */ - $copy = '
    '; - $url = '' . rex::getServer() . '?maintenance_secret=' . rex_config::get('maintenance', 'maintenance_secret'); - $copy .= '
  • '; - $copy .= ' - - - -
  • '; - - // Ebenfalls für alle YRewrite-Domains ausgeben - if (rex_addon::get('yrewrite')->isAvailable() && count(rex_yrewrite::getDomains()) > 1) { - foreach (rex_yrewrite::getDomains() as $key => $domain) { - if ('default' == $key) { - continue; - } - $url = $domain->getUrl() . '?maintenance_secret=' . rex_config::get('maintenance', 'maintenance_secret'); - $copy .= '
  • '; - $copy .= ' - - - -
  • '; - } - } - - $copy .= '
'; - - $fragment = new rex_fragment(); - $fragment->setVar('class', 'info', false); - $fragment->setVar('title', rex_i18n::msg('maintenance_copy_url_title'), false); - $fragment->setVar('body', $copy, false); - echo $fragment->parse('core/page/section.php'); - ?> -
- -
diff --git a/pages/frontend.scheduled.php b/pages/frontend.scheduled.php new file mode 100644 index 0000000..a78dca4 --- /dev/null +++ b/pages/frontend.scheduled.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$addon = rex_addon::get('maintenance'); + +// Nur für Admins zugänglich +if (!rex::getUser()->isAdmin()) { + echo rex_view::error($addon->i18n('maintenance_admin_only')); + return; +} + +$form = rex_config_form::factory($addon->getName()); + +// ===== ZEITGESTEUERTE WARTUNG ===== +$form->addFieldset($addon->i18n('maintenance_scheduled_title')); + +// Info-Text für geplante Wartung mit Cronjob-Hinweis +$cronjobInstalled = false; +if (rex_addon::get('cronjob')->isAvailable()) { + $sql = rex_sql::factory(); + $sql->setQuery('SELECT id FROM ' . rex::getTable('cronjob') . ' WHERE type = :type LIMIT 1', ['type' => 'rex_cronjob_scheduled_maintenance']); + $cronjobInstalled = $sql->getRows() > 0; +} + +$infoHtml = '
'; +$infoHtml .= '

' . $addon->i18n('maintenance_scheduled_info') . '

'; +if (!$cronjobInstalled) { + $infoHtml .= '

' . $addon->i18n('maintenance_scheduled_cronjob_missing') . '
'; + $infoHtml .= ''; + $infoHtml .= ' ' . $addon->i18n('maintenance_scheduled_cronjob_create') . '

'; +} else { + $infoHtml .= '

' . $addon->i18n('maintenance_scheduled_cronjob_active') . '

'; +} +$infoHtml .= '
'; +$field = $form->addRawField($infoHtml); + +// Geplanter Start +$field = $form->addTextField('scheduled_start'); +$field->setLabel($addon->i18n('maintenance_scheduled_start_label')); +$field->setNotice($addon->i18n('maintenance_scheduled_start_notice') . '
' . $addon->i18n('maintenance_scheduled_example') . ''); +$field->setAttribute('placeholder', '2025-12-31 02:00:00'); + +// Geplantes Ende +$field = $form->addTextField('scheduled_end'); +$field->setLabel($addon->i18n('maintenance_scheduled_end_label')); +$field->setNotice($addon->i18n('maintenance_scheduled_end_notice') . '
' . $addon->i18n('maintenance_scheduled_example') . ''); +$field->setAttribute('placeholder', '2025-12-31 06:00:00'); + +// Aktuellen Status anzeigen +$scheduledStart = (string) $addon->getConfig('scheduled_start', ''); +$scheduledEnd = (string) $addon->getConfig('scheduled_end', ''); +if ('' !== $scheduledStart || '' !== $scheduledEnd) { + $statusHtml = '
'; + $statusHtml .= '' . $addon->i18n('maintenance_scheduled_active') . '
'; + if ('' !== $scheduledStart) { + $statusHtml .= $addon->i18n('maintenance_scheduled_starts_at') . ': ' . rex_escape($scheduledStart) . '
'; + } + if ('' !== $scheduledEnd) { + $statusHtml .= $addon->i18n('maintenance_scheduled_ends_at') . ': ' . rex_escape($scheduledEnd) . ''; + } + $statusHtml .= '
'; + $field = $form->addRawField($statusHtml); +} + +// ===== WARTUNGSANKÜNDIGUNG ===== +$form->addFieldset($addon->i18n('maintenance_announcement_title')); + +// Benachrichtigungstext +$field = $form->addTextAreaField('announcement'); +$field->setLabel($addon->i18n('maintenance_announcement_label')); +$field->setNotice($addon->i18n('maintenance_announcement_notice')); +if ('' !== (string) rex_config::get('maintenance', 'editor')) { + $field->setAttribute('class', '###maintenance-settings-editor###'); +} + +// Start- und Endzeitpunkt der Wartungsankündigung +$field = $form->addTextField('announcement_start_date'); +$field->setLabel($addon->i18n('maintenance_announcement_start_date_label')); +$field->setNotice(rex_i18n::rawMsg('maintenance_announcement_start_date_notice', date('Y-m-d H:i:s'))); +$field->setAttribute('type', 'datetime-local'); + +$field = $form->addTextField('announcement_end_date'); +$field->setLabel($addon->i18n('maintenance_announcement_end_date_label')); +$field->setNotice(rex_i18n::rawMsg('maintenance_announcement_end_date_notice', date('Y-m-d H:i:s'))); +$field->setAttribute('type', 'datetime-local'); + +$fragment = new rex_fragment(); +$fragment->setVar('class', 'edit', false); +$fragment->setVar('title', $addon->i18n('maintenance_planning_page_title'), false); +$fragment->setVar('body', $form->get(), false); +?> + +
+
+ parse('core/page/section.php') ?> +
+
+ '; + + $info = '
'; + $info .= '

' . $addon->i18n('maintenance_announcement_info_title') . '

'; + $info .= '

' . $addon->i18n('maintenance_announcement_info_text') . '

'; + $info .= '

' . $addon->i18n('maintenance_announcement_usage') . ':

'; + $info .= '
'; + $info .= '
' . htmlspecialchars($codeExample) . '
'; + $info .= ''; + $info .= '
'; + $info .= '
'; + + echo $info; + + // Sidebar + $sidebar = new rex_fragment(); + $sidebar->setVar('title', $addon->i18n('maintenance_quick_links')); + + $content = ''; + + $sidebar->setVar('body', $content, false); + echo $sidebar->parse('core/page/section.php'); + ?> +
+
+ diff --git a/pages/frontend.sidebar.php b/pages/frontend.sidebar.php new file mode 100644 index 0000000..a2a5b12 --- /dev/null +++ b/pages/frontend.sidebar.php @@ -0,0 +1,109 @@ +'; +$preview .= ' ' . rex_i18n::msg('maintenance_preview') . ''; + +$fragment = new rex_fragment(); +$fragment->setVar('class', 'info', false); +$fragment->setVar('title', rex_i18n::msg('maintenance_preview_title'), false); +$fragment->setVar('body', $preview, false); +$sidebarContent .= $fragment->parse('core/page/section.php'); + +/* Kopieren der URL für den Wartungsmodus */ +$copy = '
    '; + +// Prüfen, ob YRewrite verfügbar ist +$yrewriteAvailable = rex_addon::exists('yrewrite') && rex_addon::get('yrewrite')->isAvailable(); +$allDomainsLocked = rex_config::get('maintenance', 'all_domains_locked', false); +$domainStatus = rex_config::get('maintenance', 'domain_status', []); + +// Standard-Domain immer anzeigen, wenn Frontend-Wartung aktiv ist oder alle Domains gesperrt sind +if (rex_config::get('maintenance', 'block_frontend') || $allDomainsLocked) { + $url = '' . rex::getServer() . '?maintenance_secret=' . rex_config::get('maintenance', 'maintenance_secret'); + $copy .= '
  • '; + $copy .= ''; + $copy .= ' + + + +
  • '; +} + +// YRewrite-Domains nur anzeigen, wenn sie gesperrt sind +if ($yrewriteAvailable && count(rex_yrewrite::getDomains()) > 1) { + foreach (rex_yrewrite::getDomains() as $key => $domain) { + if ('default' == $key) { + continue; + } + + // Domain nur anzeigen, wenn: + // - Alle Domains gesperrt sind ODER + // - Diese spezifische Domain gesperrt ist + $isDomainLocked = $allDomainsLocked || (isset($domainStatus[$key]) && 1 == $domainStatus[$key]); + + if ($isDomainLocked) { + $url = $domain->getUrl() . '?maintenance_secret=' . rex_config::get('maintenance', 'maintenance_secret'); + $copy .= '
  • '; + $copy .= ''; + $copy .= ' + + + +
  • '; + } + } +} + +$copy .= '
'; + +$fragment = new rex_fragment(); +$fragment->setVar('class', 'info', false); +$fragment->setVar('title', rex_i18n::msg('maintenance_copy_url_title'), false); +$fragment->setVar('body', $copy, false); +$sidebarContent .= $fragment->parse('core/page/section.php'); + +/* Quick Links */ +$quickLinks = '
'; + +// Link zur Hauptseite (Allgemeine Einstellungen) +if ('maintenance/frontend/index' !== $currentPage && 'maintenance/frontend' !== $currentPage) { + $quickLinks .= ''; + $quickLinks .= ' ' . $addon->i18n('maintenance_frontend_general_title') . ''; +} + +if (rex::getUser()->isAdmin()) { + if ('maintenance/frontend/advanced' !== $currentPage) { + $quickLinks .= ''; + $quickLinks .= ' ' . $addon->i18n('maintenance_settings_title') . ''; + } + + if ('maintenance/frontend/announcement' !== $currentPage) { + $quickLinks .= ''; + $quickLinks .= ' ' . $addon->i18n('maintenance_announcement_settings') . ''; + } +} + +if (rex_addon::exists('yrewrite') && rex_addon::get('yrewrite')->isAvailable() && 'maintenance/domains' !== $currentPage) { + $quickLinks .= ''; + $quickLinks .= ' ' . $addon->i18n('maintenance_domain_settings') . ''; +} + +$quickLinks .= '
'; + +$fragment = new rex_fragment(); +$fragment->setVar('class', 'info', false); +$fragment->setVar('title', $addon->i18n('maintenance_quick_links'), false); +$fragment->setVar('body', $quickLinks, false); +$sidebarContent .= $fragment->parse('core/page/section.php'); + +echo $sidebarContent; diff --git a/update.php b/update.php index 9ef44e8..b998a1b 100644 --- a/update.php +++ b/update.php @@ -33,11 +33,11 @@ } if ($addon->hasConfig('type')) { - $addon->setConfig('authentification_mode', 'Password' === $addon->getConfig('type') ? 'password' : 'URL'); + $addon->setConfig('authentication_mode', 'Password' === $addon->getConfig('type') ? 'password' : 'URL'); } if ($addon->hasConfig('secret')) { - $addon->setConfig('maintenance_secret', $addon->getConfig('maintenance_secret')); + $addon->setConfig('maintenance_secret', $addon->getConfig('secret')); } $addon->removeConfig('responsecode'); @@ -51,11 +51,22 @@ $addon->removeConfig('secret'); } +// Migration von 'authentification_mode' zu 'authentication_mode' (Rechtschreibkorrektur) +// Prüfe zuerst, ob die alte falsche Schreibweise existiert +if ($addon->hasConfig('authentification_mode')) { + // Wenn die neue korrekte Schreibweise noch nicht existiert, übernehme den Wert + if (!$addon->hasConfig('authentication_mode')) { + $addon->setConfig('authentication_mode', $addon->getConfig('authentification_mode')); + } + // Entferne in jedem Fall die alte falsche Schreibweise + $addon->removeConfig('authentification_mode'); +} + // Leerer String ('') und 'URL' werden beide als gültige URL-Authentifizierung betrachtet -$authentification_mode = $addon->getConfig('authentification_mode', ''); -if (!in_array($authentification_mode, ['URL', 'password'], true)) { +$authentication_mode = $addon->getConfig('authentication_mode', ''); +if (!in_array($authentication_mode, ['URL', 'password'], true)) { // Wenn kein gültiger Modus gesetzt ist, standardmäßig auf URL setzen - $addon->setConfig('authentification_mode', 'URL'); + $addon->setConfig('authentication_mode', 'URL'); } // Überprüfen, ob ein maintenance_secret existiert @@ -63,3 +74,31 @@ // Falls kein Secret vorhanden, ein neues generieren $addon->setConfig('maintenance_secret', bin2hex(random_bytes(16))); } + +// Migration der alten allowed_yrewrite_domains zu neuem domain_status System +if ($addon->hasConfig('allowed_yrewrite_domains') && !$addon->hasConfig('domain_status')) { + $oldAllowedDomains = (string) $addon->getConfig('allowed_yrewrite_domains', ''); + + if ('' !== $oldAllowedDomains && rex_addon::exists('yrewrite') && rex_addon::get('yrewrite')->isAvailable()) { + // Die alten allowed_yrewrite_domains waren eine Whitelist (erlaubte Domains) + // Im neuen System bedeutet: Domains die NICHT in der Whitelist sind, sollten gesperrt sein + $allowedDomainsArray = explode('|', $oldAllowedDomains); + $allowedDomainsArray = array_filter(array_map('trim', $allowedDomainsArray)); + + $domainStatus = []; + foreach (rex_yrewrite::getDomains() as $domain) { + $domainName = $domain->getName(); + if ('default' !== $domainName) { + // Domain ist gesperrt, wenn sie NICHT in der Whitelist war + $domainStatus[$domainName] = !in_array($domain->getHost(), $allowedDomainsArray, true); + } + } + + if (!empty($domainStatus)) { + $addon->setConfig('domain_status', $domainStatus); + } + } + + // Alte Konfiguration kann entfernt werden (bleibt aber zur Kompatibilität) + // $addon->removeConfig('allowed_yrewrite_domains'); +}