Problem/Motivation

While working on a D8 port of a contrib using the request_path condition plugin, I observed the following behavior. I then repeated this bug using the block config form.

Steps to reproduce

  1. Configure a block's page visibility settings.
  2. Enter a path, such as <front>.
  3. Select the negate option "Show for the listed pages".
  4. Save the form.
  5. Click to configure the same block and view the pages visibility settings.
  6. Observe that the radio option, "Show for the listed pages" is not selected.

Expected behavior

I'd expect that the selected radio option will appear after saving the config form.

Why and how

\Drupal\block\BlockForm.php uses the request_path condition plugin and subsequently the ConditionPluginBase::buildConfigurationForm to build it's negate form element, which is a checkbox.

BlockForm.php changes this checkbox to a radio form element with an options index array with two elements, false/0 and true/1.

However, during form submission, the negate value is saved as FALSE. During form rendering, the logic, $element['#value'] !== FALSE, in public static function Radio::preRenderRadio, prevents the false radio input, "Show for the listed pages", from being checked. Note, this logic was added https://www.drupal.org/node/971120#comment-3706168.

Tangential Issues

However, this scenario is straightforward. I can see user confusion if the pages form element is left empty ( no paths are added ) and the radio option "Show for the listed pages" is selected. While this radio option translates to FALSE negate condition as opposed to interpreting that this block will "Show for the listed pages", which there are none, so I deduce that this block will NOT show any pages.

Proposed resolution

  1. Consider whether or not the introduction of this logic, $element['#value'] !== FALSE in the radio renderer is source of bug.
  2. If it is, remove it
  3. If it's not, use ternary to switch the negate's default value form element from boolean to integer during buildVisibilityInterface method so that the radio renderer will check the appropriate radio input.
  4. Add #states to the negate form element to show/hide when pages form element is not empty. *Likely needs a separate issue created. See #2451461: Hide/show block page visibility negate form element if pages form element is/is not empty

Remaining tasks

  1. Build consensus regarding source of bug.
  2. Identify source of bug.
  3. Identify solution to bug.

User interface changes

  1. Check the "Show for the listed pages" radio input if False.
  2. Possibly show/hide block's page visibility negate option as condition if paths are provided or not. See #2451461: Hide/show block page visibility negate form element if pages form element is/is not empty

API changes

Not sure

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

mrjmd’s picture

Version: 8.0.0-beta7 » 8.0.x-dev

I can confirm this behavior is happening in the latest 8.0.x.

A quick test does show that the change noted in the issue summary does appear to solve the problem, but I'm not familiar enough with D8 FAPI so say whether it would have other side-effects.

Here's what I did to test the solution, after following the steps to reproduce above:

--- a/core/lib/Drupal/Core/Render/Element/Radio.php
+++ b/core/lib/Drupal/Core/Render/Element/Radio.php
@@ -57,7 +57,7 @@ public static function preRenderRadio($element) {
     $element['#attributes']['type'] = 'radio';
     Element::setAttributes($element, array('id', 'name', '#return_value' => 'value'));
 
-    if (isset($element['#return_value']) && $element['#value'] !== FALSE && $element['#value'] == $element['#return_value']) {
+    if (isset($element['#return_value']) && $element['#value'] == $element['#return_value']) {
       $element['#attributes']['checked'] = 'checked';
     }
     static::setAttributes($element, array('form-radio'));
jasonawant’s picture

Issue summary: View changes
Status: Active » Needs review
FileSize
792 bytes

Hello,

Here's a patch that removes the offending logic from the preRenderRadio method. Feedback welcomed. Thanks, Jason.

Status: Needs review » Needs work

The last submitted patch, 2: drupal-render-block-negate-option-checked-2450637-2.patch, failed testing.

jasonawant’s picture

Issue summary: View changes

Hello,

I've created a feature request for the hide/show functionality mentioned in the issue summary. See it here > https://www.drupal.org/node/2451461

jasonawant’s picture

Hello,

It looks like I'll need to learn how to execute tests locally to figure this out.

I suppose if this is the right approach, we'll need to update/modify the tests appropriately, is that right?

1) BlockTest fails because of $this->assertIdentical([], $block->get('visibility'));. Do we update the the first parameter with something resembling the mixed property return by $block->get('visibility'), something akin to array ( 'request_path' => array ( 'id' => 'request_path', 'pages' => '', 'negate' => false, 'context_mapping' => array ( ), ), ).

2) Not sure what a path forward would be here yet. I need to run these tests locally to know what's going on here.

Wim Leers’s picture

Issue summary: View changes

Fixing the STR HTML.

Wim Leers’s picture

Priority: Normal » Major
Issue summary: View changes
FileSize
26.1 KB

It's even worse than the IS suggests: no radio button is selected initially:

(This is when configuring the "Search form" block after a fresh install.)

Seems like this problem goes back to the way condition plugins (see #2339151: Conditions / context system does not allow for multiple configurable contexts, eg. language types, where this was introduced IIRC). The problem doesn't lie in \Drupal\Core\Render\Element\Radio, but in how condition plugins map data from Block config entities to the form.

Pinging @EclipseGc and @TimPlunkett to help.

Wim Leers’s picture

tim.plunkett’s picture

Status: Needs work » Needs review
Issue tags: +Needs tests
FileSize
758 bytes

\Drupal\Core\Condition\ConditionPluginBase::buildConfigurationForm uses ['#type' => 'checkbox'] for the negate portion of the form.

For UX reasons, \Drupal\block\BlockForm::buildVisibilityInterface changes the type to radios, and doesn't cast the value.
We used to cast the values, it was probably removed in that commit as well.

Wim Leers’s picture

@tim.plunkett++

tim.plunkett’s picture

Yep, sure enough.

This was removed in #2339151: Conditions / context system does not allow for multiple configurable contexts, eg. language types:

-    if (isset($form['visibility']['request_path'])) {
-      $form['visibility']['request_path']['#title'] = $this->t('Pages');
-      $form['visibility']['request_path']['negate']['#type'] = 'radios';
-      $form['visibility']['request_path']['negate']['#title_display'] = 'invisible';
-      $form['visibility']['request_path']['negate']['#default_value'] = (int) $form['visibility']['request_path']['negate']['#default_value'];
-      $form['visibility']['request_path']['negate']['#options'] = array(
-        $this->t('Show for the listed pages'),
-        $this->t('Hide for the listed pages'),
-      );
-    }

and replaced with

+    if (isset($form['request_path'])) {
+      $form['request_path']['#title'] = $this->t('Pages');
+      $form['request_path']['negate']['#type'] = 'radios';
+      $form['request_path']['negate']['#title_display'] = 'invisible';
+      $form['request_path']['negate']['#options'] = [
+        $this->t('Show for the listed pages'),
+        $this->t('Hide for the listed pages'),
+      ];
+    }

Turns out that cast *was* important!

However, this was just a confusing UI problem, the values were saved. So this can probably be downgraded.

The last submitted patch, 11: block_page_visibility-2450637-11-PASS.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 11: block_page_visibility-2450637-11-FAIL.patch, failed testing.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
2.62 KB
999 bytes

Yay for having to work around FAPI.
We need this because \Drupal\Core\Condition\ConditionPluginCollection::getConfiguration() will compare 0 and FALSE strictly.

jasonawant’s picture

Status: Needs review » Reviewed & tested by the community

Hi,

I applied the patch in 14 and tested. Everything looks good. I followed the new logic in BlockTest.php as follows.

$this->assertFieldChecked('edit-visibility-request-path-negate-0'); ensures the the negate option defaults to false when initially viewing the config form.

After saving the test block with $edit['visibility[request_path][negate]'] = TRUE;, the tests check to ensure the the negate option is TRUE with $this->assertFieldChecked('edit-visibility-request-path-negate-1');.

All looks good to me, thanks! Jason.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

This issue addresses a major bug and is allowed per https://www.drupal.org/core/beta-changes. Committed 2eee259 and pushed to 8.0.x. Thanks!

  • alexpott committed 2eee259 on 8.0.x
    Issue #2450637 by tim.plunkett, jasonawant: Block page visibility option...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.