Skip to content
Snippets Groups Projects
Verified Commit da2eaec5 authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3278493 by mglaman, rabbitlair, mherchel, Purencool, dww, benjifisher,...

Issue #3278493 by mglaman, rabbitlair, mherchel, Purencool, dww, benjifisher, lauriii, andy-blum, AdamPS, jwilson3, alexpott, smustgrave, rlahoda, akalata, ckrina, bnjmnm, crasx, mndonx, hbrokmeier, callinmullaney: Make it easier for theme builders to enable Twig debugging and disable render cache
parent 17c7f0a1
No related branches found
No related tags found
34 merge requests!11628Update file MediaLibraryWidget.php,!7564Revert "Issue #3364773 by roshnichordiya, Chris Matthews, thakurnishant_06,...,!5752Issue #3275828 by joachim, quietone, bradjones1, Berdir: document the reason...,!5688Issue #3087950 by Utkarsh_33, swatichouhan012, komalk, Sivaji_Ganesh_Jojodae,...,!5627Issue #3261805: Field not saved when change of 0 on string start,!5427Issue #3338518: send credentials in ajax if configured in CORS settings.,!5395Issue #3387916 by fjgarlin, Spokje: Each GitLab job exposes user email,!5217Issue #3386607 by alexpott: Improve spell checking in commit-code-check.sh,!5064Issue #3379522 by finnsky, Gauravvvv, kostyashupenko, smustgrave, Chi: Revert...,!5040SDC ComponentElement: Transform slots scalar values to #plain_text instead of throwing an exception,!4958Issue #3392147: Whitelist IP for a Ban module.,!4942Issue #3365945: Errors: The following table(s) do not have a primary key: forum_index,!4894Issue #3280279: Add API to allow sites to opt in to upload SVG images in CKEditor 5,!4857Issue #3336994: StringFormatter always displays links to entity even if the user in context does not have access,!4856Issue #3336994: StringFormatter always displays links to entity even if the user in context does not have access,!4788Issue #3272985: RSS Feed header reverts to text/html when cached,!4716Issue #3362929: Improve 400 responses for broken/invalid image style routes,!4553Draft: Issue #2980951: Permission to see own unpublished comments in comment thread,!4273Add UUID to sections,!4192Issue #3367204: [CKEditor5] Missing dependency on drupal.ajax,!4158Issue #3087950 by Utkarsh_33, swatichouhan012, komalk, Sivaji_Ganesh_Jojodae,...,!4100Issue #3249600: Add support for PHP 8.1 Enums as allowed values for list_* data types,!4090Draft: Issue #3362924 by shwetaDevkate, Gauravvvv, frank8199,!3690Issue #3341682: New config schema data type: `required_label`,!3679Issue #115801: Allow password on registration without disabling e-mail verification,!3676Issue #3347497: Introduce a FetchModeTrait to allow emulating PDO fetch modes,!3629Issue #3347343: Continuation Add Views EntityReference filter to be available for all entity reference fields,!3106Issue #3017548: "Filtered HTML" text format does not support manual teaser break (<!--break-->),!3066Issue #3325175: Deprecate calling \Drupal\menu_link_content\Form\MenuLinkContentForm::_construct() with the $language_manager argument,!3004Issue #2463967: Use .user.ini file for PHP settings,!2851Issue #2264739: Allow multiple field widgets to not use tabledrag,!1484Exposed filters get values from URL when Ajax is on,!925Issue #2339235: Remove taxonomy hard dependency on node module,!872Draft: Issue #3221319: Race condition when creating menu links and editing content deletes menu links
......@@ -9,6 +9,7 @@
use Drupal\Core\DependencyInjection\Compiler\CorsCompilerPass;
use Drupal\Core\DependencyInjection\Compiler\DeprecatedServicePass;
use Drupal\Core\DependencyInjection\Compiler\ContextProvidersPass;
use Drupal\Core\DependencyInjection\Compiler\DevelopmentSettingsPass;
use Drupal\Core\DependencyInjection\Compiler\ProxyServicesPass;
use Drupal\Core\DependencyInjection\Compiler\StackedKernelPass;
use Drupal\Core\DependencyInjection\Compiler\StackedSessionHandlerPass;
......@@ -58,6 +59,8 @@ public function register(ContainerBuilder $container) {
// list-building passes are operating on the post-alter services list.
$container->addCompilerPass(new ModifyServiceDefinitionsPass());
$container->addCompilerPass(new DevelopmentSettingsPass());
$container->addCompilerPass(new ProxyServicesPass());
$container->addCompilerPass(new BackendCompilerPass());
......@@ -93,6 +96,7 @@ public function register(ContainerBuilder $container) {
$container->addCompilerPass(new PluginManagerPass());
$container->addCompilerPass(new DeprecatedServicePass());
}
/**
......
<?php
namespace Drupal\Core\DependencyInjection\Compiler;
use Drupal\Core\Cache\NullBackendFactory;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Defines a compiler pass to register development settings.
*/
class DevelopmentSettingsPass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
/** @var \Drupal\Core\State\StateInterface $state */
$state = $container->get('state');
$twig_debug = $state->get('twig_debug', FALSE);
$twig_cache_disable = $state->get('twig_cache_disable', FALSE);
if ($twig_debug || $twig_cache_disable) {
$twig_config = $container->getParameter('twig.config');
$twig_config['debug'] = $twig_debug;
$twig_config['cache'] = !$twig_cache_disable;
$container->setParameter('twig.config', $twig_config);
}
if ($state->get('disable_rendered_output_cache_bins', FALSE)) {
$cache_bins = ['page', 'dynamic_page_cache', 'render'];
if (!$container->hasDefinition('cache.backend.null')) {
$container->register('cache.backend.null', NullBackendFactory::class);
}
foreach ($cache_bins as $cache_bin) {
if ($container->has("cache.$cache_bin")) {
$container->getDefinition("cache.$cache_bin")
->clearTag('cache.bin')
->addTag('cache.bin', ['default_backend' => 'cache.backend.null']);
}
}
}
}
}
<?php
namespace Drupal\system\Form;
use Drupal\Core\DrupalKernelInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\State\StateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configure development settings for this site.
*
* @internal
*/
class DevelopmentSettingsForm extends FormBase {
/**
* Constructs a new development settings form.
*
* @param \Drupal\Core\State\StateInterface $state
* The state service.
* @param \Drupal\Core\DrupalKernelInterface $kernel
* The Drupal kernel.
*/
public function __construct(
protected StateInterface $state,
protected DrupalKernelInterface $kernel
) {
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$instance = new static(
$container->get('state'),
$container->get('kernel')
);
$instance->setMessenger($container->get('messenger'));
return $instance;
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'development_settings_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['description'] = [
'#plain_text' => $this->t('These settings should only be enabled on development environments and never on production.'),
];
$twig_debug = $this->state->get('twig_debug', FALSE);
$twig_cache_disable = $this->state->get('twig_cache_disable', FALSE);
$twig_development_state_conditions = [
'input[data-drupal-selector="edit-twig-development-mode"]' => [
'checked' => TRUE,
],
];
$form['twig_development_mode'] = [
'#type' => 'checkbox',
'#title' => $this->t('Twig development mode'),
'#description' => $this->t('Exposes Twig development settings.'),
'#default_value' => $twig_debug || $twig_cache_disable,
];
$form['twig_development'] = [
'#type' => 'fieldset',
'#title' => $this->t('Twig development mode'),
'#states' => [
'visible' => $twig_development_state_conditions,
],
];
$form['twig_development']['twig_debug'] = [
'#type' => 'checkbox',
'#title' => $this->t('Twig debug mode'),
'#description' => $this->t("Provides Twig's <code>dump()</code> function for debugging, outputs template suggestions to HTML comments, and automatically recompile Twig templates after changes."),
'#default_value' => $twig_debug,
];
$form['twig_development']['twig_cache_disable'] = [
'#type' => 'checkbox',
'#title' => $this->t('Disable Twig cache'),
'#description' => $this->t('Twig templates are not cached and are always compiled when rendered.'),
'#default_value' => $twig_cache_disable,
];
if (!$twig_debug && !$twig_cache_disable) {
$form['twig_development']['twig_debug']['#states'] = [
'checked' => $twig_development_state_conditions,
];
$form['twig_development']['twig_cache_disable']['#states'] = [
'checked' => $twig_development_state_conditions,
];
}
$form['disable_rendered_output_cache_bins'] = [
'#type' => 'checkbox',
'#title' => $this->t('Do not cache markup'),
'#description' => $this->t('Disables render cache, dynamic page cache, and page cache.'),
'#default_value' => $this->state->get('disable_rendered_output_cache_bins', FALSE),
];
$form['actions']['#type'] = 'actions';
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Save settings'),
'#button_type' => 'primary',
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$disable_rendered_output_cache_bins_previous = $this->state->get('disable_rendered_output_cache_bins', FALSE);
$disable_rendered_output_cache_bins = (bool) $form_state->getValue('disable_rendered_output_cache_bins');
if ($disable_rendered_output_cache_bins) {
$this->state->set('disable_rendered_output_cache_bins', TRUE);
}
else {
$this->state->delete('disable_rendered_output_cache_bins');
}
$twig_development_mode = (bool) $form_state->getValue('twig_development_mode');
$twig_development_previous = $this->state->getMultiple(['twig_debug', 'twig_cache_disable']);
$twig_development = [
'twig_debug' => (bool) $form_state->getValue('twig_debug'),
'twig_cache_disable' => (bool) $form_state->getValue('twig_cache_disable'),
];
if ($twig_development_mode) {
$invalidate_container = $twig_development_previous !== $twig_development;
$this->state->setMultiple($twig_development);
}
else {
$invalidate_container = TRUE;
$this->state->deleteMultiple(array_keys($twig_development));
}
if ($invalidate_container || $disable_rendered_output_cache_bins_previous !== $disable_rendered_output_cache_bins) {
$this->kernel->invalidateContainer();
}
$this->messenger()->addStatus($this->t('The settings have been saved.'));
}
}
......@@ -1532,6 +1532,37 @@ function (callable $hook, string $module) use (&$module_list, $update_registry,
}
}
// Add warning when twig debug option is enabled.
if ($phase === 'runtime') {
$twig_debug = \Drupal::state()->get('twig_debug', FALSE);
$twig_cache_disable = \Drupal::state()->get('twig_cache_disable', FALSE);
if ($twig_debug || $twig_cache_disable) {
$requirements['twig_debug_enabled'] = [
'title' => t('Twig development mode'),
'value' => t('Twig development mode settings are turned on. Go to @link to disable them.', [
'@link' => Link::createFromRoute(
'development settings page',
'system.development_settings',
)->toString(),
]),
'severity' => REQUIREMENT_WARNING,
];
}
$render_cache_disabled = \Drupal::state()->get('disable_rendered_output_cache_bins', FALSE);
if ($render_cache_disabled) {
$requirements['render_cache_disabled'] = [
'title' => t('Markup caching disabled'),
'value' => t('Render cache, dynamic page cache, and page cache are bypassed. Go to @link to enable them.', [
'@link' => Link::createFromRoute(
'development settings page',
'system.development_settings',
)->toString(),
]),
'severity' => REQUIREMENT_WARNING,
];
}
}
return $requirements;
}
......
......@@ -76,6 +76,12 @@ system.performance_settings:
description: 'Configure caching and bandwidth optimization.'
route_name: system.performance_settings
weight: -20
system.development_settings:
title: Development settings
parent: system.admin_config_development
description: 'Configure theme development settings'
route_name: system.development_settings
weight: -19
system.logging_settings:
title: 'Logging and errors'
parent: system.admin_config_development
......
......@@ -182,6 +182,14 @@ system.performance_settings:
requirements:
_permission: 'administer site configuration'
system.development_settings:
path: '/admin/config/development/settings'
defaults:
_form: '\Drupal\system\Form\DevelopmentSettingsForm'
_title: 'Development settings'
requirements:
_permission: 'administer site configuration'
system.file_system_settings:
path: '/admin/config/media/file-system'
defaults:
......
<?php
namespace Drupal\Tests\system\FunctionalJavascript\Form;
use Drupal\Core\Cache\NullBackend;
use Drupal\Core\Url;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Symfony\Component\HttpFoundation\Request;
/**
* Tests development settings form items for expected behavior.
*
* @group Form
*/
class DevelopmentSettingsFormTest extends WebDriverTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['system', 'dynamic_page_cache', 'page_cache'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$admin_user = $this->drupalCreateUser([
'administer site configuration',
]);
$this->drupalLogin($admin_user);
}
/**
* Tests turning on Twig development mode.
*
* @dataProvider twigDevelopmentData
*/
public function testTwigDevelopmentMode(bool $twig_development_mode, ?bool $twig_debug, ?bool $twig_cache_disable): void {
$twig_debug = $twig_debug ?? $twig_development_mode;
$twig_cache_disable = $twig_cache_disable ?? $twig_development_mode;
$twig_config = \Drupal::getContainer()->getParameter('twig.config');
self::assertFalse($twig_config['debug']);
self::assertNull($twig_config['auto_reload']);
self::assertTrue($twig_config['cache']);
$this->drupalGet(Url::fromRoute('system.development_settings'));
if ($twig_development_mode) {
$this->getSession()->getPage()->checkField('Twig development mode');
$this->assertSession()->checkboxChecked('Twig debug mode');
$this->assertSession()->checkboxChecked('Disable Twig cache');
}
if (!$twig_debug) {
$this->getSession()->getPage()->uncheckField('Twig debug mode');
}
if (!$twig_cache_disable) {
$this->getSession()->getPage()->uncheckField('Disable Twig cache');
}
$this->getSession()->getPage()->pressButton('Save settings');
$this->drupalGet(Url::fromRoute('system.status'));
if (!$twig_development_mode) {
$this->assertSession()->pageTextNotContains('Twig development mode');
}
else {
$this->assertSession()->pageTextContains('Twig development mode');
$this->assertSession()->linkExists('development settings page');
}
$refreshed_container = $this->initKernel(Request::create('/'));
$twig_config = $refreshed_container->getParameter('twig.config');
self::assertEquals($twig_debug, $twig_config['debug']);
self::assertNull($twig_config['auto_reload']);
self::assertEquals(!$twig_cache_disable, $twig_config['cache']);
}
/**
* Test data for Twig development mode.
*
* @return array[]
*/
public static function twigDevelopmentData(): array {
return [
'Twig development mode checked only' => [
TRUE,
NULL,
NULL,
],
'Twig debug mode only, keep Twig cache' => [
TRUE,
TRUE,
FALSE,
],
'Twig debug mode off, disable Twig cache' => [
TRUE,
FALSE,
TRUE,
],
'No changes' => [
FALSE,
NULL,
NULL,
],
];
}
/**
* Tests disabling cache bins which cache markup.
*/
public function testDisabledRenderedOutputCacheBins(): void {
self::assertFalse(\Drupal::getContainer()->has('cache.backend.null'));
$this->drupalGet(Url::fromRoute('system.development_settings'));
$this->getSession()->getPage()->checkField('Do not cache markup');
$this->getSession()->getPage()->pressButton('Save settings');
$this->drupalGet(Url::fromRoute('system.status'));
$this->assertSession()->pageTextContains('Markup caching disabled');
$this->assertSession()->linkExists('development settings page');
$refreshed_container = $this->initKernel(Request::create('/'));
self::assertTrue($refreshed_container->has('cache.backend.null'));
$cache_bins = ['page', 'dynamic_page_cache', 'render'];
foreach ($cache_bins as $cache_bin) {
self::assertInstanceOf(NullBackend::class, $refreshed_container->get("cache.$cache_bin"));
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment