From 6ed3e02dcbe3aa0f200764967f6af4e469c58928 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 17:57:28 +0100 Subject: [PATCH 01/51] feat: Umfassende Verbesserungen des Maintenance AddOns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Emojis durch Font Awesome 6 Icons ersetzt - YRewrite Domain-Verwaltung hinzugefügt mit Migration - Settings in übersichtliche Unterseiten reorganisiert (Wartung, Einstellungen, Ankündigung, Domains) - Mehrsprachige Wartungstexte (Deutsch/Englisch) mit Sprachswitcher - Modernes upkeep-inspiriertes Design übernommen - Card-basiertes Layout mit Animationen - Dark Mode Support - Responsive Design - Dropdown-Sprachauswahl mit Globe-Icon - IP-Adressen per Klick zur Whitelist hinzufügen - Login-Formular nur bei gesetztem Passwort anzeigen - Intelligente Bypass-URL-Anzeige (nur für gesperrte Domains) - Sidebar-Komponente für alle Frontend-Seiten - Editor-Konfiguration in Einstellungen verschoben - Begriffe vereinheitlicht (Wartungsmodus -> Wartung) - Weiterleitungs-URL zu allgemeinen Einstellungen verschoben --- assets/css/maintenance-icons.css | 97 ++++++ boot.php | 13 +- fragments/maintenance/frontend.php | 463 ++++++++++++++++++++++++- fragments/maintenance/frontend_old.php | 266 ++++++++++++++ fragments/maintenance/login.php | 6 +- lang/de_de.lang | 102 ++++-- lib/Maintenance.php | 49 +++ package.yml | 24 +- pages/domains.php | 133 +++++++ pages/frontend.advanced.php | 110 ++++++ pages/frontend.announcement.php | 71 ++++ pages/frontend.index.php | 87 +++++ pages/frontend.php | 197 ----------- pages/frontend.sidebar.php | 109 ++++++ update.php | 28 ++ 15 files changed, 1517 insertions(+), 238 deletions(-) create mode 100644 assets/css/maintenance-icons.css create mode 100644 fragments/maintenance/frontend_old.php create mode 100644 pages/domains.php create mode 100644 pages/frontend.advanced.php create mode 100644 pages/frontend.announcement.php create mode 100644 pages/frontend.index.php delete mode 100644 pages/frontend.php create mode 100644 pages/frontend.sidebar.php 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/boot.php b/boot.php index 677bdeb..456ef35 100644 --- a/boot.php +++ b/boot.php @@ -18,7 +18,13 @@ rex_extension::register('PACKAGES_INCLUDED', static function () { $addon = rex_addon::get('maintenance'); - if (rex::isFrontend() && (bool) $addon->getConfig('block_frontend')) { + // Check domain-based maintenance or global frontend maintenance + $domainInMaintenance = false; + if (rex::isFrontend() && rex_addon::get('yrewrite')->isAvailable()) { + $domainInMaintenance = Maintenance::isDomainInMaintenance(); + } + + if (rex::isFrontend() && ((bool) $addon->getConfig('block_frontend') || $domainInMaintenance)) { Maintenance::checkFrontend(); } if (rex::isBackend() && (bool) $addon->getConfig('block_backend')) { @@ -32,8 +38,11 @@ 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/announcement' === 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..4e353b3 100644 --- a/fragments/maintenance/frontend.php +++ b/fragments/maintenance/frontend.php @@ -1,12 +1,18 @@ - + - + + <?php @@ -17,21 +23,458 @@ } ?> - Maintenance - +
-
-

-

This website is temporarily unavailable.

-

Diese Website ist vorübergehend nicht erreichbar.

+ +
+
+ +
+ + +
+
+
+ + + + + + + + +

+ + +
+
+ + + +
+ +
+ + subfragment('maintenance/announcement.php'); echo $this->subfragment('maintenance/login.php'); echo $this->subfragment('maintenance/reload.php'); ?>
+ + + + diff --git a/fragments/maintenance/frontend_old.php b/fragments/maintenance/frontend_old.php new file mode 100644 index 0000000..a8fb450 --- /dev/null +++ b/fragments/maintenance/frontend_old.php @@ -0,0 +1,266 @@ + + + + + + + + + <?php + if (rex_addon::get('yrewrite')->isAvailable() && null !== rex_yrewrite::getCurrentDomain()?->getName()) { + echo rex_yrewrite::getCurrentDomain()->getName(); + } else { + echo rex::getServerName(); + } + ?> - Maintenance + + + + + +
+ +
+
+ +
+ + +
+
+
+ + +
+

+ + +

+ + +

+ +
+ 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..d7c47f9 100644 --- a/fragments/maintenance/login.php +++ b/fragments/maintenance/login.php @@ -1,5 +1,9 @@ +$authentification_mode = rex_config::get('maintenance', 'authentification_mode', ''); +$maintenance_secret = rex_config::get('maintenance', 'maintenance_secret', ''); + +// Show login form only if password mode is selected AND a password is actually set +if ('password' === $authentification_mode && '' !== $maintenance_secret) { ?> diff --git a/pages/frontend.index.php b/pages/frontend.index.php new file mode 100644 index 0000000..d19b2d6 --- /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('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'); + +// 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.sidebar.php b/pages/frontend.sidebar.php new file mode 100644 index 0000000..02bf6dc --- /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::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]) && $domainStatus[$key] == 1); + + 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) +$currentPage = rex_be_controller::getCurrentPage(); +if ($currentPage !== 'maintenance/frontend/index' && $currentPage !== 'maintenance/frontend') { + $quickLinks .= ''; + $quickLinks .= ' ' . $addon->i18n('maintenance_frontend_general_title') . ''; +} + +if (rex::getUser()->isAdmin()) { + if ($currentPage !== 'maintenance/frontend/advanced') { + $quickLinks .= ''; + $quickLinks .= ' ' . $addon->i18n('maintenance_settings_title') . ''; + } + + if ($currentPage !== 'maintenance/frontend/announcement') { + $quickLinks .= ''; + $quickLinks .= ' ' . $addon->i18n('maintenance_announcement_settings') . ''; + } +} + +if (rex_addon::get('yrewrite')->isAvailable() && $currentPage !== 'maintenance/domains') { + $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..2501aea 100644 --- a/update.php +++ b/update.php @@ -63,3 +63,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::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 ($domainName !== 'default') { + // 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'); +} From e7a56691a9963a465f650d6756fd9be45d2d4887 Mon Sep 17 00:00:00 2001 From: skerbis Date: Wed, 29 Oct 2025 16:58:27 +0000 Subject: [PATCH 02/51] Apply php-cs-fixer changes --- boot.php | 2 +- fragments/maintenance/frontend.php | 8 ++++---- fragments/maintenance/frontend_old.php | 6 +++--- lib/Maintenance.php | 7 ++++--- pages/domains.php | 12 ++++++------ pages/frontend.advanced.php | 2 +- pages/frontend.announcement.php | 4 ++-- pages/frontend.index.php | 2 +- pages/frontend.sidebar.php | 18 +++++++++--------- update.php | 10 +++++----- 10 files changed, 36 insertions(+), 35 deletions(-) diff --git a/boot.php b/boot.php index 456ef35..9aae59c 100644 --- a/boot.php +++ b/boot.php @@ -40,7 +40,7 @@ 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/announcement' === rex_be_controller::getCurrentPage()) { rex_extension::register('OUTPUT_FILTER', static function (rex_extension_point $ep) { diff --git a/fragments/maintenance/frontend.php b/fragments/maintenance/frontend.php index 4e353b3..d6209d3 100644 --- a/fragments/maintenance/frontend.php +++ b/fragments/maintenance/frontend.php @@ -382,7 +382,7 @@
- + @@ -396,13 +396,13 @@
- +
- + - + diff --git a/fragments/maintenance/frontend_old.php b/fragments/maintenance/frontend_old.php index a8fb450..b95037e 100644 --- a/fragments/maintenance/frontend_old.php +++ b/fragments/maintenance/frontend_old.php @@ -180,17 +180,17 @@ - +

- +

- +
getName(); $domainStatus = (array) self::getConfig('domain_status', []); - + // Check if this specific domain is in maintenance mode if (isset($domainStatus[$domainName]) && $domainStatus[$domainName]) { return true; @@ -245,7 +246,7 @@ public static function checkFrontend(): void // Check if the current domain is in maintenance mode (new domain-based logic) $domainInMaintenance = self::isDomainInMaintenance(); - + // If domain is NOT in maintenance, allow access if (!$domainInMaintenance) { return; @@ -351,7 +352,7 @@ public static function setIndicators(): void $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 . ''; diff --git a/pages/domains.php b/pages/domains.php index 4d37cf8..0483ba3 100644 --- a/pages/domains.php +++ b/pages/domains.php @@ -30,22 +30,22 @@ // 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 ($name !== 'default') { + 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')); } @@ -75,7 +75,7 @@ foreach ($domains as $domain) { $name = $domain->getName(); - if ($name !== 'default') { + if ('default' !== $name) { $content .= ''; $content .= '' . htmlspecialchars($name) . '
' . htmlspecialchars($domain->getUrl()) . ''; $content .= ''; @@ -116,7 +116,7 @@ parse('core/page/section.php') ?>
- +
diff --git a/pages/frontend.advanced.php b/pages/frontend.advanced.php index 7ef2b7c..9beadd7 100644 --- a/pages/frontend.advanced.php +++ b/pages/frontend.advanced.php @@ -76,7 +76,7 @@ parse('core/page/section.php') ?>
- +
diff --git a/pages/frontend.announcement.php b/pages/frontend.announcement.php index 1ca1f9c..c28ba64 100644 --- a/pages/frontend.announcement.php +++ b/pages/frontend.announcement.php @@ -61,9 +61,9 @@ $info .= '

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

'; $info .= '
<?php
use FriendsOfREDAXO\Maintenance\Maintenance;
Maintenance::showAnnouncement();
?>
'; $info .= ''; - + echo $info; - + /* Sidebar-Panel einbinden */ include __DIR__ . '/frontend.sidebar.php'; ?> diff --git a/pages/frontend.index.php b/pages/frontend.index.php index d19b2d6..4f4d199 100644 --- a/pages/frontend.index.php +++ b/pages/frontend.index.php @@ -82,6 +82,6 @@ parse('core/page/section.php') ?>
- +
diff --git a/pages/frontend.sidebar.php b/pages/frontend.sidebar.php index 02bf6dc..ee880f4 100644 --- a/pages/frontend.sidebar.php +++ b/pages/frontend.sidebar.php @@ -2,7 +2,7 @@ /** * Sidebar-Panel für Frontend-Seiten des Maintenance AddOns - * Zeigt Vorschau, Bypass-URLs und Quick-Links + * Zeigt Vorschau, Bypass-URLs und Quick-Links. */ $addon = rex_addon::get('maintenance'); @@ -44,12 +44,12 @@ if ('default' == $key) { continue; } - + // Domain nur anzeigen, wenn: // - Alle Domains gesperrt sind ODER // - Diese spezifische Domain gesperrt ist - $isDomainLocked = $allDomainsLocked || (isset($domainStatus[$key]) && $domainStatus[$key] == 1); - + $isDomainLocked = $allDomainsLocked || (isset($domainStatus[$key]) && 1 == $domainStatus[$key]); + if ($isDomainLocked) { $url = $domain->getUrl() . '?maintenance_secret=' . rex_config::get('maintenance', 'maintenance_secret'); $copy .= '
  • '; @@ -76,24 +76,24 @@ // Link zur Hauptseite (Allgemeine Einstellungen) $currentPage = rex_be_controller::getCurrentPage(); -if ($currentPage !== 'maintenance/frontend/index' && $currentPage !== 'maintenance/frontend') { +if ('maintenance/frontend/index' !== $currentPage && 'maintenance/frontend' !== $currentPage) { $quickLinks .= ''; $quickLinks .= ' ' . $addon->i18n('maintenance_frontend_general_title') . ''; } if (rex::getUser()->isAdmin()) { - if ($currentPage !== 'maintenance/frontend/advanced') { + if ('maintenance/frontend/advanced' !== $currentPage) { $quickLinks .= ''; $quickLinks .= ' ' . $addon->i18n('maintenance_settings_title') . ''; } - - if ($currentPage !== 'maintenance/frontend/announcement') { + + if ('maintenance/frontend/announcement' !== $currentPage) { $quickLinks .= ''; $quickLinks .= ' ' . $addon->i18n('maintenance_announcement_settings') . ''; } } -if (rex_addon::get('yrewrite')->isAvailable() && $currentPage !== 'maintenance/domains') { +if (rex_addon::get('yrewrite')->isAvailable() && 'maintenance/domains' !== $currentPage) { $quickLinks .= ''; $quickLinks .= ' ' . $addon->i18n('maintenance_domain_settings') . ''; } diff --git a/update.php b/update.php index 2501aea..c275d5c 100644 --- a/update.php +++ b/update.php @@ -67,27 +67,27 @@ // 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::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 ($domainName !== 'default') { + 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'); } From d58efb828d342623d95d3de0a8db19c51e0b1533 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 18:03:07 +0100 Subject: [PATCH 03/51] Update fragments/maintenance/frontend.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- fragments/maintenance/frontend.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fragments/maintenance/frontend.php b/fragments/maintenance/frontend.php index d6209d3..6cb35ea 100644 --- a/fragments/maintenance/frontend.php +++ b/fragments/maintenance/frontend.php @@ -393,7 +393,7 @@

    -
    +
    From 583f037033141467858cb96863f28200c6ef83e0 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 18:03:32 +0100 Subject: [PATCH 04/51] Update boot.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- boot.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boot.php b/boot.php index 9aae59c..e75e375 100644 --- a/boot.php +++ b/boot.php @@ -20,7 +20,7 @@ // Check domain-based maintenance or global frontend maintenance $domainInMaintenance = false; - if (rex::isFrontend() && rex_addon::get('yrewrite')->isAvailable()) { + if (rex::isFrontend() && rex_addon::exists('yrewrite') && rex_addon::get('yrewrite')->isAvailable()) { $domainInMaintenance = Maintenance::isDomainInMaintenance(); } From 1acf0595c0a917bf4ec64ec883581eb83ab02b29 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 18:03:59 +0100 Subject: [PATCH 05/51] Update fragments/maintenance/frontend_old.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- fragments/maintenance/frontend_old.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fragments/maintenance/frontend_old.php b/fragments/maintenance/frontend_old.php index b95037e..16dcad2 100644 --- a/fragments/maintenance/frontend_old.php +++ b/fragments/maintenance/frontend_old.php @@ -186,7 +186,7 @@

    -

    +

    From 2cc1e883fb603dc38e0db2ee03e9d1605c0c6b63 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 18:05:26 +0100 Subject: [PATCH 06/51] Fix: Add existence check for yrewrite addon to prevent exceptions - Check rex_addon::exists('yrewrite') before calling methods - Prevents exceptions when yrewrite addon is not installed - Affected files: frontend.php, frontend.sidebar.php (2x), Maintenance.php (2x) --- fragments/maintenance/frontend.php | 2 +- lib/Maintenance.php | 4 ++-- pages/frontend.sidebar.php | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/fragments/maintenance/frontend.php b/fragments/maintenance/frontend.php index 6cb35ea..a5bf56e 100644 --- a/fragments/maintenance/frontend.php +++ b/fragments/maintenance/frontend.php @@ -16,7 +16,7 @@ <?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(); diff --git a/lib/Maintenance.php b/lib/Maintenance.php index 2db9cdd..8198c08 100644 --- a/lib/Maintenance.php +++ b/lib/Maintenance.php @@ -135,7 +135,7 @@ public static function isDomainInMaintenance(): bool } // Check individual domain status - if (!rex_addon::get('yrewrite')->isAvailable()) { + if (!rex_addon::exists('yrewrite') || !rex_addon::get('yrewrite')->isAvailable()) { return false; } @@ -258,7 +258,7 @@ public static function checkFrontend(): void } // 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; } diff --git a/pages/frontend.sidebar.php b/pages/frontend.sidebar.php index ee880f4..e1633a5 100644 --- a/pages/frontend.sidebar.php +++ b/pages/frontend.sidebar.php @@ -22,7 +22,7 @@ $copy = '<ul class="list-group">'; // Prüfen, ob YRewrite verfügbar ist -$yrewriteAvailable = rex_addon::get('yrewrite')->isAvailable(); +$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', []); @@ -93,7 +93,7 @@ } } -if (rex_addon::get('yrewrite')->isAvailable() && 'maintenance/domains' !== $currentPage) { +if (rex_addon::exists('yrewrite') && rex_addon::get('yrewrite')->isAvailable() && $currentPage !== 'maintenance/domains') { $quickLinks .= '<a href="' . rex_url::backendPage('maintenance/domains') . '" class="btn btn-default">'; $quickLinks .= '<i class="rex-icon fa-sitemap"></i> ' . $addon->i18n('maintenance_domain_settings') . '</a>'; } From 0813f0ea21bac5d1b7636e1ca63abc1136093f00 Mon Sep 17 00:00:00 2001 From: skerbis <skerbis@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:07:37 +0000 Subject: [PATCH 07/51] Apply php-cs-fixer changes --- pages/frontend.sidebar.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/frontend.sidebar.php b/pages/frontend.sidebar.php index e1633a5..bc6fc1c 100644 --- a/pages/frontend.sidebar.php +++ b/pages/frontend.sidebar.php @@ -93,7 +93,7 @@ } } -if (rex_addon::exists('yrewrite') && rex_addon::get('yrewrite')->isAvailable() && $currentPage !== 'maintenance/domains') { +if (rex_addon::exists('yrewrite') && rex_addon::get('yrewrite')->isAvailable() && 'maintenance/domains' !== $currentPage) { $quickLinks .= '<a href="' . rex_url::backendPage('maintenance/domains') . '" class="btn btn-default">'; $quickLinks .= '<i class="rex-icon fa-sitemap"></i> ' . $addon->i18n('maintenance_domain_settings') . '</a>'; } From 18669f2655ac476d4f3ea594da0489acf59c1ea3 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis <thomas.skerbis@mac.com> Date: Wed, 29 Oct 2025 18:11:00 +0100 Subject: [PATCH 08/51] Fix review comments from PR #156 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix: Internationalize hardcoded German text 'hinzugefügt' Added maintenance_ip_added translation key - Fix: Correct spelling 'authentification' to 'authentication' Updated in package.yml, lang files, PHP code Added migration for existing installations - Already fixed: Ternary operator logic in frontend.php - Already fixed: YRewrite existence checks --- lang/de_de.lang | 12 +++++++----- lib/Maintenance.php | 6 +++--- package.yml | 2 +- pages/frontend.advanced.php | 2 +- pages/frontend.index.php | 8 ++++---- update.php | 16 +++++++++++----- 6 files changed, 27 insertions(+), 19 deletions(-) diff --git a/lang/de_de.lang b/lang/de_de.lang index b485b31..1d46707 100644 --- a/lang/de_de.lang +++ b/lang/de_de.lang @@ -58,12 +58,12 @@ maintenance_block_frontend_rex_user_false = Eingeloggte REDAXO-Benutzer haben we 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. <code>{0}</code>), das zum Aufrufen der Seite per Geheim-URL oder Passwort-Eingabe genutzt werden soll. @@ -79,6 +79,8 @@ 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_ip_added = hinzugefügt + maintenance_announcement_title = Ankündigung der Wartungsarbeiten maintenance_editor_label = Editor diff --git a/lib/Maintenance.php b/lib/Maintenance.php index 8198c08..62057fd 100644 --- a/lib/Maintenance.php +++ b/lib/Maintenance.php @@ -166,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); diff --git a/package.yml b/package.yml index d56414d..91301cd 100644 --- a/package.yml +++ b/package.yml @@ -65,7 +65,7 @@ default_config: http_response_code: 503 # 503, 403 allowed_ips: '' allowed_yrewrite_domains: '' - authentification_mode: 'URL' # `URL`, `password` + authentication_mode: 'URL' # `URL`, `password` block_frontend: 0 block_frontend_rex_user: 0 block_backend: 0 diff --git a/pages/frontend.advanced.php b/pages/frontend.advanced.php index 9beadd7..05a95e8 100644 --- a/pages/frontend.advanced.php +++ b/pages/frontend.advanced.php @@ -102,7 +102,7 @@ $tokenfield.tokenfield('setTokens', tokens); } - $(this).prop('disabled', true).addClass('btn-success').html('<i class="fa fa-check"></i> ' + ip + ' hinzugefügt'); + $(this).prop('disabled', true).addClass('btn-success').html('<i class="fa fa-check"></i> ' + ip + ' <?= $addon->i18n('maintenance_ip_added') ?>'); } } }); diff --git a/pages/frontend.index.php b/pages/frontend.index.php index 4f4d199..8a3a4d7 100644 --- a/pages/frontend.index.php +++ b/pages/frontend.index.php @@ -51,11 +51,11 @@ $field->setAttribute('type', 'password'); // Umgehung der Wartung durch GET-Parameter (URL) oder Passwort -$field = $form->addSelectField('authentification_mode'); -$field->setLabel($addon->i18n('maintenance_authentification_mode_label')); +$field = $form->addSelectField('authentication_mode'); +$field->setLabel($addon->i18n('maintenance_authentication_mode_label')); $select = $field->getSelect(); -$select->addOption($addon->i18n('maintenance_authentification_mode_url'), 'URL'); -$select->addOption($addon->i18n('maintenance_authentification_mode_password'), 'password'); +$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'); diff --git a/update.php b/update.php index c275d5c..0885052 100644 --- a/update.php +++ b/update.php @@ -33,7 +33,7 @@ } 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')) { @@ -52,10 +52,16 @@ } // 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'); +} + +// Migration von 'authentification_mode' zu 'authentication_mode' (Rechtschreibkorrektur) +if ($addon->hasConfig('authentification_mode') && !$addon->hasConfig('authentication_mode')) { + $addon->setConfig('authentication_mode', $addon->getConfig('authentification_mode')); + $addon->removeConfig('authentification_mode'); } // Überprüfen, ob ein maintenance_secret existiert @@ -68,7 +74,7 @@ if ($addon->hasConfig('allowed_yrewrite_domains') && !$addon->hasConfig('domain_status')) { $oldAllowedDomains = (string) $addon->getConfig('allowed_yrewrite_domains', ''); - if ('' !== $oldAllowedDomains && rex_addon::get('yrewrite')->isAvailable()) { + 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); From ef113821efa08424ec2dee730bc906641cb6275b Mon Sep 17 00:00:00 2001 From: Thomas Skerbis <thomas.skerbis@mac.com> Date: Wed, 29 Oct 2025 18:12:58 +0100 Subject: [PATCH 09/51] Fix: Complete authentication spelling correction - Fix login.php: Use authentication_mode instead of authentification_mode - Improve update.php migration: Always remove old config key - All files now consistently use 'authentication' (correct spelling) --- fragments/maintenance/login.php | 4 ++-- update.php | 17 +++++++++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/fragments/maintenance/login.php b/fragments/maintenance/login.php index d7c47f9..f68c8f9 100644 --- a/fragments/maintenance/login.php +++ b/fragments/maintenance/login.php @@ -1,9 +1,9 @@ <?php -$authentification_mode = rex_config::get('maintenance', 'authentification_mode', ''); +$authentication_mode = rex_config::get('maintenance', 'authentication_mode', ''); $maintenance_secret = rex_config::get('maintenance', 'maintenance_secret', ''); // Show login form only if password mode is selected AND a password is actually set -if ('password' === $authentification_mode && '' !== $maintenance_secret) { ?> +if ('password' === $authentication_mode && '' !== $maintenance_secret) { ?> <div class="maintenance-login"> <form action="<?= rex_url::base() ?>" method="post"> <label for="maintenance_secret">Access-Code</label> diff --git a/update.php b/update.php index 0885052..7bc8420 100644 --- a/update.php +++ b/update.php @@ -51,6 +51,17 @@ $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 $authentication_mode = $addon->getConfig('authentication_mode', ''); if (!in_array($authentication_mode, ['URL', 'password'], true)) { @@ -58,12 +69,6 @@ $addon->setConfig('authentication_mode', 'URL'); } -// Migration von 'authentification_mode' zu 'authentication_mode' (Rechtschreibkorrektur) -if ($addon->hasConfig('authentification_mode') && !$addon->hasConfig('authentication_mode')) { - $addon->setConfig('authentication_mode', $addon->getConfig('authentification_mode')); - $addon->removeConfig('authentification_mode'); -} - // Überprüfen, ob ein maintenance_secret existiert if (!$addon->hasConfig('maintenance_secret') || '' === $addon->getConfig('maintenance_secret')) { // Falls kein Secret vorhanden, ein neues generieren From 20d851c4ed9ae47526c3bfb73be2da24c225a989 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis <thomas.skerbis@mac.com> Date: Wed, 29 Oct 2025 18:17:10 +0100 Subject: [PATCH 10/51] Update README and extend console commands README changes: - Set Thomas Skerbis as lead developer - Remove sponsor references - Update project team structure Console command enhancements: - Add 'status' command to show current maintenance state - Add 'frontend on/off' for frontend maintenance - Add 'backend on/off' for backend maintenance - Add 'all on/off' to toggle all modes at once - Add 'domain <name> --lock/--unlock' for YRewrite domains - Maintain backward compatibility with legacy 'on/off' commands - Add comprehensive documentation for all commands --- README.md | 71 +++++++++++-- lib/command/maintenance_mode.php | 175 ++++++++++++++++++++++++++++--- package.yml | 2 +- 3 files changed, 221 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 24261af..b7e31c3 100644 --- a/README.md +++ b/README.md @@ -66,40 +66,93 @@ 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 ``` -Deaktivieren des Wartungsmodus: +### 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 +``` + +### 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 -* <http://www.redaxo.org> -* <https://github.com/FriendsOfREDAXO> +* [Thomas Skerbis](https://github.com/skerbis) ## Projekt-Team -* [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) * u.v.a diff --git a/lib/command/maintenance_mode.php b/lib/command/maintenance_mode.php index e7ba736..3686e0a 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 ? '<fg=red>LOCKED</>' : '<fg=green>Open</>'), + 'Backend: ' . ($blockBackend ? '<fg=red>LOCKED</>' : '<fg=green>Open</>'), + 'All Domains: ' . ($allDomainsLocked ? '<fg=red>LOCKED</>' : '<fg=green>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 = ($state === 'on') ? 1 : 0; + + if ($currentState === $newState) { + $io->info('Frontend 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_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 = ($state === 'on') ? 1 : 0; + + if ($currentState === $newState) { + $io->info('Backend maintenance is already ' . $state); + return Command::FAILURE; + } + + $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 ('off' === $state) { - $addon->setConfig('block_frontend', 0); - $io->success(rex_i18n::msg('maintenance_mode_deactivated')); - return Command::SUCCESS; + + if ($lock === $unlock) { + $io->error('Use either --lock or --unlock option'); + return Command::INVALID; } - $io->error(rex_i18n::msg('maintenance_mode_invalid')); - return Command::INVALID; + + $domains = rex_yrewrite::getDomains(); + if (!isset($domains[$domain])) { + $io->error('Domain "' . $domain . '" not found'); + return Command::FAILURE; + } + + $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; + } + + $newState = ($state === 'on') ? 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/package.yml b/package.yml index 91301cd..46e1df0 100644 --- a/package.yml +++ b/package.yml @@ -1,5 +1,5 @@ package: maintenance -version: '3.4.0' +version: '3.5.0' author: Friends Of REDAXO supportpage: https://github.com/FriendsOfREDAXO/maintenance title: translate:maintenance_title From 133c1ccdaa8ed7805dd0cd71107b15df36af9332 Mon Sep 17 00:00:00 2001 From: skerbis <skerbis@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:17:38 +0000 Subject: [PATCH 11/51] Apply php-cs-fixer changes --- lib/command/maintenance_mode.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/command/maintenance_mode.php b/lib/command/maintenance_mode.php index 3686e0a..d01e5ad 100644 --- a/lib/command/maintenance_mode.php +++ b/lib/command/maintenance_mode.php @@ -64,7 +64,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int case 'all': return $this->toggleAll($io, $addon, $state); - // Legacy support: "on" and "off" as direct arguments + // Legacy support: "on" and "off" as direct arguments case 'on': case 'off': return $this->toggleFrontend($io, $addon, $action); @@ -83,7 +83,7 @@ private function showStatus(OutputInterface $io, rex_addon $addon): int $domainStatus = $addon->getConfig('domain_status', []); $io->section('Current Status'); - + $io->text([ 'Frontend: ' . ($blockFrontend ? '<fg=red>LOCKED</>' : '<fg=green>Open</>'), 'Backend: ' . ($blockBackend ? '<fg=red>LOCKED</>' : '<fg=green>Open</>'), @@ -116,7 +116,7 @@ private function toggleFrontend(OutputInterface $io, rex_addon $addon, ?string $ } $currentState = $addon->getConfig('block_frontend', 0); - $newState = ($state === 'on') ? 1 : 0; + $newState = ('on' === $state) ? 1 : 0; if ($currentState === $newState) { $io->info('Frontend maintenance is already ' . $state); @@ -136,7 +136,7 @@ private function toggleBackend(OutputInterface $io, rex_addon $addon, ?string $s } $currentState = $addon->getConfig('block_backend', 0); - $newState = ($state === 'on') ? 1 : 0; + $newState = ('on' === $state) ? 1 : 0; if ($currentState === $newState) { $io->info('Backend maintenance is already ' . $state); @@ -186,7 +186,7 @@ private function toggleAll(OutputInterface $io, rex_addon $addon, ?string $state return Command::INVALID; } - $newState = ($state === 'on') ? 1 : 0; + $newState = ('on' === $state) ? 1 : 0; $addon->setConfig('block_frontend', $newState); $addon->setConfig('block_backend', $newState); From d0b4b07dcb79cf11c97eb61e055e4b721b67e504 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis <thomas.skerbis@mac.com> Date: Wed, 29 Oct 2025 18:20:19 +0100 Subject: [PATCH 12/51] Fix: Critical edge-case in multilanguage logic Problem: When only German text (maintenance_text_de) was filled and English text was empty, no text was displayed because the DE block didn't receive the 'active' class. Solution: Add fallback logic for DE block: - DE is active if multilanguage is disabled OR only DE text exists - EN is active if multilanguage is disabled OR only EN text exists This ensures at least one text block is always visible when text is provided. --- fragments/maintenance/frontend.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fragments/maintenance/frontend.php b/fragments/maintenance/frontend.php index a5bf56e..62c4cd8 100644 --- a/fragments/maintenance/frontend.php +++ b/fragments/maintenance/frontend.php @@ -399,7 +399,7 @@ <?php endif ?> <?php if ('' !== $maintenanceTextDe): ?> - <div class="maintenance-message maintenance-text" data-lang="de"> + <div class="maintenance-message maintenance-text <?= !$multilanguageEnabled || '' === $maintenanceTextEn ? 'active' : '' ?>" data-lang="de"> <?= nl2br(rex_escape($maintenanceTextDe)) ?> </div> <?php endif ?> From 0e2192e40f23279b58a9226bae0c27ce248a642b Mon Sep 17 00:00:00 2001 From: Thomas Skerbis <thomas.skerbis@mac.com> Date: Wed, 29 Oct 2025 18:21:44 +0100 Subject: [PATCH 13/51] Fix: Password input field not showing in password mode Removed overly restrictive condition that required both password mode AND a set secret. The login form should be displayed whenever password mode is selected, regardless of whether a secret is actually configured. This allows users to see the login form immediately after selecting password authentication mode, even before setting a password. --- fragments/maintenance/login.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fragments/maintenance/login.php b/fragments/maintenance/login.php index f68c8f9..aa122f7 100644 --- a/fragments/maintenance/login.php +++ b/fragments/maintenance/login.php @@ -2,8 +2,8 @@ $authentication_mode = rex_config::get('maintenance', 'authentication_mode', ''); $maintenance_secret = rex_config::get('maintenance', 'maintenance_secret', ''); -// Show login form only if password mode is selected AND a password is actually set -if ('password' === $authentication_mode && '' !== $maintenance_secret) { ?> +// Show login form only if password mode is selected +if ('password' === $authentication_mode) { ?> <div class="maintenance-login"> <form action="<?= rex_url::base() ?>" method="post"> <label for="maintenance_secret">Access-Code</label> From 2c330d65c53102114390515816647d6bb651f3fa Mon Sep 17 00:00:00 2001 From: Thomas Skerbis <thomas.skerbis@mac.com> Date: Wed, 29 Oct 2025 18:25:19 +0100 Subject: [PATCH 14/51] Fix: Improve login form rendering - Add id attribute to password input field for better accessibility - Add autocomplete='off' for better security - Improve code formatting and comments - Add debug comment placeholder for troubleshooting --- fragments/maintenance/login.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/fragments/maintenance/login.php b/fragments/maintenance/login.php index aa122f7..2ce3725 100644 --- a/fragments/maintenance/login.php +++ b/fragments/maintenance/login.php @@ -1,14 +1,17 @@ <?php $authentication_mode = rex_config::get('maintenance', 'authentication_mode', ''); -$maintenance_secret = rex_config::get('maintenance', 'maintenance_secret', ''); + +// Debug output - remove this later +// echo '<!-- DEBUG: authentication_mode = ' . htmlspecialchars($authentication_mode) . ' -->'; // Show login form only if password mode is selected +// (This fragment is only loaded when maintenance mode is active) if ('password' === $authentication_mode) { ?> <div class="maintenance-login"> <form action="<?= rex_url::base() ?>" method="post"> - <label for="maintenance_secret">Access-Code</label> - <input name="maintenance_secret" class="maintenance-pw-input" type="password" placeholder=""/> - <button type="submit" class="maintenance-pw-btn">Login</button> + <label for="maintenance_secret">Access-Code</label> + <input name="maintenance_secret" id="maintenance_secret" class="maintenance-pw-input" type="password" placeholder="" autocomplete="off"/> + <button type="submit" class="maintenance-pw-btn">Login</button> </form> </div> <?php } ?> From 9cbc56fe962e106d033ef1db6f74c967e00c5607 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis <thomas.skerbis@mac.com> Date: Wed, 29 Oct 2025 18:29:52 +0100 Subject: [PATCH 15/51] feat: Add Silent Mode for staging/development environments Implements feature request for permanent site blocking without maintenance messaging, ideal for staging environments in deployment processes. Features: - New 'silent_mode' config option - Sends only HTTP status code (503/403) without HTML content - Prevents information disclosure about CMS - Perfect for staging systems that should be permanently offline - Accessible only via backend login, IP whitelist, or secret URL Changes: - Added silent_mode to package.yml default_config - Added silent_mode checkbox in advanced settings - Implemented logic in Maintenance::checkFrontend() - Added German translations - Updated README with Silent Mode documentation Use case: Production preview systems that need permanent protection without revealing 'maintenance mode' or CMS information. --- README.md | 16 +++++++++++++--- lang/de_de.lang | 4 ++++ lib/Maintenance.php | 11 ++++++++++- package.yml | 1 + pages/frontend.advanced.php | 6 ++++++ 5 files changed, 34 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b7e31c3..a5855c4 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ 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 @@ -46,6 +47,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. @@ -141,9 +153,6 @@ php redaxo/bin/console maintenance:mode off * [Thomas Skerbis](https://github.com/skerbis) -## Projekt-Team - -* [Simon Krull](https://github.com/crydotsnake) ## Credits @@ -153,6 +162,7 @@ Danke an: * [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/lang/de_de.lang b/lang/de_de.lang index 1d46707..a0e3208 100644 --- a/lang/de_de.lang +++ b/lang/de_de.lang @@ -79,6 +79,10 @@ 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_announcement_title = Ankündigung der Wartungsarbeiten diff --git a/lib/Maintenance.php b/lib/Maintenance.php index 62057fd..8386ee7 100644 --- a/lib/Maintenance.php +++ b/lib/Maintenance.php @@ -302,13 +302,22 @@ public static function checkFrontend(): void // Block everything that has not been allowed so far as chosen in the settings $redirect_url = (string) self::getConfig('redirect_frontend_to_url', ''); $responsecode = (int) self::getConfig('http_response_code', 503); + $silentMode = (bool) self::getConfig('silent_mode', false); - $mpage = new rex_fragment(); + // Redirect if configured if ('' !== $redirect_url) { rex_response::setStatus(rex_response::HTTP_MOVED_TEMPORARILY); rex_response::sendRedirect($redirect_url); } + // Silent mode: Only send HTTP status, no content + if ($silentMode) { + header('HTTP/1.1 ' . $responsecode); + exit; + } + + // Regular mode: Show maintenance page + $mpage = new rex_fragment(); header('HTTP/1.1 ' . $responsecode); exit($mpage->parse('maintenance/frontend.php')); } diff --git a/package.yml b/package.yml index 46e1df0..30775fa 100644 --- a/package.yml +++ b/package.yml @@ -81,6 +81,7 @@ default_config: maintenance_backend_update_interval: 60 domain_status: [] all_domains_locked: false + silent_mode: false # If true, only send HTTP status without content installer_ignore: - .git diff --git a/pages/frontend.advanced.php b/pages/frontend.advanced.php index 05a95e8..3d40d7f 100644 --- a/pages/frontend.advanced.php +++ b/pages/frontend.advanced.php @@ -58,6 +58,12 @@ // 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')); From f1c3fdb730739a70cc02fa179b8b61f6303f606c Mon Sep 17 00:00:00 2001 From: Thomas Skerbis <thomas.skerbis@mac.com> Date: Wed, 29 Oct 2025 19:02:36 +0100 Subject: [PATCH 16/51] Update fragments/maintenance/frontend.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- fragments/maintenance/frontend.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fragments/maintenance/frontend.php b/fragments/maintenance/frontend.php index 62c4cd8..5b4539a 100644 --- a/fragments/maintenance/frontend.php +++ b/fragments/maintenance/frontend.php @@ -368,7 +368,7 @@ <path d="M2 12h20"></path> <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path> </svg> - <span id="language-text">Language</span> + <span id="language-text"><?= rex_i18n::msg('maintenance_language') ?></span> </button> <div class="language-menu" id="language-menu"> <button class="language-option active" data-lang="en"> From 1a6ea5cd4f674e4f048f579c06dae4e152faa24b Mon Sep 17 00:00:00 2001 From: Thomas Skerbis <thomas.skerbis@mac.com> Date: Wed, 29 Oct 2025 19:03:10 +0100 Subject: [PATCH 17/51] Update fragments/maintenance/frontend.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- fragments/maintenance/frontend.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fragments/maintenance/frontend.php b/fragments/maintenance/frontend.php index 5b4539a..816ef5e 100644 --- a/fragments/maintenance/frontend.php +++ b/fragments/maintenance/frontend.php @@ -393,7 +393,7 @@ <h1 class="maintenance-title"><?= rex_escape($maintenanceFrontendHeadline) ?></h1> <?php if ('' !== $maintenanceTextEn): ?> - <div class="maintenance-message maintenance-text <?= !$multilanguageEnabled || '' === $maintenanceTextDe ? 'active' : '' ?>" data-lang="en"> + <div class="maintenance-message maintenance-text <?= '' === $maintenanceTextDe || $multilanguageEnabled ? 'active' : '' ?>" data-lang="en"> <?= nl2br(rex_escape($maintenanceTextEn)) ?> </div> <?php endif ?> From 95dd080f26828ff18720fd890d845483f02690db Mon Sep 17 00:00:00 2001 From: Thomas Skerbis <thomas.skerbis@mac.com> Date: Wed, 29 Oct 2025 19:14:03 +0100 Subject: [PATCH 18/51] fix: Korrigiere Mehrsprachigkeits-Logik in frontend_old.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Behebt Ternary-Operator Fehler: EN-Text war immer als 'active' markiert - DE-Text ist jetzt standardmäßig 'active', EN-Text nur wenn kein DE-Text vorhanden --- fragments/maintenance/frontend_old.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fragments/maintenance/frontend_old.php b/fragments/maintenance/frontend_old.php index 16dcad2..3b50961 100644 --- a/fragments/maintenance/frontend_old.php +++ b/fragments/maintenance/frontend_old.php @@ -186,10 +186,10 @@ <p class="maintenance-error-title"><?= $maintenanceFrontendHeadline ?></p> <?php if ('' !== $maintenanceTextEn): ?> - <p class="maintenance-error-message maintenance-text active" data-lang="en"><?= nl2br(rex_escape($maintenanceTextEn)) ?></p> + <p class="maintenance-error-message maintenance-text <?= '' === $maintenanceTextDe ? 'active' : '' ?>" data-lang="en"><?= nl2br(rex_escape($maintenanceTextEn)) ?></p> <?php endif ?> <?php if ('' !== $maintenanceTextDe): ?> - <p class="maintenance-error-message maintenance-text" lang="de" data-lang="de"><?= nl2br(rex_escape($maintenanceTextDe)) ?></p> + <p class="maintenance-error-message maintenance-text active" lang="de" data-lang="de"><?= nl2br(rex_escape($maintenanceTextDe)) ?></p> <?php endif ?> </div> <?php From 47383e8299e10ca178092174bd2b93a2b7315b29 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis <thomas.skerbis@mac.com> Date: Wed, 29 Oct 2025 19:16:34 +0100 Subject: [PATCH 19/51] fix: Korrigiere Mehrsprachigkeits-Logik auch in frontend.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DE-Text ist jetzt standardmäßig aktiv - EN-Text nur aktiv wenn kein DE-Text vorhanden - Konsistente Logik zwischen frontend.php und frontend_old.php --- fragments/maintenance/frontend.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fragments/maintenance/frontend.php b/fragments/maintenance/frontend.php index 816ef5e..8b14a9d 100644 --- a/fragments/maintenance/frontend.php +++ b/fragments/maintenance/frontend.php @@ -393,13 +393,13 @@ <h1 class="maintenance-title"><?= rex_escape($maintenanceFrontendHeadline) ?></h1> <?php if ('' !== $maintenanceTextEn): ?> - <div class="maintenance-message maintenance-text <?= '' === $maintenanceTextDe || $multilanguageEnabled ? 'active' : '' ?>" data-lang="en"> + <div class="maintenance-message maintenance-text <?= '' === $maintenanceTextDe ? 'active' : '' ?>" data-lang="en"> <?= nl2br(rex_escape($maintenanceTextEn)) ?> </div> <?php endif ?> <?php if ('' !== $maintenanceTextDe): ?> - <div class="maintenance-message maintenance-text <?= !$multilanguageEnabled || '' === $maintenanceTextEn ? 'active' : '' ?>" data-lang="de"> + <div class="maintenance-message maintenance-text active" data-lang="de"> <?= nl2br(rex_escape($maintenanceTextDe)) ?> </div> <?php endif ?> From d1ebd746c57a5af73c1d816aa31f951640b9a86e Mon Sep 17 00:00:00 2001 From: Thomas Skerbis <thomas.skerbis@mac.com> Date: Wed, 29 Oct 2025 19:18:02 +0100 Subject: [PATCH 20/51] =?UTF-8?q?fix:=20Frontend-Sperre=20wird=20wieder=20?= =?UTF-8?q?ausgel=C3=B6st?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Behebt kritischen Bug: Early-Return umging block_frontend-Schalter - checkFrontend() prüft jetzt sowohl block_frontend als auch domainbasierte Wartung - Frontend wird nur dann erlaubt, wenn BEIDE Flags inaktiv sind Fixes: https://github.com/FriendsOfREDAXO/maintenance/pull/156#discussion_r2474367562 --- lib/Maintenance.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/Maintenance.php b/lib/Maintenance.php index 8386ee7..c567669 100644 --- a/lib/Maintenance.php +++ b/lib/Maintenance.php @@ -246,9 +246,10 @@ public static function checkFrontend(): void // Check if the current domain is in maintenance mode (new domain-based logic) $domainInMaintenance = self::isDomainInMaintenance(); + $blockFrontend = self::getBoolConfig('block_frontend', false); - // If domain is NOT in maintenance, allow access - if (!$domainInMaintenance) { + // If neither frontend blocking nor domain-based maintenance is active, allow access + if (!$blockFrontend && !$domainInMaintenance) { return; } From 906354d5fdb03da7148970b96dcab0a4bbc7f913 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis <thomas.skerbis@mac.com> Date: Wed, 29 Oct 2025 19:21:20 +0100 Subject: [PATCH 21/51] chore: Entferne frontend_old.php Backup-Datei --- fragments/maintenance/frontend_old.php | 266 ------------------------- 1 file changed, 266 deletions(-) delete mode 100644 fragments/maintenance/frontend_old.php diff --git a/fragments/maintenance/frontend_old.php b/fragments/maintenance/frontend_old.php deleted file mode 100644 index 3b50961..0000000 --- a/fragments/maintenance/frontend_old.php +++ /dev/null @@ -1,266 +0,0 @@ -<?php - $maintenanceFrontendHeadline = rex_config::get('maintenance', 'maintenance_frontend_headline', 'Maintenance / Wartung'); - $maintenanceTextEn = rex_config::get('maintenance', 'maintenance_text_en', 'This website is temporarily unavailable.'); - $maintenanceTextDe = rex_config::get('maintenance', 'maintenance_text_de', 'Diese Website ist vorübergehend nicht erreichbar.'); - $maintenanceFrontendUpdateIntervalNumber = rex_config::get('maintenance', 'maintenance_frontend_update_interval', 60); -?> -<!doctype html> -<html lang="en"> -<head> - <meta charset="utf-8"> - <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> - <meta http-equiv="refresh" content="<?= $maintenanceFrontendUpdateIntervalNumber > 0 ? $maintenanceFrontendUpdateIntervalNumber : '' ?>"> - <title> - <?php - if (rex_addon::get('yrewrite')->isAvailable() && null !== rex_yrewrite::getCurrentDomain()?->getName()) { - echo rex_yrewrite::getCurrentDomain()->getName(); - } else { - echo rex::getServerName(); - } - ?> - Maintenance - - - - - -
    - -
    -
    - -
    - - -
    -
    -
    - - -
    -

    - - -

    - - -

    - -
    - subfragment('maintenance/announcement.php'); - echo $this->subfragment('maintenance/login.php'); - echo $this->subfragment('maintenance/reload.php'); - ?> -
    - - - - From b46cbb85569db1a5804bc2477cf128b52229bd9b Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 19:24:39 +0100 Subject: [PATCH 22/51] =?UTF-8?q?perf:=20Code-Optimierungen=20f=C3=BCr=20b?= =?UTF-8?q?essere=20Performance=20und=20Sicherheit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - YRewrite Existenz-Check in domains.php hinzugefügt (verhindert Exception) - Redundanten Domain-Check in boot.php entfernt (isDomainInMaintenance in checkFrontend) - Silent Mode Check früher durchgeführt (direkt nach Maintenance-Check) * Spart unnötige IP/Host/Secret-Checks bei Silent Mode * Verbessert Performance für Staging-Umgebungen --- boot.php | 8 +------- lib/Maintenance.php | 17 +++++++++-------- pages/domains.php | 4 ++-- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/boot.php b/boot.php index e75e375..68159bb 100644 --- a/boot.php +++ b/boot.php @@ -18,13 +18,7 @@ rex_extension::register('PACKAGES_INCLUDED', static function () { $addon = rex_addon::get('maintenance'); - // Check domain-based maintenance or global frontend maintenance - $domainInMaintenance = false; - if (rex::isFrontend() && rex_addon::exists('yrewrite') && rex_addon::get('yrewrite')->isAvailable()) { - $domainInMaintenance = Maintenance::isDomainInMaintenance(); - } - - if (rex::isFrontend() && ((bool) $addon->getConfig('block_frontend') || $domainInMaintenance)) { + if (rex::isFrontend() && ((bool) $addon->getConfig('block_frontend') || Maintenance::isDomainInMaintenance())) { Maintenance::checkFrontend(); } if (rex::isBackend() && (bool) $addon->getConfig('block_backend')) { diff --git a/lib/Maintenance.php b/lib/Maintenance.php index c567669..3184566 100644 --- a/lib/Maintenance.php +++ b/lib/Maintenance.php @@ -253,6 +253,14 @@ public static function checkFrontend(): void 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; @@ -303,7 +311,6 @@ public static function checkFrontend(): void // Block everything that has not been allowed so far as chosen in the settings $redirect_url = (string) self::getConfig('redirect_frontend_to_url', ''); $responsecode = (int) self::getConfig('http_response_code', 503); - $silentMode = (bool) self::getConfig('silent_mode', false); // Redirect if configured if ('' !== $redirect_url) { @@ -311,13 +318,7 @@ public static function checkFrontend(): void rex_response::sendRedirect($redirect_url); } - // Silent mode: Only send HTTP status, no content - if ($silentMode) { - header('HTTP/1.1 ' . $responsecode); - exit; - } - - // Regular mode: Show maintenance page + // Show maintenance page $mpage = new rex_fragment(); header('HTTP/1.1 ' . $responsecode); exit($mpage->parse('maintenance/frontend.php')); diff --git a/pages/domains.php b/pages/domains.php index 0483ba3..e333d79 100644 --- a/pages/domains.php +++ b/pages/domains.php @@ -12,8 +12,8 @@ $addon = rex_addon::get('maintenance'); -// Wenn YRewrite nicht verfügbar ist, Hinweis anzeigen -if (!rex_addon::get('yrewrite')->isAvailable()) { +// 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; } From e8537d2a6d919d9e0dc53eb0a41c0bc7c403cdd4 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 19:34:45 +0100 Subject: [PATCH 23/51] feat: Zeitgesteuerte Wartung (Scheduled Maintenance) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implementiert automatische Aktivierung/Deaktivierung des Wartungsmodus: Features: - Request-basierte Prüfung: Automatische Aktivierung bei jedem Request - Optionaler Cronjob für präzisere Steuerung (jede Minute) - Konfiguration über Start- und Endzeitpunkt (YYYY-MM-DD HH:MM:SS) - Automatische Bereinigung nach Deaktivierung - Status-Anzeige im Backend Neue Config-Felder: - scheduled_start: Automatischer Aktivierungszeitpunkt - scheduled_end: Automatischer Deaktivierungszeitpunkt Neue Klassen: - Maintenance::checkScheduledMaintenance(): Request-basierte Prüfung - rex_cronjob_scheduled_maintenance: Optionaler Cronjob UI-Erweiterungen: - Zeitgesteuerte Wartung Sektion in Frontend-Einstellungen - Echtzeit-Status der geplanten Wartung - Mehrsprachige Labels (DE) Dokumentation: - README erweitert mit Verwendungsbeispielen - Inline-Kommentare für alle neuen Funktionen --- README.md | 18 +++++++++ lang/de_de.lang | 21 +++++++++- lib/Maintenance.php | 42 ++++++++++++++++++++ lib/cronjob/scheduled_maintenance.php | 55 +++++++++++++++++++++++++++ package.yml | 5 +++ pages/frontend.index.php | 34 +++++++++++++++++ 6 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 lib/cronjob/scheduled_maintenance.php diff --git a/README.md b/README.md index a5855c4..651b29f 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,24 @@ Das AddOn ermöglicht es Administratoren, das Frontend und/oder des Backend von * 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: + +* **Request-basiert**: Wird bei jedem Seitenaufruf geprüft (keine Cronjob-Konfiguration nötig) +* **Optional per Cronjob**: Für präzisere Steuerung kann der Cronjob "Geplante Wartung prüfen" eingerichtet werden +* **Automatische Bereinigung**: Nach erfolgreicher Deaktivierung werden die geplanten Zeiten automatisch gelöscht + +**Verwendung:** + +1. 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`) +2. 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`) ### Sperren des REDAXO-Backends diff --git a/lang/de_de.lang b/lang/de_de.lang index a0e3208..52f5f94 100644 --- a/lang/de_de.lang +++ b/lang/de_de.lang @@ -18,7 +18,13 @@ 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“ +maintenance_mode_invalid = Ungültiges Argument! Benutze „on" oder „off" + +// Cronjob + +maintenance_cronjob_scheduled_name = Geplante Wartung prüfen + +// Allgemein // Einstellungsseite Backend @@ -149,6 +155,19 @@ 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 = Automatische Aktivierung und Deaktivierung des Wartungsmodus zu festgelegten Zeiten +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 + // Quick Links maintenance_quick_links = Schnellzugriff diff --git a/lib/Maintenance.php b/lib/Maintenance.php index 3184566..c864fd5 100644 --- a/lib/Maintenance.php +++ b/lib/Maintenance.php @@ -237,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. */ @@ -244,6 +283,9 @@ public static function checkFrontend(): void { rex_login::startSession(); + // Check scheduled maintenance (auto-enable/disable based on time) + self::checkScheduledMaintenance(); + // Check if the current domain is in maintenance mode (new domain-based logic) $domainInMaintenance = self::isDomainInMaintenance(); $blockFrontend = self::getBoolConfig('block_frontend', false); diff --git a/lib/cronjob/scheduled_maintenance.php b/lib/cronjob/scheduled_maintenance.php new file mode 100644 index 0000000..4bccca9 --- /dev/null +++ b/lib/cronjob/scheduled_maintenance.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use FriendsOfREDAXO\Maintenance\Maintenance; + +/** + * Cronjob to check and apply scheduled maintenance mode. + * This is optional - the scheduled maintenance also works via request-based checking. + * Use this cronjob for more precise timing (e.g., run every minute). + */ +class rex_cronjob_scheduled_maintenance extends rex_cronjob +{ + public function execute(): bool + { + // Call the scheduled maintenance checker + Maintenance::checkScheduledMaintenance(); + + $addon = rex_addon::get('maintenance'); + $scheduledStart = (string) $addon->getConfig('scheduled_start', ''); + $scheduledEnd = (string) $addon->getConfig('scheduled_end', ''); + $blockFrontend = (bool) $addon->getConfig('block_frontend', false); + + // Log what happened + if ('' !== $scheduledStart || '' !== $scheduledEnd) { + if ($blockFrontend) { + $this->setMessage('Wartungsmodus ist aktiv (geplant bis: ' . $scheduledEnd . ')'); + } else { + $this->setMessage('Wartungsmodus ist inaktiv (geplanter Start: ' . $scheduledStart . ')'); + } + } else { + $this->setMessage('Keine geplante Wartung konfiguriert'); + } + + return true; + } + + public function getTypeName(): string + { + return rex_i18n::msg('maintenance_cronjob_scheduled_name'); + } + + public function getParamFields(): array + { + return []; + } +} diff --git a/package.yml b/package.yml index 30775fa..00c4aed 100644 --- a/package.yml +++ b/package.yml @@ -61,6 +61,9 @@ permissions: console_commands: maintenance:mode: rex_maintenance_mode_command +cronjobs: + - { class: rex_cronjob_scheduled_maintenance, interval: '{i}', environment: 'frontend|backend' } + default_config: http_response_code: 503 # 503, 403 allowed_ips: '' @@ -82,6 +85,8 @@ default_config: 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/frontend.index.php b/pages/frontend.index.php index 8a3a4d7..2de88e0 100644 --- a/pages/frontend.index.php +++ b/pages/frontend.index.php @@ -71,6 +71,40 @@ $field->setNotice($addon->i18n('maintenance_redirect_frontend_to_url_notice')); $field->setAttribute('type', 'url'); +// Scheduled Maintenance +$form->addFieldset($addon->i18n('maintenance_scheduled_title')); + +// Info-Text für geplante Wartung +$field = $form->addRawField('

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

    '); + +// 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); +} + $fragment = new rex_fragment(); $fragment->setVar('class', 'edit', false); $fragment->setVar('title', $addon->i18n('maintenance_settings_frontend_title'), false); From 5c4c049c5ba2a5be7f69e2ae929263d4229816ea Mon Sep 17 00:00:00 2001 From: skerbis Date: Wed, 29 Oct 2025 18:35:26 +0000 Subject: [PATCH 24/51] Apply php-cs-fixer changes --- lib/Maintenance.php | 10 +++++----- lib/cronjob/scheduled_maintenance.php | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/Maintenance.php b/lib/Maintenance.php index c864fd5..b6fc7c1 100644 --- a/lib/Maintenance.php +++ b/lib/Maintenance.php @@ -245,16 +245,16 @@ 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) { @@ -264,7 +264,7 @@ public static function checkScheduledMaintenance(): void } } } - + // Auto-deactivate: If we're past end time, disable maintenance if (false !== $endTime && $now >= $endTime) { if ($blockFrontend) { diff --git a/lib/cronjob/scheduled_maintenance.php b/lib/cronjob/scheduled_maintenance.php index 4bccca9..ebe5bb9 100644 --- a/lib/cronjob/scheduled_maintenance.php +++ b/lib/cronjob/scheduled_maintenance.php @@ -23,12 +23,12 @@ public function execute(): bool { // Call the scheduled maintenance checker Maintenance::checkScheduledMaintenance(); - + $addon = rex_addon::get('maintenance'); $scheduledStart = (string) $addon->getConfig('scheduled_start', ''); $scheduledEnd = (string) $addon->getConfig('scheduled_end', ''); $blockFrontend = (bool) $addon->getConfig('block_frontend', false); - + // Log what happened if ('' !== $scheduledStart || '' !== $scheduledEnd) { if ($blockFrontend) { @@ -39,7 +39,7 @@ public function execute(): bool } else { $this->setMessage('Keine geplante Wartung konfiguriert'); } - + return true; } From adf1f621b4143419b42d376aadacc3b32b708e07 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 19:38:50 +0100 Subject: [PATCH 25/51] =?UTF-8?q?fix:=20IP-Adressen=20per=20Klick=20hinzuf?= =?UTF-8?q?=C3=BCgen=20repariert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Probleme behoben: - Button zum Hinzufügen der eigenen IP fehlte in Sidebar - JavaScript nutzt jetzt Event-Delegation (document.on) - Timeout für Tokenfield-Initialisierung hinzugefügt - Feedback für bereits vorhandene IPs Verbesserungen: - Zeigt eigene IP-Adresse in Sidebar (nur auf Advanced-Seite) - Button wechselt zu Erfolg (grün) oder Warnung (gelb) - CSP-Nonce für inline JavaScript - Neue Sprachschlüssel: maintenance_my_ip_title, maintenance_add_my_ip --- lang/de_de.lang | 4 ++++ pages/frontend.advanced.php | 23 +++++++++++++++++++---- pages/frontend.announcement.php | 10 +++++++++- pages/frontend.sidebar.php | 16 ++++++++++++++++ 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/lang/de_de.lang b/lang/de_de.lang index 52f5f94..5dcddb3 100644 --- a/lang/de_de.lang +++ b/lang/de_de.lang @@ -90,6 +90,8 @@ 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 @@ -120,6 +122,8 @@ 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 diff --git a/pages/frontend.advanced.php b/pages/frontend.advanced.php index 3d40d7f..c71ce5a 100644 --- a/pages/frontend.advanced.php +++ b/pages/frontend.advanced.php @@ -86,13 +86,14 @@
    - diff --git a/pages/frontend.sidebar.php b/pages/frontend.sidebar.php index a0ae969..97e804d 100644 --- a/pages/frontend.sidebar.php +++ b/pages/frontend.sidebar.php @@ -6,6 +6,7 @@ */ $addon = rex_addon::get('maintenance'); +$currentPage = rex_be_controller::getCurrentPage(); $sidebarContent = ''; /* Vorschau des Wartungsmodus */ @@ -91,7 +92,6 @@ $quickLinks = '
    '; // Link zur Hauptseite (Allgemeine Einstellungen) -$currentPage = rex_be_controller::getCurrentPage(); if ('maintenance/frontend/index' !== $currentPage && 'maintenance/frontend' !== $currentPage) { $quickLinks .= ''; $quickLinks .= ' ' . $addon->i18n('maintenance_frontend_general_title') . ''; From 1a598c70a56234b669a52e536207cef8d2c0b3dc Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 19:47:02 +0100 Subject: [PATCH 29/51] fix: IP click-to-add mit exakter Upkeep-Implementierung MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Tokenfield-Abhängigkeit entfernt, einfaches Textfeld verwendet - IP-Sektion aus Sidebar entfernt - Vereinfachte JavaScript-Logik nach Upkeep-Vorbild - Verbesserte UI mit formatierten IP-Zeilen - Sprachschlüssel ergänzt (maintenance_your_ip, maintenance_server_ip, etc.) --- lang/de_de.lang | 6 +- lang/en_gb.lang | 6 +- pages/frontend.advanced.php | 131 ++++++++++++++++-------------------- pages/frontend.sidebar.php | 16 ----- 4 files changed, 69 insertions(+), 90 deletions(-) diff --git a/lang/de_de.lang b/lang/de_de.lang index 5dcddb3..c98ec81 100644 --- a/lang/de_de.lang +++ b/lang/de_de.lang @@ -112,8 +112,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) eingeben, die weiterhin Zugriff auf das Frontend haben soll(en). +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. 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/pages/frontend.advanced.php b/pages/frontend.advanced.php index 8cc822d..aa8ef15 100644 --- a/pages/frontend.advanced.php +++ b/pages/frontend.advanced.php @@ -31,29 +31,33 @@ // Zugriffskontrolle $form->addFieldset($addon->i18n('maintenance_access_control_title')); -// Erlaubte IP-Adressen +// Liste der erlaubten IP-Adressen $field = $form->addTextField('allowed_ips'); $field->setLabel($addon->i18n('maintenance_allowed_ips_label')); - -$remoteAddr = rex_server('REMOTE_ADDR', 'string', ''); -$serverAddr = rex_server('SERVER_ADDR', 'string', ''); - -$ipButtons = ''; -if ($remoteAddr) { - $ipButtons .= ' '; -} -if ($serverAddr && $serverAddr !== $remoteAddr) { - $ipButtons .= ''; -} - -$field->setNotice($ipButtons . '
    ' . $addon->i18n('maintenance_allowed_ips_notice')); $field->setAttribute('class', 'form-control'); -$field->setAttribute('data-maintenance', 'tokenfield'); -$field->setAttribute('data-beautify', 'false'); +$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') . '
    '; +$notice .= ''; +$field->setNotice($notice); // HTTP-Einstellungen $form->addFieldset($addon->i18n('maintenance_http_settings_title')); @@ -87,61 +91,44 @@
    diff --git a/pages/frontend.sidebar.php b/pages/frontend.sidebar.php index 97e804d..a2a5b12 100644 --- a/pages/frontend.sidebar.php +++ b/pages/frontend.sidebar.php @@ -72,22 +72,6 @@ $fragment->setVar('body', $copy, false); $sidebarContent .= $fragment->parse('core/page/section.php'); -/* Eigene IP hinzufügen */ -if ('maintenance/frontend/advanced' === $currentPage) { - $currentIp = rex_server('REMOTE_ADDR', 'string', ''); - if ('' !== $currentIp) { - $addIp = ''; - - $fragment = new rex_fragment(); - $fragment->setVar('class', 'info', false); - $fragment->setVar('title', $addon->i18n('maintenance_my_ip_title'), false); - $fragment->setVar('body', $addIp, false); - $sidebarContent .= $fragment->parse('core/page/section.php'); - } -} - /* Quick Links */ $quickLinks = '
    '; From 41193391bed493bf9893117392996950e7c0292d Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 19:48:21 +0100 Subject: [PATCH 30/51] fix: Cronjob-Registrierung korrigiert & Tokenfield-Assets entfernt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - package.yml: Cronjob-Definition vereinfacht (war falsch formatiert) - boot.php: Tokenfield JavaScript/CSS entfernt (nicht mehr benötigt) - Cronjob sollte nun im Cronjob-Addon sichtbar sein --- boot.php | 3 --- package.yml | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/boot.php b/boot.php index 68159bb..9e7c20b 100644 --- a/boot.php +++ b/boot.php @@ -27,9 +27,6 @@ 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')); diff --git a/package.yml b/package.yml index 00c4aed..0202fea 100644 --- a/package.yml +++ b/package.yml @@ -59,10 +59,10 @@ 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 cronjobs: - - { class: rex_cronjob_scheduled_maintenance, interval: '{i}', environment: 'frontend|backend' } + - rex_cronjob_scheduled_maintenance default_config: http_response_code: 503 # 503, 403 From 1a240f817de0f1063b5f459341f820bc8165602b Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 19:51:44 +0100 Subject: [PATCH 31/51] refactor: Cronjob-Datei verschoben & Inline JS/CSS ausgelagert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - lib/cronjob/scheduled_maintenance.php → lib/cronjob.php (REDAXO-Standard) - package.yml: Cronjob-Deklaration entfernt (automatische Erkennung) - Inline JavaScript nach assets/js/frontend-advanced.js ausgelagert - Inline JavaScript nach assets/js/domains.js ausgelagert - Inline CSS nach assets/css/ip-addresses.css ausgelagert - IP-Adressen als data-Attribute statt PHP-Interpolation - Fragments behalten inline CSS/JS (Sperrseiten) --- assets/css/ip-addresses.css | 22 +++++++ assets/js/domains.js | 12 ++++ assets/js/frontend-advanced.js | 43 ++++++++++++++ .../scheduled_maintenance.php => cronjob.php} | 0 package.yml | 3 - pages/domains.php | 15 +---- pages/frontend.advanced.php | 57 ++----------------- 7 files changed, 86 insertions(+), 66 deletions(-) create mode 100644 assets/css/ip-addresses.css create mode 100644 assets/js/domains.js create mode 100644 assets/js/frontend-advanced.js rename lib/{cronjob/scheduled_maintenance.php => cronjob.php} (100%) 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/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..79809b4 --- /dev/null +++ b/assets/js/frontend-advanced.js @@ -0,0 +1,43 @@ +/** + * 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 = $('#maintenance-allowed-ips'); + + 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 + $('#maintenance-add-ip').on('click', function(e) { + e.preventDefault(); + var currentIp = $(this).data('ip'); + addIpToWhitelist(currentIp); + }); + + // Server-IP-Adresse hinzufügen + $('#maintenance-add-server-ip').on('click', function(e) { + e.preventDefault(); + var serverIp = $(this).data('ip'); + addIpToWhitelist(serverIp); + }); +}); diff --git a/lib/cronjob/scheduled_maintenance.php b/lib/cronjob.php similarity index 100% rename from lib/cronjob/scheduled_maintenance.php rename to lib/cronjob.php diff --git a/package.yml b/package.yml index 0202fea..3095f29 100644 --- a/package.yml +++ b/package.yml @@ -61,9 +61,6 @@ permissions: console_commands: maintenance:mode: rex_maintenance_mode_command -cronjobs: - - rex_cronjob_scheduled_maintenance - default_config: http_response_code: 503 # 503, 403 allowed_ips: '' diff --git a/pages/domains.php b/pages/domains.php index e333d79..a69d7b2 100644 --- a/pages/domains.php +++ b/pages/domains.php @@ -12,6 +12,9 @@ $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')); @@ -119,15 +122,3 @@
    - - diff --git a/pages/frontend.advanced.php b/pages/frontend.advanced.php index aa8ef15..129fe5a 100644 --- a/pages/frontend.advanced.php +++ b/pages/frontend.advanced.php @@ -12,6 +12,10 @@ $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')); @@ -44,19 +48,13 @@ // IP-Adressen als formatierte Liste mit Buttons $notice = '
    '; $notice .= '
    ' . $addon->i18n('maintenance_your_ip') . ': ' . $clientIp . ''; -$notice .= '
    '; +$notice .= '
    '; if ($serverIp && $serverIp !== $clientIp) { $notice .= '
    ' . $addon->i18n('maintenance_server_ip') . ': ' . $serverIp . ''; - $notice .= '
    '; + $notice .= ' '; } $notice .= ''; $notice .= '
    ' . $addon->i18n('maintenance_allowed_ips_notice') . '
    '; -$notice .= ''; $field->setNotice($notice); // HTTP-Einstellungen @@ -89,46 +87,3 @@ - - From f2ebd2a8c0170e6adfce0dd35186455bc32076a1 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 19:55:18 +0100 Subject: [PATCH 32/51] =?UTF-8?q?feat:=20=C3=9Cberschrift=20wechselt=20mit?= =?UTF-8?q?=20Sprachwahl=20&=20Domain-Anzeige?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Überschrift zeigt 'Maintenance' (EN) / 'Wartung' (DE) je nach Sprachwahl - Domain wird unter der Überschrift angezeigt (YRewrite oder ServerName) - CSS angepasst für inline maintenance-text Spans in Überschrift - Funktioniert nur bei aktivierter Mehrsprachigkeit --- fragments/maintenance/frontend.php | 33 +++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/fragments/maintenance/frontend.php b/fragments/maintenance/frontend.php index 41aafb5..4409fa2 100644 --- a/fragments/maintenance/frontend.php +++ b/fragments/maintenance/frontend.php @@ -190,6 +190,14 @@ animation: slideInUp 0.6s ease 0.2s forwards; } + .maintenance-title .maintenance-text { + display: none; + } + + .maintenance-title .maintenance-text.active { + display: inline; + } + .maintenance-message { font-size: 1.1rem; margin-bottom: 2rem; @@ -390,7 +398,30 @@ -

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

    + + Maintenance + Wartung + + + +

    + + +

    + +

    +
    From 8fe0bbdbac880fec835482514d9beabf3c3afbba Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 19:57:57 +0100 Subject: [PATCH 33/51] =?UTF-8?q?fix:=20Layout-Anpassungen=20f=C3=BCr=20La?= =?UTF-8?q?nguage-Switcher?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - max-width: 450px, width: 100% für Container - padding-top: 4rem (Desktop) / 3.5rem (Mobile) für Platz über Icon - Language-Switcher bleibt in oberer rechter Ecke (auch mobil) - Kompaktere Buttons auf Mobile (120px statt 140px) - Verhindert Überlappung mit Info-Icon --- fragments/maintenance/frontend.php | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/fragments/maintenance/frontend.php b/fragments/maintenance/frontend.php index 4409fa2..faba7b4 100644 --- a/fragments/maintenance/frontend.php +++ b/fragments/maintenance/frontend.php @@ -66,8 +66,10 @@ } .maintenance-container { - max-width: 600px; + max-width: 450px; + width: 100%; padding: 2rem; + padding-top: 4rem; background-color: var(--card-bg); border-radius: 8px; box-shadow: var(--shadow); @@ -333,6 +335,7 @@ .maintenance-container { width: 90%; padding: 1.5rem; + padding-top: 3.5rem; } .maintenance-title { @@ -340,27 +343,18 @@ } .language-switcher { - position: relative; - top: auto; - right: auto; - margin-bottom: 1rem; - text-align: center; + top: 1rem; + right: 1rem; } .language-button { - min-width: 140px; + min-width: 120px; font-size: 0.85rem; + padding: 0.4rem 0.8rem; } .language-menu { - right: auto; - left: 50%; - transform: translateX(-50%) translateY(-10px); - min-width: 140px; - } - - .language-menu.active { - transform: translateX(-50%) translateY(0); + min-width: 120px; } } From ad351a74a2be441ee2cad9e93f865fa9cded5be3 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 19:59:07 +0100 Subject: [PATCH 34/51] =?UTF-8?q?fix:=20Initial=20Anzeige=20der=20=C3=9Cbe?= =?UTF-8?q?rschrift=20(DE=20als=20Standard)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Überschrift 'Wartung' (DE) wird initial angezeigt - Entspricht dem Verhalten der Texte (DE ist Standard) - Sprachwechsler funktioniert wie bisher --- fragments/maintenance/frontend.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fragments/maintenance/frontend.php b/fragments/maintenance/frontend.php index faba7b4..0935376 100644 --- a/fragments/maintenance/frontend.php +++ b/fragments/maintenance/frontend.php @@ -404,8 +404,8 @@

    - Maintenance - Wartung + Maintenance + Wartung From 4a856cb72e2ed40819d732299ae82238b601e680 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 20:01:32 +0100 Subject: [PATCH 35/51] fix: DE als Standard-Sprache korrekt setzen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Language-Option DE initial als 'active' markiert - Überschrift 'Wartung' (DE) mit 'active' Klasse - Konsistent mit den Nachrichtentexten (DE ist Standard) - Sprachwechsler zeigt nun korrekt DE als gewählte Sprache --- fragments/maintenance/frontend.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fragments/maintenance/frontend.php b/fragments/maintenance/frontend.php index 0935376..418d084 100644 --- a/fragments/maintenance/frontend.php +++ b/fragments/maintenance/frontend.php @@ -373,11 +373,11 @@ Language
    - - @@ -404,8 +404,8 @@

    - Maintenance - Wartung + Maintenance + Wartung From 02b1019287fab87ddad26eb1e76246dc34757737 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 20:02:53 +0100 Subject: [PATCH 36/51] fix: Language-Button zeigt korrekte aktuelle Sprache - Initial 'Deutsch' statt 'Language' - Button-Text wechselt bei Sprachwahl (English/Deutsch) - Extrahiert Sprachname aus Option-Text (ohne EN/DE Prefix) - Konsistent mit aktivem Dropdown-Item --- fragments/maintenance/frontend.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fragments/maintenance/frontend.php b/fragments/maintenance/frontend.php index 418d084..b56765f 100644 --- a/fragments/maintenance/frontend.php +++ b/fragments/maintenance/frontend.php @@ -370,7 +370,7 @@ - Language + Deutsch
    '; - $info .= '
    '; - $info .= '

    '; - - echo $info; - - /* Sidebar-Panel einbinden */ - include __DIR__ . '/frontend.sidebar.php'; - ?> -

    - diff --git a/pages/frontend.index.php b/pages/frontend.index.php index 40230ab..8a3a4d7 100644 --- a/pages/frontend.index.php +++ b/pages/frontend.index.php @@ -71,57 +71,6 @@ $field->setNotice($addon->i18n('maintenance_redirect_frontend_to_url_notice')); $field->setAttribute('type', 'url'); -// Scheduled Maintenance -$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); -} - $fragment = new rex_fragment(); $fragment->setVar('class', 'edit', false); $fragment->setVar('title', $addon->i18n('maintenance_settings_frontend_title'), false); diff --git a/pages/frontend.scheduled.php b/pages/frontend.scheduled.php new file mode 100644 index 0000000..82bdd3e --- /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'); + ?> +
    +
    + From 489aa776247fdfbea5b2f9958aad535aff903db9 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 20:29:40 +0100 Subject: [PATCH 44/51] fix: Editor-Hinweis-Text korrigiert (kein 'obiges' Feld mehr) --- lang/de_de.lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/de_de.lang b/lang/de_de.lang index b109326..f2be68b 100644 --- a/lang/de_de.lang +++ b/lang/de_de.lang @@ -98,7 +98,7 @@ 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_title = Wartungsankündigung From 6a33aa2db7f4a5d9fc45fd7ac1e2793fa42fdaf7 Mon Sep 17 00:00:00 2001 From: skerbis Date: Wed, 29 Oct 2025 19:29:44 +0000 Subject: [PATCH 45/51] Apply php-cs-fixer changes --- pages/frontend.scheduled.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pages/frontend.scheduled.php b/pages/frontend.scheduled.php index 82bdd3e..a78dca4 100644 --- a/pages/frontend.scheduled.php +++ b/pages/frontend.scheduled.php @@ -126,21 +126,21 @@ // 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'); ?> From cc1ea4de54e81c08a02da2c2310b9b2bf125ff63 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 20:36:21 +0100 Subject: [PATCH 46/51] Update package.yml --- package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.yml b/package.yml index 57e906e..8e58e65 100644 --- a/package.yml +++ b/package.yml @@ -1,5 +1,5 @@ package: maintenance -version: '3.5.0' +version: '4.0.0-beta1' author: Friends Of REDAXO supportpage: https://github.com/FriendsOfREDAXO/maintenance title: translate:maintenance_title From e136c30548a29c7ad8a31151df8db85f1001b2d2 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 20:36:24 +0100 Subject: [PATCH 47/51] =?UTF-8?q?docs:=20RELEASE.md=20f=C3=BCr=20Version?= =?UTF-8?q?=203.5.0=20erstellt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- RELEASE.md | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 RELEASE.md diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..200aea7 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,149 @@ +# Release Notes - Maintenance AddOn 3.5.0 + +## 🎉 Neue Features + +### Zeitgesteuerte Wartung +Automatische Aktivierung und Deaktivierung des Wartungsmodus zu festgelegten Zeiten. + +**Wichtig:** Funktioniert ausschließlich über Cronjob! + +**Einrichtung:** +1. System > Cronjobs > Neuen Cronjob erstellen +2. Typ: "Geplante Wartung prüfen" auswählen +3. Ausführungsart: z.B. "Jede Minute" oder "Alle 5 Minuten" +4. Maintenance > Frontend > Planung: Start- und Endzeitpunkt eingeben + +### Neue Planungs-Seite +Unter **Maintenance > Frontend > Planung** finden Sie: +- Zeitgesteuerte Wartung mit Cronjob-Status-Anzeige +- Wartungsankündigung mit Editor-Unterstützung +- Code-Beispiele für die Template-Integration +- Quick-Links zur Navigation + +### Silent Mode +Sendet nur HTTP-Status-Code ohne HTML-Inhalt - ideal für Staging-Umgebungen. + +**Aktivierung:** Maintenance > Frontend > Einstellungen > Silent Mode + +### Mehrsprachige Sperrseite +- Language-Switcher (DE/EN) in der oberen rechten Ecke +- Domain-Anzeige unter der Überschrift +- Responsive Layout (max-width: 450px) +- SessionStorage speichert Sprachpräferenz + +### Domain-Verwaltung vereinfacht +Domain-basierte Wartung jetzt ausschließlich über **Maintenance > Domains** +- Toggle-Buttons für schnelles Aktivieren/Deaktivieren +- Übersichtliche Tabelle mit allen YRewrite-Domains + +## 🔧 Verbesserungen + +### Performance +- YRewrite-Check nur einmal pro Request (statt mehrfach) +- Silent Mode Early Exit (ohne HTML-Rendering) +- Redundante Checks entfernt + +### UI/UX +- IP-Whitelist vereinfacht (ohne Bootstrap Tokenfield) +- Click-to-Add-Buttons für IP-Adressen +- Komma-getrennte IP-Listen +- Sidebar optimiert + +### Code-Qualität +- PHP CS Fixer durchgängig angewendet +- Inline-Assets in externe Dateien ausgelagert +- Cronjob nach REDAXO-Standard implementiert + +## 🐛 Bugfixes + +- Frontend-Sperre funktioniert wieder (#156) +- IP Click-to-Add repariert +- rex_i18n-Fehler im Frontend-Fragment behoben +- Undefined Variable $currentPage behoben +- Authentifizierungs-Typo korrigiert +- Language-Button zeigt korrekte Sprache +- Editor-Einstellung wird berücksichtigt + +## ⚠️ Breaking Changes + +**Manuelle Domain-Whitelist entfernt** + +Die Konfiguration `allowed_yrewrite_domains` existiert nicht mehr. + +**Migration:** +- Domain-basierte Wartung jetzt ausschließlich über **Maintenance > Domains** +- Keine manuelle Eingabe von Domains mehr nötig +- Alle YRewrite-Domains werden automatisch erkannt + +**Vorteil:** Vereinfachte Konfiguration und keine Inkonsistenzen mehr! + +## 📦 Update-Anleitung + +### Schritt 1: AddOn aktualisieren +Installieren Sie die neue Version über den Installer. + +### Schritt 2: Cache leeren +Backend > System > Einstellungen > Cache löschen + +### Schritt 3: Cronjob einrichten (optional) +Nur erforderlich, wenn Sie die **zeitgesteuerte Wartung** nutzen möchten: + +1. System > Cronjobs +2. Neuen Cronjob erstellen +3. Typ: "Geplante Wartung prüfen" +4. Ausführungsart: "Jede Minute" oder "Alle 5 Minuten" +5. Umgebung: "Frontend, Backend, Skript" +6. Speichern + +### Schritt 4: Domain-Einstellungen prüfen +Falls Sie zuvor manuelle Domain-Whitelists genutzt haben: + +1. Öffnen Sie **Maintenance > Domains** +2. Aktivieren/Deaktivieren Sie die gewünschten Domains per Toggle +3. Die alte Konfiguration wird ignoriert + +## 🎯 Neue Konfigurationswerte + +Werden automatisch gesetzt: +```yaml +silent_mode: false +scheduled_start: '' +scheduled_end: '' +``` + +## 📚 Verwendung + +### Zeitgesteuerte Wartung +```yaml +# In Maintenance > Frontend > Planung: +scheduled_start: 2025-12-31 02:00:00 +scheduled_end: 2025-12-31 06:00:00 +``` + +### Wartungsankündigung im Template +```php + +``` + +## 🔍 Getestet mit + +- ✅ REDAXO 5.17.0+ +- ✅ PHP 8.2+ +- ✅ YRewrite (optional) +- ✅ Mit/ohne Mehrsprachigkeit +- ✅ Desktop & Mobile +- ✅ Light & Dark Mode + +## 🙏 Danke + +Vielen Dank an: +- Alle Reviewer von PR #156 +- Das Upkeep AddOn für die IP-Whitelist-Inspiration +- Die Community für wertvolles Feedback + +--- + +**Bei Fragen oder Problemen:** https://github.com/FriendsOfREDAXO/maintenance/issues From b476bea1cb8f8b1ff7c193bb3584cc956aa86719 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 20:47:21 +0100 Subject: [PATCH 48/51] fix: Config-Key 'secret' konsistent zu 'maintenance_secret' korrigiert --- package.yml | 2 +- update.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.yml b/package.yml index 8e58e65..5ccf1ba 100644 --- a/package.yml +++ b/package.yml @@ -71,7 +71,7 @@ default_config: block_backend: 0 redirect_frontend_to_url: '' # `https://example.com/maintenance.html` redirect_backend_to_url: '' # `https://example.com/maintenance.html` - secret: '' + maintenance_secret: '' editor: 'class="form-control cke5-editor" data-profile="default"' maintenance_frontend_headline: 'Maintenance / Wartung' maintenance_text_en: 'This website is temporarily unavailable.' diff --git a/update.php b/update.php index 7bc8420..b998a1b 100644 --- a/update.php +++ b/update.php @@ -37,7 +37,7 @@ } if ($addon->hasConfig('secret')) { - $addon->setConfig('maintenance_secret', $addon->getConfig('maintenance_secret')); + $addon->setConfig('maintenance_secret', $addon->getConfig('secret')); } $addon->removeConfig('responsecode'); From 5633bcd00f801b8e5828938dde7932d8634f4949 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 20:51:33 +0100 Subject: [PATCH 49/51] docs: RELEASE.md auf Kurzform reduziert --- RELEASE.md | 158 ++++++++--------------------------------------------- 1 file changed, 24 insertions(+), 134 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 200aea7..58eea5a 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,149 +1,39 @@ -# Release Notes - Maintenance AddOn 3.5.0 +# Release Notes - Maintenance AddOn 4.0.0-beta1 -## 🎉 Neue Features +## Was ist neu? -### Zeitgesteuerte Wartung -Automatische Aktivierung und Deaktivierung des Wartungsmodus zu festgelegten Zeiten. +**Zeitgesteuerte Wartung** – Der Wartungsmodus kann jetzt automatisch zu bestimmten Zeiten aktiviert und deaktiviert werden (nur über Cronjob). -**Wichtig:** Funktioniert ausschließlich über Cronjob! +**Silent Mode** – Sendet nur HTTP-Status-Code ohne HTML-Inhalt, ideal für Staging-Umgebungen. -**Einrichtung:** -1. System > Cronjobs > Neuen Cronjob erstellen -2. Typ: "Geplante Wartung prüfen" auswählen -3. Ausführungsart: z.B. "Jede Minute" oder "Alle 5 Minuten" -4. Maintenance > Frontend > Planung: Start- und Endzeitpunkt eingeben +**Planungs-Seite** – Neue Übersichtsseite unter *Maintenance > Frontend > Planung* für zeitgesteuerte Wartung und Wartungsankündigungen. -### Neue Planungs-Seite -Unter **Maintenance > Frontend > Planung** finden Sie: -- Zeitgesteuerte Wartung mit Cronjob-Status-Anzeige -- Wartungsankündigung mit Editor-Unterstützung -- Code-Beispiele für die Template-Integration -- Quick-Links zur Navigation +**Mehrsprachige Sperrseite** – Language-Switcher (DE/EN) mit SessionStorage-Unterstützung. -### Silent Mode -Sendet nur HTTP-Status-Code ohne HTML-Inhalt - ideal für Staging-Umgebungen. +**Domain-Verwaltung** – YRewrite-Domains können jetzt direkt über *Maintenance > Domains* verwaltet werden (keine manuelle Eingabe mehr nötig). -**Aktivierung:** Maintenance > Frontend > Einstellungen > Silent Mode +## Was hat sich geändert? -### Mehrsprachige Sperrseite -- Language-Switcher (DE/EN) in der oberen rechten Ecke -- Domain-Anzeige unter der Überschrift -- Responsive Layout (max-width: 450px) -- SessionStorage speichert Sprachpräferenz +- IP-Whitelist vereinfacht: Click-to-Add-Buttons, komma-getrennte Liste +- UI modernisiert: Sidebar mit Quick-Links, verbesserte Navigation +- Performance optimiert: Weniger redundante Checks +- Code-Qualität: PHP CS Fixer, externe Assets, REDAXO-Standards -### Domain-Verwaltung vereinfacht -Domain-basierte Wartung jetzt ausschließlich über **Maintenance > Domains** -- Toggle-Buttons für schnelles Aktivieren/Deaktivieren -- Übersichtliche Tabelle mit allen YRewrite-Domains +## Was ist beim Update zu beachten? -## 🔧 Verbesserungen +**⚠️ 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*. -### Performance -- YRewrite-Check nur einmal pro Request (statt mehrfach) -- Silent Mode Early Exit (ohne HTML-Rendering) -- Redundante Checks entfernt +**Nach dem Update:** -### UI/UX -- IP-Whitelist vereinfacht (ohne Bootstrap Tokenfield) -- Click-to-Add-Buttons für IP-Adressen -- Komma-getrennte IP-Listen -- Sidebar optimiert +1. **Cache leeren** (Backend > System > Einstellungen) -### Code-Qualität -- PHP CS Fixer durchgängig angewendet -- Inline-Assets in externe Dateien ausgelagert -- Cronjob nach REDAXO-Standard implementiert +2. **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" -## 🐛 Bugfixes +3. **Domain-Einstellungen prüfen** (falls YRewrite verwendet): + - Öffnen Sie *Maintenance > Domains* + - Aktivieren/Deaktivieren Sie Domains per Toggle -- Frontend-Sperre funktioniert wieder (#156) -- IP Click-to-Add repariert -- rex_i18n-Fehler im Frontend-Fragment behoben -- Undefined Variable $currentPage behoben -- Authentifizierungs-Typo korrigiert -- Language-Button zeigt korrekte Sprache -- Editor-Einstellung wird berücksichtigt - -## ⚠️ Breaking Changes - -**Manuelle Domain-Whitelist entfernt** - -Die Konfiguration `allowed_yrewrite_domains` existiert nicht mehr. - -**Migration:** -- Domain-basierte Wartung jetzt ausschließlich über **Maintenance > Domains** -- Keine manuelle Eingabe von Domains mehr nötig -- Alle YRewrite-Domains werden automatisch erkannt - -**Vorteil:** Vereinfachte Konfiguration und keine Inkonsistenzen mehr! - -## 📦 Update-Anleitung - -### Schritt 1: AddOn aktualisieren -Installieren Sie die neue Version über den Installer. - -### Schritt 2: Cache leeren -Backend > System > Einstellungen > Cache löschen - -### Schritt 3: Cronjob einrichten (optional) -Nur erforderlich, wenn Sie die **zeitgesteuerte Wartung** nutzen möchten: - -1. System > Cronjobs -2. Neuen Cronjob erstellen -3. Typ: "Geplante Wartung prüfen" -4. Ausführungsart: "Jede Minute" oder "Alle 5 Minuten" -5. Umgebung: "Frontend, Backend, Skript" -6. Speichern - -### Schritt 4: Domain-Einstellungen prüfen -Falls Sie zuvor manuelle Domain-Whitelists genutzt haben: - -1. Öffnen Sie **Maintenance > Domains** -2. Aktivieren/Deaktivieren Sie die gewünschten Domains per Toggle -3. Die alte Konfiguration wird ignoriert - -## 🎯 Neue Konfigurationswerte - -Werden automatisch gesetzt: -```yaml -silent_mode: false -scheduled_start: '' -scheduled_end: '' -``` - -## 📚 Verwendung - -### Zeitgesteuerte Wartung -```yaml -# In Maintenance > Frontend > Planung: -scheduled_start: 2025-12-31 02:00:00 -scheduled_end: 2025-12-31 06:00:00 -``` - -### Wartungsankündigung im Template -```php - -``` - -## 🔍 Getestet mit - -- ✅ REDAXO 5.17.0+ -- ✅ PHP 8.2+ -- ✅ YRewrite (optional) -- ✅ Mit/ohne Mehrsprachigkeit -- ✅ Desktop & Mobile -- ✅ Light & Dark Mode - -## 🙏 Danke - -Vielen Dank an: -- Alle Reviewer von PR #156 -- Das Upkeep AddOn für die IP-Whitelist-Inspiration -- Die Community für wertvolles Feedback - ---- - -**Bei Fragen oder Problemen:** https://github.com/FriendsOfREDAXO/maintenance/issues +Die alte Konfiguration wird automatisch migriert. From d276725930925485a2795cf4d33cfdf8dd72d82d Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 20:53:06 +0100 Subject: [PATCH 50/51] =?UTF-8?q?docs:=20UX-Verbesserungen=20in=20RELEASE.?= =?UTF-8?q?md=20erg=C3=A4nzt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- RELEASE.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 58eea5a..90daddd 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -14,10 +14,17 @@ ## Was hat sich geändert? +### UX-Verbesserungen +- Einstellungen reorganisiert: Frontend > Planung, Frontend > Erweitert - IP-Whitelist vereinfacht: Click-to-Add-Buttons, komma-getrennte Liste -- UI modernisiert: Sidebar mit Quick-Links, verbesserte Navigation -- Performance optimiert: Weniger redundante Checks -- Code-Qualität: PHP CS Fixer, externe Assets, REDAXO-Standards +- 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 ## Was ist beim Update zu beachten? From 40470c90d59e93b060281429a034fe053611fdb4 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 29 Oct 2025 20:56:52 +0100 Subject: [PATCH 51/51] =?UTF-8?q?docs:=20Erweiterte=20Console-Kommandos=20?= =?UTF-8?q?in=20RELEASE.md=20erg=C3=A4nzt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- RELEASE.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 90daddd..94d1b5f 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,5 +1,9 @@ # 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). @@ -12,6 +16,15 @@ **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 @@ -26,15 +39,9 @@ - Redundante Checks entfernt - PHP CS Fixer, externe Assets, REDAXO-Standards -## 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*. - **Nach dem Update:** -1. **Cache leeren** (Backend > System > Einstellungen) - -2. **Cronjob einrichten** (nur für zeitgesteuerte Wartung): +**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"