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);
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc