[TikiWiki-commits] [Git][tikiwiki/tiki][master] [NEW] PluginList: Add advanced filter options (OR, AND, NOT) to PluginList with multiple selections

"Victor Emanouilov \(@kroky\) via TikiWiki-cvs" <[email protected]>
Newsgroups gmane.comp.cms.tiki.cvs
Message-ID <690dfff7cf99e_2c554a7b8568de@gitlab-sidekiq-low-urgency-cpu-bound-v2-75f5bb84f4-sp64l.mail>

Victor Emanouilov pushed to branch master at Tiki Wiki CMS Groupware / Tiki


Commits:
f38a51a7 by UshindiG at 2025-11-07T14:11:26+00:00
[NEW] PluginList: Add advanced filter options (OR, AND, NOT) to PluginList with multiple selections
---
* [FIX] Fix unit tests

* Update filter query

* Add the operator to the plugin list parameters

* [FIX] 'NOT' Operator is not working when submitting empty value as input

* [NEW] PluginList: Add advanced filter options (OR, AND, NOT) to PluginList with multiple selections

* [FIX] 'NOT' Operator is not working when submitting empty value as input

* [FiX] Fix failing Not operator

* Add the operator to the plugin list parameters

* [FIX] 'NOT' Operator is not working when submitting empty value as input

* [NEW] PluginList: Add advanced filter options (OR, AND, NOT) to PluginList with multiple selections

* Add the operator to the plugin list parameters

* [FIX] 'NOT' Operator is not working when submitting empty value as input

* [NEW] PluginList: Add advanced filter options (OR, AND, NOT) to PluginList with multiple selections

See merge request tikiwiki/tiki!5416

- - - - -


4 changed files:

- doc/devtools/codesniffer/standards/TikiIgnore/ignore_list.json
- lib/core/Search/Query/WikiBuilder.php
- + lib/test/Core/Search/Query/WikiBuilderTest.php
- tiki-adminusers.php


Changes:

=====================================
doc/devtools/codesniffer/standards/TikiIgnore/ignore_list.json
=====================================
@@ -2572,6 +2572,9 @@
         "lib\/test\/Core\/Request\/RequestTest.php": {
             "class:RequestTest": true
         },
+        "lib\/test\/Core\/Search\/Query\/WikiBuilderTest.php": {
+            "class:Search_Query_WikiBuilderTest": true
+        },
         "lib\/test\/Core\/TikiDb\/TableTest.php": {
             "class:TikiDb_TableTest": true
         },


=====================================
lib/core/Search/Query/WikiBuilder.php
=====================================
@@ -133,14 +133,28 @@ class Search_Query_WikiBuilder
     {
         $fields = $this->get_fields_from_arguments($arguments);
         $masterField = null;
-        $subquery = new Search_Query(null, 'or');
+        $inputData = $this->input->asArray();
+
+        if (isset($arguments['operator']) && $arguments['operator'] == 'AND') {
+            $subquery = new Search_Query(null, 'and');
+        } else {
+            $subquery = new Search_Query(null, 'or');
+        }
+
         foreach ($fields as $fieldNum => $fieldName) {
             $filter = $this->getEditableFilter($fieldName, $editableType, $fields[0]);
             $filter->applyCondition($subquery);
         }
-        $query->getExpr()->addPart($subquery->getExpr());
+
+        $expr = $subquery->getExpr();
+        if ((isset($arguments['operator']) && $arguments['operator'] == 'NOT') && isset($inputData['filter'])) {
+            $query->getExpr()->addPart(new Search_Expr_Not($expr));
+        } else {
+            $query->getExpr()->addPart($expr);
+        }
     }
 
+
     /**
      * Handle return only the list of results defined by the user
      *


=====================================
lib/test/Core/Search/Query/WikiBuilderTest.php
=====================================
@@ -0,0 +1,172 @@
+<?php
+
+// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
+//
+// All Rights Reserved. See copyright.txt for details and a complete list of authors.
+// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
+
+namespace Tiki\Test\Core\Search\Query;
+
+use PHPUnit\Framework\TestCase;
+use Search_Index_Memory;
+use Search_Query;
+use Search_Query_WikiBuilder;
+use JitFilter;
+use Search_Expr_Not;
+use Search_Expr_And;
+use Search_Expr_Or;
+use Search_Expr_Token;
+
+class WikiBuilderTest extends TestCase
+{
+    public function testOperatorsProduceDifferentStructures()
+    {
+        $index1 = new Search_Index_Memory();
+        $query1 = new Search_Query();
+        $mockInput1 = new JitFilter(['contents' => 'word1 word2 word3', 'filter' => 'test_filter']);
+        $wikiBuilder1 = new Search_Query_WikiBuilder($query1, $mockInput1);
+        $wikiBuilder1->wpquery_filter_editable($query1, 'text', ['operator' => 'AND', 'field' => 'contents']);
+        $query1->search($index1);
+        $andResult = $index1->getLastQuery();
+
+        $index2 = new Search_Index_Memory();
+        $query2 = new Search_Query();
+        $mockInput2 = new JitFilter(['contents' => 'word1 word2 word3', 'filter' => 'test_filter']);
+        $wikiBuilder2 = new Search_Query_WikiBuilder($query2, $mockInput2);
+        $wikiBuilder2->wpquery_filter_editable($query2, 'text', ['operator' => 'OR', 'field' => 'contents']);
+        $query2->search($index2);
+        $orResult = $index2->getLastQuery();
+
+        $index3 = new Search_Index_Memory();
+        $query3 = new Search_Query();
+        $mockInput3 = new JitFilter(['contents' => 'word1 word2 word3', 'filter' => 'test_filter']);
+        $wikiBuilder3 = new Search_Query_WikiBuilder($query3, $mockInput3);
+        $wikiBuilder3->wpquery_filter_editable($query3, 'text', ['operator' => 'NOT', 'field' => 'contents']);
+        $query3->search($index3);
+        $notResult = $index3->getLastQuery();
+
+        $this->assertNotNull($andResult, 'AND result should not be null');
+        $this->assertNotNull($orResult, 'OR result should not be null');
+        $this->assertNotNull($notResult, 'NOT result should not be null');
+
+        $this->assertTrue($this->exprHasInstance($notResult, Search_Expr_Not::class), 'NOT operator should produce a Search_Expr_Not');
+
+        // Operators should produce different structures
+        $this->assertNotEquals($andResult, $orResult, 'AND and OR should produce different structures');
+        $this->assertNotEquals($andResult, $notResult, 'AND and NOT should produce different structures');
+        $this->assertNotEquals($orResult, $notResult, 'OR and NOT should produce different structures');
+    }
+
+    public function testNotOperatorRequiresFilterKey()
+    {
+        $index1 = new Search_Index_Memory();
+        $query1 = new Search_Query();
+        $mockInput1 = new JitFilter(['contents' => 'test value']);
+        $wikiBuilder1 = new Search_Query_WikiBuilder($query1, $mockInput1);
+        $wikiBuilder1->wpquery_filter_editable($query1, 'text', ['operator' => 'NOT', 'field' => 'contents']);
+        $query1->search($index1);
+        $resultWithoutFilter = $index1->getLastQuery();
+
+        $index2 = new Search_Index_Memory();
+        $query2 = new Search_Query();
+        $mockInput2 = new JitFilter(['contents' => 'test value', 'filter' => 'test_filter']);
+        $wikiBuilder2 = new Search_Query_WikiBuilder($query2, $mockInput2);
+        $wikiBuilder2->wpquery_filter_editable($query2, 'text', ['operator' => 'NOT', 'field' => 'contents']);
+        $query2->search($index2);
+        $resultWithFilter = $index2->getLastQuery();
+
+        $this->assertNotEquals($resultWithoutFilter, $resultWithFilter);
+
+        $this->assertFalse($this->exprHasInstance($resultWithoutFilter, Search_Expr_Not::class), 'Without filter key, NOT should not be present');
+        $this->assertTrue($this->exprHasInstance($resultWithFilter, Search_Expr_Not::class), 'With filter key, NOT should be present');
+    }
+
+
+    public function testOperatorStructures()
+    {
+        $subQuery1 = new Search_Query(null, 'and');
+        $subQuery1->filterContent('term1', ['contents']);
+        $subQuery1->filterContent('term2', ['contents']);
+        $subQuery1->filterContent('term3', ['contents']);
+        $index1 = new Search_Index_Memory();
+        $subQuery1->search($index1);
+        $andResult = $index1->getLastQuery();
+        $this->assertNotNull($andResult, 'andResult should not be null');
+
+        $subQuery2 = new Search_Query(null, 'or');
+        $subQuery2->filterContent('term1', ['contents']);
+        $subQuery2->filterContent('term2', ['contents']);
+        $subQuery2->filterContent('term3', ['contents']);
+        $index2 = new Search_Index_Memory();
+        $subQuery2->search($index2);
+        $orResult = $index2->getLastQuery();
+        $this->assertNotNull($orResult, 'orResult should not be null');
+
+        $andTokenCount = $this->countTokens($andResult);
+        $orTokenCount  = $this->countTokens($orResult);
+
+        $this->assertEquals(3, $andTokenCount, 'AND case should contain exactly 3 token expressions');
+        $this->assertEquals(3, $orTokenCount, 'OR case should contain exactly 3 token expressions');
+
+        $this->assertTrue(
+            $andResult instanceof Search_Expr_And || $this->exprHasInstance($andResult, Search_Expr_And::class),
+            'AND result should be or contain Search_Expr_And'
+        );
+        $this->assertTrue(
+            $orResult instanceof Search_Expr_Or || $this->exprHasInstance($orResult, Search_Expr_Or::class),
+            'OR result should be or contain Search_Expr_Or'
+        );
+
+        $this->assertNotEquals($this->serializeExpr($andResult), $this->serializeExpr($orResult));
+    }
+
+    private function countTokens($expr)
+    {
+        $count = 0;
+        if ($expr instanceof Search_Expr_Token) {
+            return 1;
+        }
+
+        if (method_exists($expr, 'walk')) {
+            $expr->walk(function ($current) use (&$count) {
+                if ($current instanceof Search_Expr_Token) {
+                    $count++;
+                }
+                return $current;
+            });
+        }
+
+        return $count;
+    }
+
+    private function exprHasInstance($expr, string $className): bool
+    {
+        if ($expr === null) {
+            return false;
+        }
+        if (is_object($expr) && $expr instanceof $className) {
+            return true;
+        }
+
+        $found = false;
+        if (method_exists($expr, 'walk')) {
+            $expr->walk(function ($current) use (&$found, $className) {
+                if ($current instanceof $className) {
+                    $found = true;
+                }
+                return $current;
+            });
+        }
+
+        return $found;
+    }
+
+    private function serializeExpr($expr)
+    {
+        if (method_exists($expr, 'getSerializedParts')) {
+            return get_class($expr) . ':' . $expr->getSerializedParts();
+        }
+
+        return var_export($expr, true);
+    }
+}


=====================================
tiki-adminusers.php
=====================================
@@ -539,7 +539,7 @@ if (isset($_REQUEST['user']) and $_REQUEST['user']) {
                         $cookietab = '1';
                         $logslib->add_log('adminusers', 'Password reset required at next login has been enabled for ' . $_POST['login'], $user);
                     }
-                } else if (! $pass_first_login && $userinfo['pass_confirm'] === 0) {
+                } elseif (! $pass_first_login && $userinfo['pass_confirm'] === 0) {
                     if ($userlib->change_user_password($userinfo['login'], '', $pass_first_login)) {
                         Feedback::success(sprintf(tra('Password reset requirement has been disabled for %s'), $_POST['login']));
                         $cookietab = '1';



View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/f38a51a7c91bf39d08cbc83447506f4e46c5fcaa

-- 
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/f38a51a7c91bf39d08cbc83447506f4e46c5fcaa
You're receiving this email because of your account on gitlab.com.

_______________________________________________
TikiWiki-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/tikiwiki-cvs
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.