maintenance-1.0.0-beta1/src/Form/MaintenanceModeForm.php
src/Form/MaintenanceModeForm.php
<?php
namespace Drupal\maintenance\Form;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Path\PathValidatorInterface;
use Drupal\Core\State\StateInterface;
use Drupal\maintenance\MaintenanceManagerInterface;
use Drupal\maintenance\MaintenanceStringHelperTrait;
use Drupal\system\Form\SiteMaintenanceModeForm;
use Drupal\user\PermissionHandlerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides the advanced maintenance mode configuration form.
*
* This form extends the core SiteMaintenanceModeForm and adds
* advanced features such as scheduling, redirection, theming,
* logging, visibility controls, and dynamic messages.
*/
class MaintenanceModeForm extends SiteMaintenanceModeForm {
use MaintenanceStringHelperTrait;
/**
* The maintenance manager service.
*
* @var \Drupal\maintenance\MaintenanceManagerInterface
*/
protected $maintenance;
/**
* The state keyvalue collection.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* The permission handler.
*
* @var \Drupal\user\PermissionHandlerInterface
*/
protected $permissionHandler;
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The theme handler service.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The date formatter service.
*
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected $dateFormatter;
/**
* The time service.
*
* @var \Drupal\Component\Datetime\TimeInterface
*/
protected $time;
/**
* The path validator service.
*
* @var \Drupal\Core\Path\PathValidatorInterface
*/
protected $pathValidator;
/**
* Constructs a new SiteMaintenanceModeForm.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The factory for configuration objects.
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
* The typed config manager.
* @param \Drupal\Core\State\StateInterface $state
* The state keyvalue collection to use.
* @param \Drupal\user\PermissionHandlerInterface $permission_handler
* The permission handler.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke the alter hook with.
* @param \Drupal\Core\Extension\themeHandlerInterface $theme_handler
* The theme handler to invoke the alter hook with.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
* @param \Drupal\maintenance\MaintenanceManagerInterface $maintenance
* The maintenance manager service.
* @param \Drupal\Core\Path\PathValidatorInterface $path_validator
* The path validator service.
*/
public function __construct(
ConfigFactoryInterface $config_factory,
TypedConfigManagerInterface $typedConfigManager,
StateInterface $state,
PermissionHandlerInterface $permission_handler,
ModuleHandlerInterface $module_handler,
ThemeHandlerInterface $theme_handler,
EntityTypeManagerInterface $entity_type_manager,
DateFormatterInterface $date_formatter,
TimeInterface $time,
MaintenanceManagerInterface $maintenance,
PathValidatorInterface $path_validator,
) {
parent::__construct($config_factory, $typedConfigManager, $state, $permission_handler);
$this->moduleHandler = $module_handler;
$this->themeHandler = $theme_handler;
$this->entityTypeManager = $entity_type_manager;
$this->dateFormatter = $date_formatter;
$this->time = $time;
$this->maintenance = $maintenance;
$this->pathValidator = $path_validator;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container): static {
return new static(
$container->get('config.factory'),
$container->get('config.typed'),
$container->get('state'),
$container->get('user.permissions'),
$container->get('module_handler'),
$container->get('theme_handler'),
$container->get('entity_type.manager'),
$container->get('date.formatter'),
$container->get('datetime.time'),
$container->get('maintenance.manager'),
$container->get('path.validator'),
);
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames(): array {
return [
'maintenance.settings',
'system.maintenance',
];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// Load the current maintenance config.
$config = $this->config('maintenance.settings');
// Load the core maintenance mode state.
$maintenance_enabled = $this->state->get('system.maintenance_mode');
// Build base form from parent (SiteMaintenanceModeForm).
$form = parent::buildForm($form, $form_state);
// Add suffix label for time fields (e.g., delay seconds).
$time_unit_label = ' <span class="maintenance-unit-flag">s</span>';
// Begin custom Maintenance form container.
$form['maintenance'] = [
'#type' => 'container',
'#title' => $this->t('Maintenance mode configuration'),
];
// Content display mode selection (text / HTML / node).
$form['maintenance']['maintenance_mode_base'] = [
'#type' => 'radios',
'#title' => $this->t('Content type for maintenance page'),
'#options' => [
'text' => $this->t('Plain text message'),
'html' => $this->t('HTML formatted message'),
'node' => $this->t('Display a node content'),
],
'#default_value' => $config->get('content.base') ?? 'html',
];
// Maintenance title when using HTML display.
$form['maintenance']['maintenance_mode_title'] = [
'#type' => 'textfield',
'#title' => $this->t('Maintenance page title'),
'#default_value' => $config->get('content.title') ?? $this->t('Site under maintenance'),
'#states' => [
'visible' => [
':input[name="maintenance_mode_base"]' => ['value' => 'html'],
],
],
];
// Message for plain text mode (provided by parent form).
$form['maintenance']['maintenance_mode_message'] = $form['maintenance_mode_message'];
$form['maintenance']['maintenance_mode_message']['#states'] = [
'visible' => [
':input[name="maintenance_mode_base"]' => ['value' => 'text'],
],
];
// Remove the original plain text message field from parent form.
// We'll reinsert it into our custom container with conditional visibility.
unset($form['maintenance_mode_message']);
// HTML message (text_format field) for 'html' display mode.
$form['maintenance']['maintenance_mode_content'] = [
'#type' => 'text_format',
'#format' => 'basic_html',
'#title' => $this->t('Maintenance message (HTML)'),
'#default_value' => $config->get('content.message'),
'#states' => [
'visible' => [
':input[name="maintenance_mode_base"]' => ['value' => 'html'],
],
],
];
// Load selected node if previously set in config.
$node_id = $config->get('content.node');
$node = $node_id ? $this->entityTypeManager
->getStorage('node')
->load($node_id) : NULL;
// Node autocomplete input for 'node' display mode.
$form['maintenance']['maintenance_mode_node'] = [
'#type' => 'entity_autocomplete',
'#target_type' => 'node',
'#title' => $this->t('Maintenance node'),
'#description' => $this->t('Node to show when maintenance mode is on. When this field is empty, the default message will be shown.'),
'#default_value' => $node,
'#states' => [
'visible' => [
':input[name="maintenance_mode_base"]' => ['value' => 'node'],
],
],
];
// Container for advanced configuration options.
// Organizes extended settings like redirection, scheduling, and HTTP codes
// into a collapsible section for better form clarity.
$form['advanced'] = [
'#type' => 'container',
'#accordion' => TRUE,
];
// Container for meta information and logging options.
$form['meta'] = [
'#type' => 'container',
'#group' => 'advanced',
'#title' => $this->t('Status and Log'),
'#attributes' => ['class' => ['entity-meta__header']],
'#weight' => -10,
'#access' => TRUE,
];
// Current maintenance mode state indicator.
$form['meta']['enabled'] = [
'#type' => 'item',
'#markup' => $maintenance_enabled
? $this->t('Is under maintenance')
: $this->t('Not under maintenance'),
'#wrapper_attributes' => ['class' => ['entity-meta__title']],
];
// Display the last time maintenance mode was enabled.
$form['meta']['latest'] = [
'#type' => 'item',
'#title' => $this->t('Last enabled'),
'#markup' => $config->get('report.latest')
? $this->dateFormatter->format($config->get('report.latest'), 'short')
: $this->t('Not recorded yet'),
'#wrapper_attributes' => ['class' => ['entity-meta__last-saved']],
];
// Checkbox for enabling internal log tracking.
$form['meta']['maintenance_mode_log'] = [
'#type' => 'checkbox',
'#title' => $this->t('Enable log entry'),
'#default_value' => $config->get('report.log.enabled'),
];
// Optional log note to describe purpose of maintenance.
$form['meta']['maintenance_mode_note'] = [
'#type' => 'textarea',
'#title' => $this->t('Log note'),
'#description' => $this->t('Describe why the site was taken into maintenance mode.'),
'#rows' => 3,
'#default_value' => $config->get('report.log.note'),
'#states' => [
'visible' => [
':input[name="maintenance_mode_log"]' => ['checked' => TRUE],
],
],
];
// Section for redirect configuration.
$form['redirect'] = [
'#type' => 'details',
'#title' => $this->t('Redirect'),
'#group' => 'advanced',
'#attributes' => ['class' => ['maintenance-form-redirect']],
'#open' => (bool) $config->get('redirect.enabled'),
];
// Checkbox to enable/disable redirection during maintenance.
$form['redirect']['maintenance_mode_redirect'] = [
'#type' => 'checkbox',
'#title' => $this->t('Enable URL redirect'),
'#description' => $this->t('Redirect users to a different page while in maintenance mode.'),
'#default_value' => $config->get('redirect.enabled'),
];
// URL field for redirect destination.
$form['redirect']['maintenance_mode_url'] = [
'#type' => 'textfield',
'#title' => $this->t('URL'),
'#description' => $this->t('The URL visitors will be redirected to (internal or external).'),
'#default_value' => $config->get('redirect.url'),
'#states' => [
'disabled' => [
':input[name="maintenance_mode_redirect"]' => ['checked' => FALSE],
],
],
];
// Delay before redirect (in seconds).
$form['redirect']['maintenance_mode_delay'] = [
'#type' => 'number',
'#title' => $this->t('delay'),
'#description' => $this->t('Seconds to wait before redirect. Use 0 for immediate redirection.'),
'#min' => 0,
'#step' => 1,
'#field_suffix' => $time_unit_label,
'#default_value' => $config->get('redirect.delay') ?? 0,
'#states' => [
'disabled' => [
':input[name="maintenance_mode_redirect"]' => ['checked' => FALSE],
],
],
];
// Section for automatic or manual page reload.
$form['refresh'] = [
'#type' => 'details',
'#title' => $this->t('Page reload'),
'#group' => 'advanced',
'#open' => (bool) $config->get('refresh.enabled'),
];
// Enable reload feature.
$form['refresh']['maintenance_mode_refresh'] = [
'#type' => 'checkbox',
'#title' => $this->t('Enable reload feature'),
'#description' => $this->t('Show a reload button or auto-reload the maintenance page.'),
'#default_value' => $config->get('refresh.enabled'),
];
// Container for reload method settings.
$form['refresh']['reload_settings'] = [
'#type' => 'container',
'#states' => [
'disabled' => [
':input[name="maintenance_mode_refresh"]' => ['checked' => FALSE],
],
],
];
// Choose between button/manual reload or auto-reload.
$form['refresh']['reload_settings']['maintenance_mode_reload'] = [
'#type' => 'radios',
'#title' => $this->t('Reload behavior'),
'#default_value' => $config->get('refresh.reload') ?? 1,
'#options' => [
0 => $this->t('Show "Reload" button'),
1 => $this->t("Don't show reload button"),
2 => $this->t('Auto-reload page every 15 seconds'),
],
];
// Section to override HTTP status code for maintenance response.
$form['http_status'] = [
'#type' => 'details',
'#title' => $this->t('HTTP status code'),
'#group' => 'advanced',
'#open' => (bool) $config->get('status.enabled'),
'#attributes' => ['class' => ['maintenance-form-http-status']],
];
// Enable/disable custom HTTP status code.
$form['http_status']['maintenance_mode_status'] = [
'#type' => 'checkbox',
'#title' => $this->t('Override status code'),
'#description' => $this->t('Enable to override the default HTTP response status code.'),
'#default_value' => $config->get('status.enabled'),
];
// Status code select field.
$form['http_status']['maintenance_mode_code'] = [
'#type' => 'select',
'#title' => $this->t('HTTP status code'),
'#options' => $this->maintenance->getHttpStatusCodes(),
'#default_value' => $config->get('status.code') ?? '200',
'#description' => $this->t('Common codes: 200 (OK), 403 (Forbidden), 503 (Service Unavailable). See the full list on <a href="@url" target="_blank">HTTP status codes</a>.', [
'@url' => 'https://en.wikipedia.org/wiki/List_of_HTTP_status_codes',
]),
'#states' => [
'disabled' => [
':input[name="maintenance_mode_status"]' => ['checked' => FALSE],
],
],
];
// Schedule configuration section.
$form['schedule'] = [
'#type' => 'details',
'#title' => $this->t('Schedule'),
'#group' => 'advanced',
'#attributes' => ['class' => ['maintenance-form-schedule']],
'#open' => (bool) $config->get('schedule.enabled'),
];
// Enable scheduling feature.
$form['schedule']['schedule_enabled'] = [
'#type' => 'checkbox',
'#title' => $this->t('Enable scheduling'),
'#description' => $this->t('Automatically enable maintenance mode at a specific date and time.'),
'#default_value' => $config->get('schedule.enabled'),
];
// Container for schedule-related fields.
$form['schedule']['schedule_settings'] = [
'#type' => 'container',
'#states' => [
'disabled' => [
':input[name="schedule_enabled"]' => ['checked' => FALSE],
],
],
];
// Default start time if exists.
$start_value = $config->get('schedule.start');
$start_date_time = $start_value ? new DrupalDateTime($start_value) : NULL;
// Schedule start datetime.
$form['schedule']['schedule_settings']['start_time'] = [
'#type' => 'datetime',
'#title' => $this->t('Start time'),
'#default_value' => $start_date_time,
'#description' => $this->t('When maintenance mode should begin.'),
];
// Enable automatic disabling after maintenance start.
$form['schedule']['schedule_settings']['auto_disable'] = [
'#type' => 'checkbox',
'#title' => $this->t('Auto-disable after end time'),
'#description' => $this->t('Automatically turn off maintenance mode at the defined end time.'),
'#default_value' => $config->get('schedule.automatic'),
];
// Container for end time, visible only if auto_disable is enabled.
$form['schedule']['schedule_settings']['end_time_container'] = [
'#type' => 'container',
'#states' => [
'visible' => [
':input[name="auto_disable"]' => ['checked' => TRUE],
],
],
];
// Default end time if exists.
$end_value = $config->get('schedule.end');
$end_date_time = $end_value ? new DrupalDateTime($end_value) : NULL;
// Schedule end datetime.
$form['schedule']['schedule_settings']['end_time_container']['end_time'] = [
'#type' => 'datetime',
'#title' => $this->t('End time'),
'#default_value' => $end_date_time,
'#description' => $this->t('When maintenance mode should end.'),
];
// Enable warning message before start.
$form['schedule']['schedule_settings']['warning'] = [
'#type' => 'checkbox',
'#title' => $this->t('Show warning before start'),
'#default_value' => (bool) $config->get('schedule.warning'),
'#description' => $this->t('Display a warning message before maintenance begins.'),
];
// Container for warning configuration.
$form['schedule']['schedule_settings']['warning_container'] = [
'#type' => 'container',
'#states' => [
'visible' => [
':input[name="warning"]' => ['checked' => TRUE],
],
],
];
// Warning message text.
$form['schedule']['schedule_settings']['warning_container']['warning_message'] = [
'#type' => 'textfield',
'#title' => $this->t('Warning message'),
'#default_value' => $config->get('schedule.warning'),
'#description' => $this->t('No HTML tags allowed.'),
];
// Container for offset (value + unit).
$form['schedule']['schedule_settings']['warning_container']['warning_offset'] = [
'#type' => 'item',
'#title' => $this->t('Show warning this long before start'),
'#description' => $this->t('Set how long before start time the warning message appears.'),
];
// Inline container for numeric offset and unit.
$form['schedule']['schedule_settings']['warning_container']['warning_offset']['offset_container'] = [
'#type' => 'container',
'#attributes' => ['class' => ['container-inline']],
];
// Offset value.
$form['schedule']['schedule_settings']['warning_container']['warning_offset']['offset_container']['offset_value'] = [
'#type' => 'number',
'#title_display' => 'invisible',
'#min' => 0,
'#size' => 4,
'#default_value' => $config->get('schedule.offset.value'),
];
// Offset unit (minutes, hours, etc.).
$form['schedule']['schedule_settings']['warning_container']['offset_container']['offset_unit'] = [
'#type' => 'select',
'#title_display' => 'invisible',
'#options' => $this->maintenance->getAllowedUnits(),
'#default_value' => $config->get('schedule.offset.unit'),
];
// Theme options section.
$form['maintenance_themes'] = [
'#type' => 'details',
'#title' => $this->t('Maintenance theme'),
'#open' => TRUE,
];
// Theme template selector.
$form['maintenance_themes']['theme'] = [
'#type' => 'select',
'#title' => $this->t('Select maintenance page template'),
'#options' => $this->maintenance->getAvailableThemes(),
'#default_value' => $config->get('theme.template') ?? 'particles',
];
// Define maintenance access rules based on IPs, routes, or query strings,
// enabling selective user access during maintenance mode. Rules leverage
// request context or user source to control access seamlessly..
$form['visibility_tabs'] = [
'#type' => 'vertical_tabs',
'#title' => $this->t('Visibility'),
'#parents' => ['visibility_tabs'],
];
// IP address-based visibility rules,
// Users from matching IPs can be exempted or targeted.
$form['request_ip'] = [
'#type' => 'details',
'#title' => $this->t('IP addresses'),
'#group' => 'visibility_tabs',
'#open' => TRUE,
];
// Current IP.
$client_ip = \Drupal::request()->getClientIp();
// List of IPs.
$form['request_ip']['ips'] = [
'#type' => 'textarea',
'#title' => $this->t('IP addresses'),
'#default_value' => $this->arrayToString($config->get('ip.addresses')),
'#description' => $this->t("Specify access using IP addresses. Enter one IP per line. Both individual addresses and CIDR notation (e.g. 192.168.0.0/24) are supported. Your current IP is %ip.", [
'%ip' => $client_ip,
]),
];
// Visibility mode for IPs.
$form['request_ip']['ip_visibility'] = [
'#type' => 'radios',
'#title' => $this->t('Mode'),
'#title_display' => 'invisible',
'#options' => [
0 => $this->t('Allow access to the listed IP addresses'),
1 => $this->t('Show maintenance page only to the listed IP addresses'),
],
'#default_value' => $config->get('ip.visibility'),
];
// Route path-based visibility rules,
// Define which pages/paths are included/excluded.
$form['request_path'] = [
'#type' => 'details',
'#title' => $this->t('Pages'),
'#group' => 'visibility_tabs',
'#open' => TRUE,
];
// List of route paths.
$form['request_path']['routes'] = [
'#type' => 'textarea',
'#title' => $this->t('Pages'),
'#default_value' => $this->arrayToString($config->get('page.routes')),
'#description' => $this->t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. An example path is %node-wildcard for every node page. %front is the front page.", [
'%node-wildcard' => '/node/*',
'%front' => '<front>',
]),
];
// Visibility mode for routes.
$form['request_path']['page_visibility'] = [
'#type' => 'radios',
'#title' => $this->t('Mode'),
'#title_display' => 'invisible',
'#options' => [
0 => $this->t('Allow access to the listed pages'),
1 => $this->t('Show maintenance page only on the listed pages'),
],
'#default_value' => $config->get('page.visibility'),
];
// Query string-based visibility rules,
// Specific query key allows bypassing maintenance mode.
$form['request_string'] = [
'#type' => 'details',
'#title' => $this->t('Query string'),
'#attributes' => ['class' => ['details--queries', 'b-tooltip']],
'#group' => 'visibility_tabs',
'#open' => TRUE,
];
// Current Query string.
$query_key = $config->get('query.strings');
$form['request_string']['queries'] = [
'#type' => 'textfield',
'#title' => $this->t('Query string'),
'#default_value' => $query_key,
'#description' => $this->t('Specify a query string to allow access. If the URL contains this key-value pair, the maintenance page will be bypassed for that visitor. For example: ?access_key=demo. <a href="@link" target="_blank">Learn more</a>.', [
'@link' => 'http://en.wikipedia.org/wiki/Query_string',
]),
];
// Attach JS and CSS assets needed for this form.
$form['#attached']['library'][] = 'maintenance/maintenance.form';
// If admin theme is active, structure the form for sticky sidebar, etc.
if (_maintenance_admin_theme()) {
// Move primary action buttons into a dedicated sidebar container.
if (isset($form['actions'])) {
$form['maintenance_actions'] = [
'#type' => 'container',
'#weight' => -1,
'#multilingual' => TRUE,
'#attributes' => ['class' => ['maintenance-sticky']],
];
// Move the core maintenance toggle checkbox into the new action group.
$form['maintenance_mode']['#group'] = 'maintenance_actions';
$form['maintenance_mode']['#prefix'] = '<div class="field--maintenance-mode">';
$form['maintenance_mode']['#suffix'] = '</div>';
$form['maintenance_mode']['#title'] = $this->t('Enabled');
// Move description to container and remove from checkbox.
$form['maintenance']['#description'] = $form['maintenance_mode']['#description'];
unset($form['maintenance_mode']['#description']);
// Move submit/cancel buttons into the action container.
$form['maintenance_actions']['actions'] = $form['actions'] ?? [];
$form['maintenance_actions']['actions']['#weight'] = 130;
unset($form['actions']);
// Add visual toggler to open/close sidebar panel.
$hide_panel = $this->t('Hide sidebar panel');
$form['maintenance_actions']['maintenance_sidebar_toggle'] = [
'#markup' => '
<a href="#toggle-sidebar" class="meta-sidebar__trigger trigger" role="button" title="' . $hide_panel . '" aria-controls="maintenance-sidebar">
<span class="visually-hidden">' . $hide_panel . '</span>
</a>',
'#weight' => 999,
];
// Add close button inside sidebar.
$close_panel = $this->t('Close sidebar panel');
$form['maintenance_sidebar']['maintenance_sidebar_close'] = [
'#markup' => '
<a href="#close-sidebar" class="meta-sidebar__close trigger" role="button" title="' . $close_panel . '">
<span class="visually-hidden">' . $close_panel . '</span>
</a>',
];
// Add transparent overlay when sidebar is open.
$form['maintenance_sidebar_overlay'] = [
'#markup' => '<div class="meta-sidebar__overlay trigger"></div>',
];
}
// Attach additional styles/scripts for admin theme layout enhancements.
$form['#attached']['library'][] = 'maintenance/maintenance.edit';
}
// Final override: render this form with custom theme if defined.
$form['#theme'] = 'maintenance_form';
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state): void {
// Validate scheduled end is after start if enabled.
if ($form_state->getValue('schedule_enabled')) {
$start = $form_state->getValue('start_time');
$end = $form_state->getValue('end_time');
if ($start instanceof DrupalDateTime && $end instanceof DrupalDateTime && $end < $start) {
$form_state->setErrorByName('end_time', $this->t('End time must be after start time.'));
}
// Validate offset.
$offset_value = $form_state->getValue('offset_value');
if (!is_numeric($offset_value) || $offset_value < 0) {
$form_state->setErrorByName('offset_value', $this->t('Offset value must be a non-negative number.'));
}
}
// Validate HTTP status code.
$code = $form_state->getValue('maintenance_mode_code');
if (!is_numeric($code) || $code < 100 || $code > 599) {
$form_state->setErrorByName('maintenance_mode_code', $this->t('HTTP status code must be between 100 and 599.'));
}
// Validate redirect URL & delay if enabled.
if ($form_state->getValue('maintenance_mode_redirect')) {
$url = $form_state->getValue('maintenance_mode_url');
if (!empty($url) && !$this->pathValidator->isValid($url)) {
$form_state->setErrorByName('maintenance_mode_url', $this->t('The redirect URL is not valid.'));
}
$delay = $form_state->getValue('maintenance_mode_delay');
if (!is_numeric($delay) || $delay < 0) {
$form_state->setErrorByName('maintenance_mode_delay', $this->t('Redirect delay must be a non-negative number.'));
}
}
// Validate IPs.
$ip_list_raw = $form_state->getValue('ips');
$ip_list = $this->stringToArray($ip_list_raw);
foreach ((array) $ip_list as $index => $ip) {
if (str_contains($ip, '/')) {
[$addr, $mask] = explode('/', $ip, 2) + [NULL, NULL];
if (
!filter_var($addr, FILTER_VALIDATE_IP) ||
!is_numeric($mask) ||
$mask < 0 || $mask > 32
) {
$form_state->setErrorByName('ips', $this->t('Line @line: Invalid CIDR IP format: @ip', [
'@line' => $index + 1,
'@ip' => $ip,
]));
}
}
elseif (!filter_var($ip, FILTER_VALIDATE_IP)) {
$form_state->setErrorByName('ips', $this->t('Line @line: Invalid IP address: @ip', [
'@line' => $index + 1,
'@ip' => $ip,
]));
}
}
// Validate route paths.
$routes_raw = $form_state->getValue('routes');
$routes = $this->stringToArray($routes_raw);
foreach ((array) $routes as $index => $route) {
if ($route === '<front>') {
continue;
}
if (!str_starts_with($route, '/')) {
$form_state->setErrorByName('routes', $this->t('Line @line: Route must start with "/" or use <front>.', [
'@line' => $index + 1,
]));
}
if (preg_match('/\s/', $route)) {
$form_state->setErrorByName('routes', $this->t('Line @line: Route must not contain spaces: @route', [
'@line' => $index + 1,
'@route' => $route,
]));
}
}
// Validate query string does not contain spaces.
$query = $form_state->getValue('queries');
if (!empty($query) && preg_match('/\s/', $query)) {
$form_state->setErrorByName('queries', $this->t('Query string must not contain spaces.'));
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state): void {
// Get all form values.
$values = $form_state->getValues();
// Prepare editable config object.
$config = $this->config('maintenance.settings');
// Track current time if maintenance mode is being enabled.
if (!empty($values['maintenance_mode'])) {
$config->set('report.latest', $this->time->getCurrentTime());
}
// Maintenance Log Settings.
$config->set('report.enabled', $values['maintenance_mode']);
$config->set('report.log.enabled', $values['maintenance_mode_log']);
$config->set('report.log.note', $values['maintenance_mode_note']);
// Content Settings.
$config->set('content.base', $values['maintenance_mode_base']);
$config->set('content.title', $values['maintenance_mode_title']);
$config->set('content.node', $values['maintenance_mode_node']);
// Set message depending on mode.
if ($values['maintenance_mode_base'] === 'text') {
$config->set('content.message', $values['maintenance_mode_message']);
}
elseif ($values['maintenance_mode_base'] === 'html') {
$config->set('content.message', $values['maintenance_mode_content']['value']);
}
// Redirect Settings.
$config->set('redirect.enabled', $values['maintenance_mode_redirect']);
$config->set('redirect.url', $values['maintenance_mode_url']);
$config->set('redirect.delay', (int) $values['maintenance_mode_delay']);
// Refresh Settings.
$config->set('refresh.enabled', $values['maintenance_mode_refresh']);
$config->set('refresh.reload', (int) $values['maintenance_mode_reload']);
// HTTP Status Settings.
$config->set('status.enabled', $values['maintenance_mode_status']);
$config->set('status.code', $values['maintenance_mode_code']);
// Schedule Settings.
$config->set('schedule.enabled', $values['schedule_enabled']);
$config->set('schedule.automatic', $values['auto_disable']);
$config->set('schedule.warning', $values['warning_message']);
$config->set('schedule.offset.value', (int) $values['offset_value']);
$config->set('schedule.offset.unit', $values['offset_unit']);
// Format start and end times.
$start = $values['start_time'];
$end = $values['end_time'];
$config->set('schedule.start', $start ? $start->format('Y-m-d H:i:s') : '');
$config->set('schedule.end', $end ? $end->format('Y-m-d H:i:s') : '');
// Theme Settings.
$config->set('theme.template', $values['theme']);
// IP Visibility Settings.
$config->set('ip.visibility', (int) $values['ip_visibility']);
$config->set('ip.addresses', $this->stringToArray($values['ips']));
// Page Visibility Settings.
$config->set('page.visibility', (int) $values['page_visibility']);
$config->set('page.routes', $this->stringToArray($values['routes']));
// Query Visibility Settings.
$config->set('query.strings', trim($values['queries']));
// Save the config object.
$config->save();
// Rebuild site cache.
drupal_flush_all_caches();
// Continue to parent handler.
parent::submitForm($form, $form_state);
}
}
