Commit 64f70652 authored by catch's avatar catch
Browse files

Issue #2408549 by alexpott, narendraR, yash.rode, kunal.sachdev, lauriii, Liam...

Issue #2408549 by alexpott, narendraR, yash.rode, kunal.sachdev, lauriii, Liam Morland, Wim Leers, Hardik_Patel_12, jofitz, DamienMcKenna, eiriksm, andypost, jenlampton, Gábor Hojtsy, swentel, borisson_, jhedstrom, snehi, Elijah Lynn, narendra.rajwar27, Shubham Chandra, smustgrave, sime, AaronMcHale, Chi, karolus, rkoller, joshua.boltz, anavarre, colan, frob, Berdir, bircher, minnur, effulgentsia, quietone, catch, xjm, hanoii, benjifisher, worldlinemine, larowlan, longwave, simohell, shaal, worldlinemine: Display status message on configuration forms when there are overridden values

(cherry picked from commit 46895166)
parent de3e9ac6
Loading
Loading
Loading
Loading
Loading
+57 −1
Original line number Diff line number Diff line
@@ -2,11 +2,13 @@

namespace Drupal\Core\Form;

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Config\Config;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
@@ -86,7 +88,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    // property.
    $form['#process'][] = '::loadDefaultValuesFromConfig';
    $form['#after_build'][] = '::storeConfigKeyToFormElementMap';

    $form['#after_build'][] = '::checkConfigOverrides';
    return $form;
  }

@@ -333,4 +335,58 @@ private static function copyFormValuesToConfig(Config $config, FormStateInterfac
    }
  }

  /**
   * Form #after_build callback: Adds message if overrides exist.
   */
  public function checkConfigOverrides(array $form, FormStateInterface $form_state): array {
    // Determine which of those editable config keys have overrides.
    $override_links = [];
    $map = $form_state->get(static::CONFIG_KEY_TO_FORM_ELEMENT_MAP) ?? [];
    foreach ($map as $config_name => $config_keys) {
      $stored_config = $this->configFactory->get($config_name);
      if (!$stored_config->hasOverrides()) {
        // The config has no overrides at all. Can be skipped.
        continue;
      }

      foreach ($config_keys as $key => $array_parents) {
        if ($stored_config->hasOverrides($key)) {
          $element = NestedArray::getValue($form, $array_parents);
          $override_links[] = [
            'attributes' => ['title' => $this->t("'@title' form element", ['@title' => $element['#title']])],
            'url' => Url::fromUri("internal:#{$element['#id']}"),
            'title' => $element['#title'],
          ];
        }
      }
    }

    if (!empty($override_links)) {
      $override_output = [
        '#theme' => 'links__config_overrides',
        '#heading' => [
          'text' => $this->t('These values are overridden. Changes on this form will be saved, but overrides will take precedence. See <a href="https://www.drupal.org/docs/drupal-apis/configuration-api/configuration-override-system">configuration overrides documentation</a> for more information.'),
          'level' => 'div',
        ],
        '#links' => $override_links,
      ];
      $form['config_override_status_messages'] = [
        'message' => [
          '#theme' => 'status_messages',
          '#message_list' => ['status' => [$override_output]],
          '#status_headings' => [
            'status' => $this->t('Status message'),
          ],
        ],
        // Ensure that the status message is at the top of the form.
        '#weight' => array_reduce(
          Element::children($form),
          fn (int $carry, string $key) => min(($form[$key]['#weight'] ?? 0), $carry),
          0
        ) - 1,
      ];
    }
    return $form;
  }

}
+4 −0
Original line number Diff line number Diff line
name: 'Configuration override message test'
type: module
package: Testing
version: VERSION
+17 −0
Original line number Diff line number Diff line
<?php

/**
 * @file
 * Tests configuration override message functionality.
 */

use Drupal\Core\Form\FormStateInterface;

/**
 * Implements hook_form_FORM_ID_alter().
 */
function config_override_message_test_form_system_site_information_settings_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
  // Set a weight to a negative amount to ensure the config overrides message
  // is above it.
  $form['site_information']['#weight'] = -5;
}
+54 −12
Original line number Diff line number Diff line
@@ -14,6 +14,18 @@
 */
class ConfigFormOverrideTest extends BrowserTestBase {

  /**
   * Message text that appears when forms have values for overridden config.
   */
  private const OVERRIDE_TEXT = 'These values are overridden. Changes on this form will be saved, but overrides will take precedence. See configuration overrides documentation for more information.';

  /**
   * Modules to enable.
   *
   * @var array
   */
  protected static $modules = ['update', 'config_override_message_test'];

  /**
   * {@inheritdoc}
   */
@@ -26,31 +38,61 @@ public function testFormsWithOverrides(): void {
    $this->drupalLogin($this->drupalCreateUser([
      'access administration pages',
      'administer site configuration',
      'link to any page',
    ]));

    $overridden_name = 'Site name global conf override';
    // Set up an overrides for configuration that is present in the form.
    $settings['config']['system.site']['weight_select_max'] = (object) [
      'value' => 200,
      'required' => TRUE,
    ];
    $this->writeSettings($settings);

    // Set up an override.
    // Test that although system.site has an overridden key no override
    // information is displayed because there is no corresponding form field.
    $this->drupalGet('admin/config/system/site-information');
    $this->assertSession()->fieldValueEquals("site_name", 'Drupal');
    $this->assertSession()->pageTextNotContains(self::OVERRIDE_TEXT);

    // Set up an overrides for configuration that is present in the form.
    $overridden_name = 'Site name global conf override';
    $settings['config']['system.site']['name'] = (object) [
      'value' => $overridden_name,
      'required' => TRUE,
    ];
    $settings['config']['update.settings']['notification']['emails'] = (object) [
      'value' => [
        0 => 'a@abc.com',
        1 => 'admin@example.com',
      ],
      'required' => TRUE,
    ];
    $this->writeSettings($settings);

    // Test that everything on the form is the same, but that the override
    // worked for the actual site name.
    $this->drupalGet('admin/config/system/site-information');
    $this->assertSession()->titleEquals('Basic site settings | ' . $overridden_name);
    $this->assertSession()->elementTextContains('css', 'div[data-drupal-messages]', self::OVERRIDE_TEXT);
    // Ensure the configuration overrides message is at the top of the form.
    $this->assertSession()->elementExists('css', 'div[data-drupal-messages] + details#edit-site-information');
    $this->assertSession()->elementContains('css', 'div[data-drupal-messages]', '<a href="#edit-site-name" title="\'Site name\' form element">Site name</a>');
    $this->assertSession()->fieldValueEquals("site_name", 'Drupal');

    // Submit the form and ensure the site name is not changed.
    $edit = [
    $this->submitForm([
      'site_name' => 'Custom site name',
    ];
    $this->drupalGet('admin/config/system/site-information');
    $this->submitForm($edit, 'Save configuration');
    ], 'Save configuration');
    $this->assertSession()->titleEquals('Basic site settings | ' . $overridden_name);
    $this->assertSession()->fieldValueEquals("site_name", $edit['site_name']);
    $this->assertSession()->fieldValueEquals("site_name", 'Custom site name');

    // Ensure it works for sequence.
    $this->drupalGet('admin/reports/updates/settings');
    $this->submitForm([], 'Save configuration');
    $this->assertSession()->pageTextContainsOnce(self::OVERRIDE_TEXT);
    // There are two status messages on the page due to the save.
    $messages = $this->getSession()->getPage()->findAll('css', 'div[data-drupal-messages]');
    $this->assertCount(2, $messages);
    $this->assertStringContainsString('The configuration options have been saved.', $messages[0]->getText());
    $this->assertTrue(
      $messages[1]->hasLink('Email addresses to notify when updates are available'),
      "Link to 'Email addresses to notify when updates are available' exists"
    );
  }

}
+10 −14
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\ConfigTarget;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Path\PathValidatorInterface;
use Drupal\Core\Routing\RequestContext;
@@ -92,10 +93,6 @@ protected function getEditableConfigNames() {
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $site_config = $this->config('system.site');
    $site_mail = $site_config->get('mail');
    if (empty($site_mail)) {
      $site_mail = ini_get('sendmail_from');
    }

    $form['site_information'] = [
      '#type' => 'details',
@@ -105,20 +102,24 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    $form['site_information']['site_name'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Site name'),
      '#default_value' => $site_config->get('name'),
      '#config_target' => 'system.site:name',
      '#required' => TRUE,
    ];
    $form['site_information']['site_slogan'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Slogan'),
      '#default_value' => $site_config->get('slogan'),
      '#config_target' => 'system.site:slogan',
      '#description' => $this->t("How this is used depends on your site's theme."),
      '#maxlength' => 255,
    ];
    $form['site_information']['site_mail'] = [
      '#type' => 'email',
      '#title' => $this->t('Email address'),
      '#default_value' => $site_mail,
      '#config_target' => new ConfigTarget(
        'system.site',
        'mail',
        fromConfig: fn($value) => $value ?: ini_get('sendmail_from'),
      ),
      '#description' => $this->t("The <em>From</em> address in automated emails sent during registration and new password requests, and other notifications. (Use an address ending in your site's domain to help prevent this email being flagged as spam.)"),
      '#required' => TRUE,
    ];
@@ -144,14 +145,14 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    $form['error_page']['site_403'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Default 403 (access denied) page'),
      '#default_value' => $site_config->get('page.403'),
      '#config_target' => 'system.site:page.403',
      '#size' => 40,
      '#description' => $this->t('This page is displayed when the requested document is denied to the current user. Leave blank to display a generic "access denied" page.'),
    ];
    $form['error_page']['site_404'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Default 404 (not found) page'),
      '#default_value' => $site_config->get('page.404'),
      '#config_target' => 'system.site:page.404',
      '#size' => 40,
      '#description' => $this->t('This page is displayed when no other content matches the requested document. Leave blank to display a generic "page not found" page.'),
    ];
@@ -203,12 +204,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $this->config('system.site')
      ->set('name', $form_state->getValue('site_name'))
      ->set('mail', $form_state->getValue('site_mail'))
      ->set('slogan', $form_state->getValue('site_slogan'))
      ->set('page.front', $form_state->getValue('site_frontpage'))
      ->set('page.403', $form_state->getValue('site_403'))
      ->set('page.404', $form_state->getValue('site_404'))
      ->save();

    parent::submitForm($form, $form_state);
Loading