Skip to content
Snippets Groups Projects
Verified Commit badd417e authored by Lauri Timmanee's avatar Lauri Timmanee
Browse files

Issue #2245767 by John Pitcairn, andrewmacpherson, danflanagan8, leymannx,...

Issue #2245767 by John Pitcairn, andrewmacpherson, danflanagan8, leymannx, tim.plunkett, mpolishchuck, tobiasb, olli, dawehner, Anas_maw, SpadXIII, _utsavsharma, benjifisher, ankithashetty, jidrone, SimeonKesmev, Daniel Korte, ameymudras, jyotimishra-developer, alexpott, smustgrave, geek-merlin, catch, DuaelFr, Gábor Hojtsy, lauriii, ckrina, quietone: Allow blocks to be configured to show/hide on 200/403/404 response pages
parent 4bfd55f3
Branches
Tags
45 merge requests!12227Issue #3181946 by jonmcl, mglaman,!54479.5.x SF update,!5014Issue #3071143: Table Render Array Example Is Incorrect,!4868Issue #1428520: Improve menu parent link selection,!4594Applying patch for Views Global Text area field to allow extra HTML tags. As video, source and iframe tag is not rendering. Due to which Media embedded video and remote-video not rendering in Views Global Text area field.,!3878Removed unused condition head title for views,!38582585169-10.1.x,!3818Issue #2140179: $entity->original gets stale between updates,!3742Issue #3328429: Create item list field formatter for displaying ordered and unordered lists,!3731Claro: role=button on status report items,!3668Resolve #3347842 "Deprecate the trusted",!3651Issue #3347736: Create new SDC component for Olivero (header-search),!3546refactored dialog.pcss file,!3531Issue #3336994: StringFormatter always displays links to entity even if the user in context does not have access,!3502Issue #3335308: Confusing behavior with FormState::setFormState and FormState::setMethod,!3478Issue #3337882: Deleted menus are not removed from content type config,!3452Issue #3332701: Refactor Claro's tablesort-indicator stylesheet,!3451Issue #2410579: Allows setting the current language programmatically.,!3355Issue #3209129: Scrolling problems when adding a block via layout builder,!3226Issue #2987537: Custom menu link entity type should not declare "bundle" entity key,!3154Fixes #2987987 - CSRF token validation broken on routes with optional parameters.,!3147Issue #3328457: Replace most substr($a, $i) where $i is negative with str_ends_with(),!3146Issue #3328456: Replace substr($a, 0, $i) with str_starts_with(),!3133core/modules/system/css/components/hidden.module.css,!31312878513-10.1.x,!2964Issue #2865710 : Dependencies from only one instance of a widget are used in display modes,!2812Issue #3312049: [Followup] Fix Drupal.Commenting.FunctionComment.MissingReturnType returns for NULL,!2614Issue #2981326: Replace non-test usages of \Drupal::logger() with IoC injection,!2378Issue #2875033: Optimize joins and table selection in SQL entity query implementation,!2334Issue #3228209: Add hasRole() method to AccountInterface,!2062Issue #3246454: Add weekly granularity to views date sort,!1591Issue #3199697: Add JSON:API Translation experimental module,!1255Issue #3238922: Refactor (if feasible) uses of the jQuery serialize function to use vanillaJS,!1105Issue #3025039: New non translatable field on translatable content throws error,!1073issue #3191727: Focus states on mobile second level navigation items fixed,!10223132456: Fix issue where views instances are emptied before an ajax request is complete,!877Issue #2708101: Default value for link text is not saved,!844Resolve #3036010 "Updaters",!673Issue #3214208: FinishResponseSubscriber could create duplicate headers,!579Issue #2230909: Simple decimals fail to pass validation,!560Move callback classRemove outside of the loop,!555Issue #3202493,!485Sets the autocomplete attribute for username/password input field on login form.,!213Issue #2906496: Give Media a menu item under Content,!30Issue #3182188: Updates composer usage to point at ./vendor/bin/composer
......@@ -46,7 +46,7 @@
}
$(
'[data-drupal-selector="edit-visibility-node-type"], [data-drupal-selector="edit-visibility-entity-bundlenode"], [data-drupal-selector="edit-visibility-language"], [data-drupal-selector="edit-visibility-user-role"]',
'[data-drupal-selector="edit-visibility-node-type"], [data-drupal-selector="edit-visibility-entity-bundlenode"], [data-drupal-selector="edit-visibility-language"], [data-drupal-selector="edit-visibility-user-role"], [data-drupal-selector="edit-visibility-response-status"]',
).drupalSetSummary(checkboxesSummary);
$(
......
......@@ -247,16 +247,23 @@ protected function buildVisibilityInterface(array $form, FormStateInterface $for
$form[$condition_id] = $condition_form;
}
if (isset($form['entity_bundle:node'])) {
$form['entity_bundle:node']['negate']['#type'] = 'value';
$form['entity_bundle:node']['negate']['#title_display'] = 'invisible';
$form['entity_bundle:node']['negate']['#value'] = $form['entity_bundle:node']['negate']['#default_value'];
// Disable negation for specific conditions.
$disable_negation = [
'entity_bundle:node',
'language',
'response_status',
'user_role',
];
foreach ($disable_negation as $condition) {
if (isset($form[$condition])) {
$form[$condition]['negate']['#type'] = 'value';
$form[$condition]['negate']['#value'] = $form[$condition]['negate']['#default_value'];
}
}
if (isset($form['user_role'])) {
$form['user_role']['#title'] = $this->t('Roles');
unset($form['user_role']['roles']['#description']);
$form['user_role']['negate']['#type'] = 'value';
$form['user_role']['negate']['#value'] = $form['user_role']['negate']['#default_value'];
}
if (isset($form['request_path'])) {
$form['request_path']['#title'] = $this->t('Pages');
......@@ -268,10 +275,6 @@ protected function buildVisibilityInterface(array $form, FormStateInterface $for
$this->t('Hide for the listed pages'),
];
}
if (isset($form['language'])) {
$form['language']['negate']['#type'] = 'value';
$form['language']['negate']['#value'] = $form['language']['negate']['#default_value'];
}
return $form;
}
......
......@@ -35,11 +35,13 @@ public function testBlockVisibility() {
'settings[label]' => $title,
'settings[label_display]' => TRUE,
];
// Set the block to be hidden on any user path, and to be shown only to
// authenticated users.
// Set the block to be hidden on any user path, to be shown only to
// authenticated users, and to be shown only on 200 and 404 responses.
$edit['visibility[request_path][pages]'] = '/user*';
$edit['visibility[request_path][negate]'] = TRUE;
$edit['visibility[user_role][roles][' . RoleInterface::AUTHENTICATED_ID . ']'] = TRUE;
$edit['visibility[response_status][status_codes][200]'] = 200;
$edit['visibility[response_status][status_codes][404]'] = 404;
$this->drupalGet('admin/structure/block/add/' . $block_name . '/' . $default_theme);
$this->assertSession()->checkboxChecked('edit-visibility-request-path-negate-0');
......@@ -48,16 +50,26 @@ public function testBlockVisibility() {
$this->clickLink('Configure');
$this->assertSession()->checkboxChecked('edit-visibility-request-path-negate-1');
$this->assertSession()->checkboxChecked('edit-visibility-response-status-status-codes-200');
$this->assertSession()->checkboxChecked('edit-visibility-response-status-status-codes-404');
// Confirm that the block is displayed on the front page.
// Confirm that the block is displayed on the front page (200 response).
$this->drupalGet('');
$this->assertSession()->pageTextContains($title);
// Confirm that the block is not displayed according to block visibility
// Confirm that the block is not displayed according to path visibility
// rules.
$this->drupalGet('user');
$this->assertSession()->pageTextNotContains($title);
// Confirm that the block is displayed on a 404 response.
$this->drupalGet('/0/null');
$this->assertSession()->pageTextContains($title);
// Confirm that the block is not displayed on a 403 response.
$this->drupalGet('/admin/config/system/cron');
$this->assertSession()->pageTextNotContains($title);
// Confirm that the block is not displayed to anonymous users.
$this->drupalLogout();
$this->drupalGet('');
......
......@@ -357,6 +357,14 @@ condition.plugin.request_path:
pages:
type: string
condition.plugin.response_status:
type: condition.plugin
mapping:
status_codes:
type: sequence
sequence:
type: integer
system.feature_flags:
type: config_object
label: 'System Feature Flags'
......
<?php
declare(strict_types=1);
namespace Drupal\system\Plugin\Condition;
use Drupal\Core\Condition\ConditionPluginBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
/**
* Provides a 'Response status' condition.
*
* @Condition(
* id = "response_status",
* label = @Translation("Response status"),
* )
*/
class ResponseStatus extends ConditionPluginBase implements ContainerFactoryPluginInterface {
/**
* The request stack.
*/
protected RequestStack $requestStack;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
$instance = new static($configuration, $plugin_id, $plugin_definition);
$instance->setRequestStack($container->get('request_stack'));
return $instance;
}
public function setRequestStack(RequestStack $requestStack): void {
$this->requestStack = $requestStack;
}
/**
* {@inheritdoc}
*/
public function isNegated(): bool {
return FALSE;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration(): array {
return ['status_codes' => []] + parent::defaultConfiguration();
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state): array {
$status_codes = [
Response::HTTP_OK => $this->t('Success (@status_code)', ['@status_code' => Response::HTTP_OK]),
Response::HTTP_FORBIDDEN => $this->t('Access denied (@status_code)', ['@status_code' => Response::HTTP_FORBIDDEN]),
Response::HTTP_NOT_FOUND => $this->t('Page not found (@status_code)', ['@status_code' => Response::HTTP_NOT_FOUND]),
];
$form['status_codes'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Response status'),
'#options' => $status_codes,
'#default_value' => $this->configuration['status_codes'],
'#description' => $this->t('Shows the block on pages with any matching response status. If nothing is checked, the block is shown on all pages. Other response statuses are not used.'),
];
return parent::buildConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state): void {
$this->configuration['status_codes'] = array_keys(array_filter($form_state->getValue('status_codes')));
parent::submitConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function summary(): PluralTranslatableMarkup {
$allowed_codes = $this->configuration['status_codes'];
$status_codes = [Response::HTTP_OK, Response::HTTP_FORBIDDEN, Response::HTTP_NOT_FOUND];
$result = empty($allowed_codes) ? $status_codes : $allowed_codes;
$count = count($result);
$codes = implode(', ', $result);
if (!empty($this->configuration['negate'])) {
return $this->formatPlural($count, 'Request response code is not: @codes', 'Request response code is not one of the following: @codes', ['@codes' => $codes]);
}
return $this->formatPlural($count, 'Request response code is: @codes', 'Request response code is one of the following: @codes', ['@codes' => $codes]);
}
/**
* {@inheritdoc}
*/
public function evaluate(): bool {
$allowed_codes = $this->configuration['status_codes'];
if (empty($allowed_codes)) {
return TRUE;
}
$exception = $this->requestStack->getCurrentRequest()->attributes->get('exception');
if ($exception) {
return ($exception instanceof HttpExceptionInterface && in_array($exception->getStatusCode(), $allowed_codes, TRUE));
}
return in_array(Response::HTTP_OK, $allowed_codes, TRUE);
}
/**
* {@inheritdoc}
*/
public function getCacheContexts(): array {
$contexts = parent::getCacheContexts();
$contexts[] = 'url.path';
return $contexts;
}
}
<?php
declare(strict_types=1);
namespace Drupal\KernelTests\Core\Plugin\Condition;
use Drupal\Core\Condition\ConditionManager;
use Drupal\KernelTests\KernelTestBase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
/**
* Tests the Response Status Condition, provided by the system module.
*
* @group Plugin
*/
class ResponseStatusTest extends KernelTestBase {
/**
* The condition plugin manager under test.
*/
protected ConditionManager $pluginManager;
/**
* The request stack used for testing.
*/
protected RequestStack $requestStack;
/**
* {@inheritdoc}
*/
protected static $modules = ['system', 'user'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installConfig('system');
$this->pluginManager = $this->container->get('plugin.manager.condition');
// Set the test request stack in the container.
$this->requestStack = new RequestStack();
$this->container->set('request_stack', $this->requestStack);
}
/**
* Tests the request path condition.
*
* @dataProvider providerTestConditions
*/
public function testConditions(array $status_codes, bool $negate, int $response_code, bool $expected_execute) {
if ($response_code === Response::HTTP_OK) {
$request = Request::create('/my/valid/page');
}
else {
$request = new Request();
$request->attributes->set('exception', new HttpException($response_code));
}
$this->requestStack->push($request);
/** @var \Drupal\system\Plugin\Condition\ResponseStatus $condition */
$condition = $this->pluginManager->createInstance('response_status');
$condition->setConfig('status_codes', $status_codes);
$condition->setConfig('negate', $negate);
$this->assertSame($expected_execute, $condition->execute());
}
/**
* Provides test data for testConditions.
*/
public function providerTestConditions() {
// Default values with 200 response code.
yield [
'status_codes' => [],
'negate' => FALSE,
'response_code' => Response::HTTP_OK,
'expected_execute' => TRUE,
];
// Default values with 403 response code.
yield [
'status_codes' => [],
'negate' => FALSE,
'response_code' => Response::HTTP_FORBIDDEN,
'expected_execute' => TRUE,
];
// Default values with 404 response code.
yield [
'status_codes' => [],
'negate' => FALSE,
'response_code' => Response::HTTP_NOT_FOUND,
'expected_execute' => TRUE,
];
// 200 status code enabled with 200 response code.
yield [
'status_codes' => [Response::HTTP_OK => Response::HTTP_OK],
'negate' => FALSE,
'response_code' => Response::HTTP_OK,
'expected_execute' => TRUE,
];
// 200 status code enabled with 403 response code.
yield [
'status_codes' => [Response::HTTP_OK => Response::HTTP_OK],
'negate' => FALSE,
'response_code' => Response::HTTP_FORBIDDEN,
'expected_execute' => FALSE,
];
// 200 status code enabled with 404 response code.
yield [
'status_codes' => [Response::HTTP_OK => Response::HTTP_OK],
'negate' => FALSE,
'response_code' => Response::HTTP_NOT_FOUND,
'expected_execute' => FALSE,
];
// 403 status code enabled with 200 response code.
yield [
'status_codes' => [Response::HTTP_FORBIDDEN => Response::HTTP_FORBIDDEN],
'negate' => FALSE,
'response_code' => Response::HTTP_OK,
'expected_execute' => FALSE,
];
// 403 status code enabled with 403 response code.
yield [
'status_codes' => [Response::HTTP_FORBIDDEN => Response::HTTP_FORBIDDEN],
'negate' => FALSE,
'response_code' => Response::HTTP_FORBIDDEN,
'expected_execute' => TRUE,
];
// 403 status code enabled with 404 response code.
yield [
'status_codes' => [Response::HTTP_FORBIDDEN => Response::HTTP_FORBIDDEN],
'negate' => FALSE,
'response_code' => Response::HTTP_NOT_FOUND,
'expected_execute' => FALSE,
];
// 200,403 status code enabled with 200 response code.
yield [
'status_codes' => [
Response::HTTP_OK => Response::HTTP_OK,
Response::HTTP_FORBIDDEN => Response::HTTP_FORBIDDEN,
],
'negate' => FALSE,
'response_code' => Response::HTTP_OK,
'expected_execute' => TRUE,
];
// 200,403 status code enabled with 403 response code.
yield [
'status_codes' => [
Response::HTTP_OK => Response::HTTP_OK,
Response::HTTP_FORBIDDEN => Response::HTTP_FORBIDDEN,
],
'negate' => FALSE,
'response_code' => Response::HTTP_FORBIDDEN,
'expected_execute' => TRUE,
];
// 200,403 status code enabled with 404 response code.
yield [
'status_codes' => [
Response::HTTP_OK => Response::HTTP_OK,
Response::HTTP_FORBIDDEN => Response::HTTP_FORBIDDEN,
],
'negate' => FALSE,
'response_code' => Response::HTTP_NOT_FOUND,
'expected_execute' => FALSE,
];
// 200,404 status code enabled with 200 response code.
yield [
'status_codes' => [
Response::HTTP_OK => Response::HTTP_OK,
Response::HTTP_NOT_FOUND => Response::HTTP_NOT_FOUND,
],
'negate' => FALSE,
'response_code' => Response::HTTP_OK,
'expected_execute' => TRUE,
];
// 200,404 status code enabled with 403 response code.
yield [
'status_codes' => [
Response::HTTP_OK => Response::HTTP_OK,
Response::HTTP_NOT_FOUND => Response::HTTP_NOT_FOUND,
],
'negate' => FALSE,
'response_code' => Response::HTTP_FORBIDDEN,
'expected_execute' => FALSE,
];
// 200,404 status code enabled with 404 response code.
yield [
'status_codes' => [
Response::HTTP_OK => Response::HTTP_OK,
Response::HTTP_NOT_FOUND => Response::HTTP_NOT_FOUND,
],
'negate' => FALSE,
'response_code' => Response::HTTP_NOT_FOUND,
'expected_execute' => TRUE,
];
// 403,404 status code enabled with 200 response code.
yield [
'status_codes' => [
Response::HTTP_FORBIDDEN => Response::HTTP_FORBIDDEN,
Response::HTTP_NOT_FOUND => Response::HTTP_NOT_FOUND,
],
'negate' => FALSE,
'response_code' => Response::HTTP_OK,
'expected_execute' => FALSE,
];
// 403,404 status code enabled with 403 response code.
yield [
'status_codes' => [
Response::HTTP_FORBIDDEN => Response::HTTP_FORBIDDEN,
Response::HTTP_NOT_FOUND => Response::HTTP_NOT_FOUND,
],
'negate' => FALSE,
'response_code' => Response::HTTP_FORBIDDEN,
'expected_execute' => TRUE,
];
// 403,404 status code enabled with 404 response code.
yield [
'status_codes' => [
Response::HTTP_FORBIDDEN => Response::HTTP_FORBIDDEN,
Response::HTTP_NOT_FOUND => Response::HTTP_NOT_FOUND,
],
'negate' => FALSE,
'response_code' => Response::HTTP_NOT_FOUND,
'expected_execute' => TRUE,
];
// 200,403,404 status code enabled with 200 response code.
yield [
'status_codes' => [
Response::HTTP_OK => Response::HTTP_OK,
Response::HTTP_FORBIDDEN => Response::HTTP_FORBIDDEN,
Response::HTTP_NOT_FOUND => Response::HTTP_NOT_FOUND,
],
'negate' => FALSE,
'response_code' => Response::HTTP_OK,
'expected_execute' => TRUE,
];
// 200,403 status code enabled with 403 response code.
yield [
'status_codes' => [
Response::HTTP_OK => Response::HTTP_OK,
Response::HTTP_FORBIDDEN => Response::HTTP_FORBIDDEN,
Response::HTTP_NOT_FOUND => Response::HTTP_NOT_FOUND,
],
'negate' => FALSE,
'response_code' => Response::HTTP_FORBIDDEN,
'expected_execute' => TRUE,
];
// 200,403 status code enabled with 404 response code.
yield [
'status_codes' => [
Response::HTTP_OK => Response::HTTP_OK,
Response::HTTP_FORBIDDEN => Response::HTTP_FORBIDDEN,
Response::HTTP_NOT_FOUND => Response::HTTP_NOT_FOUND,
],
'negate' => FALSE,
'response_code' => Response::HTTP_NOT_FOUND,
'expected_execute' => TRUE,
];
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment