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
parent
6afdada2f7
commit
1602eb3180
|
@ -2,11 +2,13 @@
|
||||||
|
|
||||||
namespace Drupal\Core\Form;
|
namespace Drupal\Core\Form;
|
||||||
|
|
||||||
|
use Drupal\Component\Utility\NestedArray;
|
||||||
use Drupal\Core\Config\Config;
|
use Drupal\Core\Config\Config;
|
||||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||||
use Drupal\Core\Config\TypedConfigManagerInterface;
|
use Drupal\Core\Config\TypedConfigManagerInterface;
|
||||||
use Drupal\Core\Render\Element;
|
use Drupal\Core\Render\Element;
|
||||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||||
|
use Drupal\Core\Url;
|
||||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,7 +88,7 @@ abstract class ConfigFormBase extends FormBase {
|
||||||
// property.
|
// property.
|
||||||
$form['#process'][] = '::loadDefaultValuesFromConfig';
|
$form['#process'][] = '::loadDefaultValuesFromConfig';
|
||||||
$form['#after_build'][] = '::storeConfigKeyToFormElementMap';
|
$form['#after_build'][] = '::storeConfigKeyToFormElementMap';
|
||||||
|
$form['#after_build'][] = '::checkConfigOverrides';
|
||||||
return $form;
|
return $form;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,4 +335,58 @@ abstract class ConfigFormBase extends FormBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
name: 'Configuration override message test'
|
||||||
|
type: module
|
||||||
|
package: Testing
|
||||||
|
version: VERSION
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?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;
|
||||||
|
}
|
|
@ -14,6 +14,18 @@ use Drupal\Tests\BrowserTestBase;
|
||||||
*/
|
*/
|
||||||
class ConfigFormOverrideTest extends BrowserTestBase {
|
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}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
@ -26,31 +38,61 @@ class ConfigFormOverrideTest extends BrowserTestBase {
|
||||||
$this->drupalLogin($this->drupalCreateUser([
|
$this->drupalLogin($this->drupalCreateUser([
|
||||||
'access administration pages',
|
'access administration pages',
|
||||||
'administer site configuration',
|
'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) [
|
||||||
// Set up an override.
|
'value' => 200,
|
||||||
$settings['config']['system.site']['name'] = (object) [
|
|
||||||
'value' => $overridden_name,
|
|
||||||
'required' => TRUE,
|
'required' => TRUE,
|
||||||
];
|
];
|
||||||
$this->writeSettings($settings);
|
$this->writeSettings($settings);
|
||||||
|
|
||||||
// Test that everything on the form is the same, but that the override
|
// Test that although system.site has an overridden key no override
|
||||||
// worked for the actual site name.
|
// information is displayed because there is no corresponding form field.
|
||||||
$this->drupalGet('admin/config/system/site-information');
|
$this->drupalGet('admin/config/system/site-information');
|
||||||
$this->assertSession()->titleEquals('Basic site settings | ' . $overridden_name);
|
|
||||||
$this->assertSession()->fieldValueEquals("site_name", 'Drupal');
|
$this->assertSession()->fieldValueEquals("site_name", 'Drupal');
|
||||||
|
$this->assertSession()->pageTextNotContains(self::OVERRIDE_TEXT);
|
||||||
|
|
||||||
// Submit the form and ensure the site name is not changed.
|
// Set up an overrides for configuration that is present in the form.
|
||||||
$edit = [
|
$overridden_name = 'Site name global conf override';
|
||||||
'site_name' => 'Custom site name',
|
$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);
|
||||||
$this->drupalGet('admin/config/system/site-information');
|
$this->drupalGet('admin/config/system/site-information');
|
||||||
$this->submitForm($edit, 'Save configuration');
|
|
||||||
$this->assertSession()->titleEquals('Basic site settings | ' . $overridden_name);
|
$this->assertSession()->titleEquals('Basic site settings | ' . $overridden_name);
|
||||||
$this->assertSession()->fieldValueEquals("site_name", $edit['site_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');
|
||||||
|
$this->submitForm([
|
||||||
|
'site_name' => 'Custom site name',
|
||||||
|
], 'Save configuration');
|
||||||
|
$this->assertSession()->titleEquals('Basic site settings | ' . $overridden_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"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace Drupal\system\Form;
|
||||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||||
use Drupal\Core\Config\TypedConfigManagerInterface;
|
use Drupal\Core\Config\TypedConfigManagerInterface;
|
||||||
use Drupal\Core\Form\ConfigFormBase;
|
use Drupal\Core\Form\ConfigFormBase;
|
||||||
|
use Drupal\Core\Form\ConfigTarget;
|
||||||
use Drupal\Core\Form\FormStateInterface;
|
use Drupal\Core\Form\FormStateInterface;
|
||||||
use Drupal\Core\Path\PathValidatorInterface;
|
use Drupal\Core\Path\PathValidatorInterface;
|
||||||
use Drupal\Core\Routing\RequestContext;
|
use Drupal\Core\Routing\RequestContext;
|
||||||
|
@ -92,10 +93,6 @@ class SiteInformationForm extends ConfigFormBase {
|
||||||
*/
|
*/
|
||||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||||
$site_config = $this->config('system.site');
|
$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'] = [
|
$form['site_information'] = [
|
||||||
'#type' => 'details',
|
'#type' => 'details',
|
||||||
|
@ -105,20 +102,24 @@ class SiteInformationForm extends ConfigFormBase {
|
||||||
$form['site_information']['site_name'] = [
|
$form['site_information']['site_name'] = [
|
||||||
'#type' => 'textfield',
|
'#type' => 'textfield',
|
||||||
'#title' => $this->t('Site name'),
|
'#title' => $this->t('Site name'),
|
||||||
'#default_value' => $site_config->get('name'),
|
'#config_target' => 'system.site:name',
|
||||||
'#required' => TRUE,
|
'#required' => TRUE,
|
||||||
];
|
];
|
||||||
$form['site_information']['site_slogan'] = [
|
$form['site_information']['site_slogan'] = [
|
||||||
'#type' => 'textfield',
|
'#type' => 'textfield',
|
||||||
'#title' => $this->t('Slogan'),
|
'#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."),
|
'#description' => $this->t("How this is used depends on your site's theme."),
|
||||||
'#maxlength' => 255,
|
'#maxlength' => 255,
|
||||||
];
|
];
|
||||||
$form['site_information']['site_mail'] = [
|
$form['site_information']['site_mail'] = [
|
||||||
'#type' => 'email',
|
'#type' => 'email',
|
||||||
'#title' => $this->t('Email address'),
|
'#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.)"),
|
'#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,
|
'#required' => TRUE,
|
||||||
];
|
];
|
||||||
|
@ -144,14 +145,14 @@ class SiteInformationForm extends ConfigFormBase {
|
||||||
$form['error_page']['site_403'] = [
|
$form['error_page']['site_403'] = [
|
||||||
'#type' => 'textfield',
|
'#type' => 'textfield',
|
||||||
'#title' => $this->t('Default 403 (access denied) page'),
|
'#title' => $this->t('Default 403 (access denied) page'),
|
||||||
'#default_value' => $site_config->get('page.403'),
|
'#config_target' => 'system.site:page.403',
|
||||||
'#size' => 40,
|
'#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.'),
|
'#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'] = [
|
$form['error_page']['site_404'] = [
|
||||||
'#type' => 'textfield',
|
'#type' => 'textfield',
|
||||||
'#title' => $this->t('Default 404 (not found) page'),
|
'#title' => $this->t('Default 404 (not found) page'),
|
||||||
'#default_value' => $site_config->get('page.404'),
|
'#config_target' => 'system.site:page.404',
|
||||||
'#size' => 40,
|
'#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.'),
|
'#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 @@ class SiteInformationForm extends ConfigFormBase {
|
||||||
*/
|
*/
|
||||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||||
$this->config('system.site')
|
$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.front', $form_state->getValue('site_frontpage'))
|
||||||
->set('page.403', $form_state->getValue('site_403'))
|
|
||||||
->set('page.404', $form_state->getValue('site_404'))
|
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
parent::submitForm($form, $form_state);
|
parent::submitForm($form, $form_state);
|
||||||
|
|
|
@ -165,7 +165,7 @@ router_test.25:
|
||||||
router_test.26:
|
router_test.26:
|
||||||
path: '/router_test/test26'
|
path: '/router_test/test26'
|
||||||
defaults:
|
defaults:
|
||||||
_form: '\Drupal\system\Form\LoggingForm'
|
_form: '\Drupal\router_test\Form'
|
||||||
_title: 'Cron'
|
_title: 'Cron'
|
||||||
requirements:
|
requirements:
|
||||||
_access: 'TRUE'
|
_access: 'TRUE'
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Drupal\router_test;
|
||||||
|
|
||||||
|
use Drupal\Core\Form\FormBase;
|
||||||
|
use Drupal\Core\Form\FormStateInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form to test _form routing.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class Form extends FormBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getFormId() {
|
||||||
|
return 'router_test_form';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function buildForm(array $form, FormStateInterface $form_state): array {
|
||||||
|
$form['submit'] = [
|
||||||
|
'#type' => 'submit',
|
||||||
|
'#value' => 'Save',
|
||||||
|
];
|
||||||
|
return $form;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function submitForm(array &$form, FormStateInterface $form_state): void {
|
||||||
|
$this->messenger()->addStatus('The router_test_form form has been submitted successfully.');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -8,9 +8,9 @@ use Drupal\Core\Entity\EntityStorageException;
|
||||||
use Drupal\Core\Form\FormState;
|
use Drupal\Core\Form\FormState;
|
||||||
use Drupal\Core\Session\AnonymousUserSession;
|
use Drupal\Core\Session\AnonymousUserSession;
|
||||||
use Drupal\entity_test\Entity\EntityTestMulRevPub;
|
use Drupal\entity_test\Entity\EntityTestMulRevPub;
|
||||||
|
use Drupal\form_test\Form\FormTestAlterForm;
|
||||||
use Drupal\KernelTests\KernelTestBase;
|
use Drupal\KernelTests\KernelTestBase;
|
||||||
use Drupal\language\Entity\ConfigurableLanguage;
|
use Drupal\language\Entity\ConfigurableLanguage;
|
||||||
use Drupal\system\Form\SiteInformationForm;
|
|
||||||
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
|
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
|
||||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||||
use Drupal\Tests\node\Traits\NodeCreationTrait;
|
use Drupal\Tests\node\Traits\NodeCreationTrait;
|
||||||
|
@ -71,6 +71,7 @@ class WorkspaceIntegrationTest extends KernelTestBase {
|
||||||
'language',
|
'language',
|
||||||
'content_translation',
|
'content_translation',
|
||||||
'path_alias',
|
'path_alias',
|
||||||
|
'form_test',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1073,7 +1074,7 @@ class WorkspaceIntegrationTest extends KernelTestBase {
|
||||||
$form_builder = $this->container->get('form_builder');
|
$form_builder = $this->container->get('form_builder');
|
||||||
|
|
||||||
$form_state = new FormState();
|
$form_state = new FormState();
|
||||||
$built_form = $form_builder->getForm(SiteInformationForm::class, $form_state);
|
$built_form = $form_builder->getForm(FormTestAlterForm::class, $form_state);
|
||||||
$form_builder->setCache($built_form['#build_id'], $built_form, $form_state);
|
$form_builder->setCache($built_form['#build_id'], $built_form, $form_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -132,7 +132,7 @@ class ExceptionHandlingTest extends KernelTestBase {
|
||||||
|
|
||||||
// Test with 404 path pointing to a route that uses '_form'.
|
// Test with 404 path pointing to a route that uses '_form'.
|
||||||
$response = $this->doTest404Route('/router_test/test26');
|
$response = $this->doTest404Route('/router_test/test26');
|
||||||
$this->assertStringContainsString('<form class="system-logging-settings"', $response->getContent());
|
$this->assertStringContainsString('<form class="router-test-form"', $response->getContent());
|
||||||
|
|
||||||
// Test with 404 path pointing to a route that uses '_entity_form'.
|
// Test with 404 path pointing to a route that uses '_entity_form'.
|
||||||
$response = $this->doTest404Route('/router_test/test27');
|
$response = $this->doTest404Route('/router_test/test27');
|
||||||
|
|
Loading…
Reference in New Issue