From 829f1685e8d57058995a6ff5425572eed917ca10 Mon Sep 17 00:00:00 2001
From: Nathaniel Catchpole <catch@35733.no-reply.drupal.org>
Date: Fri, 11 Dec 2015 22:42:36 +0000
Subject: [PATCH] Issue #2595169 by mikeker, claudiu.cristea: Operator 'Is not
 equal' of BooleanOperator doesn't work

---
 .../Plugin/views/filter/BooleanOperator.php   | 53 +++++++++++++++----
 .../Handler/FilterBooleanOperatorTest.php     | 49 +++++++++++++++++
 2 files changed, 92 insertions(+), 10 deletions(-)

diff --git a/core/modules/views/src/Plugin/views/filter/BooleanOperator.php b/core/modules/views/src/Plugin/views/filter/BooleanOperator.php
index e936ac9ddbfc..a4cded4acd68 100644
--- a/core/modules/views/src/Plugin/views/filter/BooleanOperator.php
+++ b/core/modules/views/src/Plugin/views/filter/BooleanOperator.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\views\Plugin\views\filter;
 
+use Drupal\Core\Database\Query\Condition;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\ViewExecutable;
@@ -31,6 +32,20 @@
  */
 class BooleanOperator extends FilterPluginBase {
 
+  /**
+   * The equal query operator.
+   *
+   * @var string
+   */
+  const EQUAL = '=';
+
+  /**
+   * The non equal query operator.
+   *
+   * @var string
+   */
+  const NOT_EQUAL = '<>';
+
   // exposed filter options
   protected $alwaysMultiple = TRUE;
   // Don't display empty space where the operator would be.
@@ -64,12 +79,14 @@ protected function operators() {
         'method' => 'queryOpBoolean',
         'short' => $this->t('='),
         'values' => 1,
+        'query_operator' => static::EQUAL,
       ),
       '!=' => array(
         'title' => $this->t('Is not equal to'),
         'method' => 'queryOpBoolean',
         'short' => $this->t('!='),
         'values' => 1,
+        'query_operator' => static::NOT_EQUAL,
       ),
     );
   }
@@ -185,7 +202,7 @@ public function adminSummary() {
     // human-readable label based on the current value.  The valueOptions
     // array is keyed with either 0 or 1, so if the current value is not
     // empty, use the label for 1, and if it's empty, use the label for 0.
-    return $this->valueOptions[!empty($this->value)];
+    return $this->operator . ' ' . $this->valueOptions[!empty($this->value)];
   }
 
   public function defaultExposeOptions() {
@@ -204,7 +221,7 @@ public function query() {
 
     $info = $this->operators();
     if (!empty($info[$this->operator]['method'])) {
-      call_user_func(array($this, $info[$this->operator]['method']), $field);
+      call_user_func(array($this, $info[$this->operator]['method']), $field, $info[$this->operator]['query_operator']);
     }
   }
 
@@ -213,25 +230,41 @@ public function query() {
    *
    * @param string $field
    *   The field name to add the where condition for.
+   * @param string $query_operator
+   *   (optional) Either static::EQUAL or static::NOT_EQUAL. Defaults to
+   *   static::EQUAL.
    */
-  protected function queryOpBoolean($field) {
+  protected function queryOpBoolean($field, $query_operator = EQUAL) {
     if (empty($this->value)) {
       if ($this->accept_null) {
-        $or = db_or()
-          ->condition($field, 0, '=')
-          ->condition($field, NULL, 'IS NULL');
-        $this->query->addWhere($this->options['group'], $or);
+        if ($query_operator == static::EQUAL) {
+          $condition = (new Condition('OR'))
+            ->condition($field, 0, $query_operator)
+            ->isNull($field);
+        }
+        else {
+          $condition = (new Condition('AND'))
+            ->condition($field, 0, $query_operator)
+            ->isNotNull($field);
+        }
+        $this->query->addWhere($this->options['group'], $condition);
       }
       else {
-        $this->query->addWhere($this->options['group'], $field, 0, '=');
+        $this->query->addWhere($this->options['group'], $field, 0, $query_operator);
       }
     }
     else {
       if (!empty($this->definition['use_equal'])) {
-        $this->query->addWhere($this->options['group'], $field, 1, '=');
+        // Forces an '=' operator instead of a '<>' for performance reasons.
+        if ($query_operator == static::EQUAL) {
+          $this->query->addWhere($this->options['group'], $field, 1, static::EQUAL);
+        }
+        else {
+          $this->query->addWhere($this->options['group'], $field, 0, static::EQUAL);
+        }
       }
       else {
-        $this->query->addWhere($this->options['group'], $field, 0, '<>');
+        $this->query->addWhere($this->options['group'], $field, 1, $query_operator);
       }
     }
   }
diff --git a/core/modules/views/src/Tests/Handler/FilterBooleanOperatorTest.php b/core/modules/views/src/Tests/Handler/FilterBooleanOperatorTest.php
index 049d439dbe8f..7e6c3104a3a3 100644
--- a/core/modules/views/src/Tests/Handler/FilterBooleanOperatorTest.php
+++ b/core/modules/views/src/Tests/Handler/FilterBooleanOperatorTest.php
@@ -95,6 +95,30 @@ public function testFilterBooleanOperator() {
 
     $this->assertEqual(3, count($view->result));
     $this->assertIdenticalResultset($view, $expected_result, $this->columnMap);
+
+    $view->destroy();
+    $view->setDisplay();
+
+    // Testing the same scenario but using the reverse status and operation.
+    $view->displayHandlers->get('default')->overrideOption('filters', array(
+      'status' => array(
+        'id' => 'status',
+        'field' => 'status',
+        'table' => 'views_test_data',
+        'value' => 0,
+        'operator' => '!=',
+      ),
+    ));
+    $this->executeView($view);
+
+    $expected_result = array(
+      array('id' => 1),
+      array('id' => 3),
+      array('id' => 5),
+    );
+
+    $this->assertEqual(3, count($view->result));
+    $this->assertIdenticalResultset($view, $expected_result, $this->columnMap);
   }
 
   /**
@@ -133,6 +157,24 @@ public function testFilterGroupedExposed() {
 
     $this->assertEqual(2, count($view->result));
     $this->assertIdenticalResultset($view, $expected_result, $this->columnMap);
+
+    $view->destroy();
+
+    // Expecting the same results as for ['status' => 1].
+    $view->setExposedInput(['status' => 3]);
+    $view->setDisplay();
+    $view->displayHandlers->get('default')->overrideOption('filters', $filters);
+
+    $this->executeView($view);
+
+    $expected_result = array(
+      array('id' => 1),
+      array('id' => 3),
+      array('id' => 5),
+    );
+
+    $this->assertEqual(3, count($view->result));
+    $this->assertIdenticalResultset($view, $expected_result, $this->columnMap);
   }
 
   /**
@@ -169,6 +211,13 @@ protected function getGroupedExposedFilters() {
               'operator' => '=',
               'value' => '0',
             ),
+            // This group should return the same results as group 1, because it
+            // is the negation of group 2.
+            3 => array(
+              'title' => 'Active (reverse)',
+              'operator' => '!=',
+              'value' => '0',
+            ),
           ),
         ),
       ),
-- 
GitLab