From 6d62acb446772cabc13aa653dda5f558ae3bf59b Mon Sep 17 00:00:00 2001 From: Mario Vavti Date: Thu, 17 Apr 2025 11:35:06 +0200 Subject: [PATCH] extend MessageFilter::test_condition() to deal with && and || conditions and add tests --- Zotlabs/Lib/MessageFilter.php | 134 ++++++++++++++------------- tests/unit/Lib/ActivityTest.php | 2 +- tests/unit/Lib/MessageFilterTest.php | 129 ++++++++++++++++++++++++++ vendor/composer/installed.php | 4 +- 4 files changed, 201 insertions(+), 68 deletions(-) create mode 100644 tests/unit/Lib/MessageFilterTest.php diff --git a/Zotlabs/Lib/MessageFilter.php b/Zotlabs/Lib/MessageFilter.php index e7382c0d5..523c179c2 100644 --- a/Zotlabs/Lib/MessageFilter.php +++ b/Zotlabs/Lib/MessageFilter.php @@ -124,9 +124,7 @@ class MessageFilter { /** - * @brief Test for Conditional Execution conditions. Shamelessly ripped off from Code/Render/Comanche - * - * This is extensible. The first version of variable testing supports tests of the forms: + * Evaluate a conditional expression with support for AND (&&) and OR (||) operators. * * - ?foo ~= baz which will check if item.foo contains the string 'baz'; * - ?foo == baz which will check if item.foo is the string 'baz'; @@ -143,103 +141,109 @@ class MessageFilter { * * The values 0, '', an empty array, and an unset value will all evaluate to false. * - * @param string $s - * @param array $item - * @return bool + * @param string $s The condition string to evaluate. + * @param array $item The associative array providing variable values. + * @return bool True if the condition is met, false otherwise. */ - public static function test_condition($s,$item) { + public static function test_condition($s, $item) { + $s = trim($s); + // Handle OR (||) + // Split on '||' not inside quotes + $or_parts = preg_split('/\s*\|\|\s*/', $s); + if (count($or_parts) > 1) { + foreach ($or_parts as $part) { + if (self::test_condition($part, $item)) { + return true; + } + } + return false; + } + + // Handle AND (&&) + // Split on '&&' not inside quotes + $and_parts = preg_split('/\s*\&\&\s*/', $s); + if (count($and_parts) > 1) { + foreach ($and_parts as $part) { + if (!self::test_condition($part, $item)) { + return false; + } + } + return true; + } + + // Basic checks + + // Contains substring (case-insensitive) if (preg_match('/(.*?)\s\~\=\s(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if (stripos($x, trim($matches[2])) !== false) { - return true; - } - return false; + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return (stripos($x, trim($matches[2])) !== false); } + // Equality if (preg_match('/(.*?)\s\=\=\s(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if ($x == trim($matches[2])) { - return true; - } - return false; + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return ($x == trim($matches[2])); } + // Inequality if (preg_match('/(.*?)\s\!\=\s(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if ($x != trim($matches[2])) { - return true; - } - return false; + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return ($x != trim($matches[2])); } + // Greater than or equal if (preg_match('/(.*?)\s\>\=\s(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if ($x >= trim($matches[2])) { - return true; - } - return false; + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return ($x >= trim($matches[2])); } + // Less than or equal if (preg_match('/(.*?)\s\<\=\s(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if ($x <= trim($matches[2])) { - return true; - } - return false; + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return ($x <= trim($matches[2])); } + // Greater than if (preg_match('/(.*?)\s\>\s(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if ($x > trim($matches[2])) { - return true; - } - return false; + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return ($x > trim($matches[2])); } - if (preg_match('/(.*?)\s\>\s(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if ($x < trim($matches[2])) { - return true; - } - return false; + // Less than + if (preg_match('/(.*?)\s\<\s(.*?)$/', $s, $matches)) { + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return ($x < trim($matches[2])); } - if (preg_match('/[\$](.*?)\s\{\}\s(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if (is_array($x) && in_array(trim($matches[2]), $x)) { - return true; - } - return false; + // Array contains value + if (preg_match('/(.*?)\s\{\}\s(.*?)$/', $s, $matches)) { + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return (is_array($x) && in_array(trim($matches[2]), $x)); } + // Array contains key if (preg_match('/(.*?)\s\{\*\}\s(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if (is_array($x) && array_key_exists(trim($matches[2]), $x)) { - return true; - } - return false; + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return (is_array($x) && array_key_exists(trim($matches[2]), $x)); } - // Ordering of this check (for falsiness) with relation to the following one (check for truthiness) is important. + // Falsy check if (preg_match('/\!(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if (!$x) { - return true; - } - return false; + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return !$x; } + // Truthy check (default) if (preg_match('/(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if ($x) { - return true; - } - return false; + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return (bool)$x; } + // If no conditions matched, return false return false; } + } diff --git a/tests/unit/Lib/ActivityTest.php b/tests/unit/Lib/ActivityTest.php index 543bf60bc..1857487c8 100644 --- a/tests/unit/Lib/ActivityTest.php +++ b/tests/unit/Lib/ActivityTest.php @@ -46,7 +46,7 @@ class ActivityTest extends UnitTestCase { * * @dataProvider get_mid_and_uuid_provider */ - public function test_get_mid_and_uuid(string $payload, $mid, $uuid): void { + public function test_get_mid_and_uuid(string $payload, string $mid, string $uuid): void { // diff --git a/tests/unit/Lib/MessageFilterTest.php b/tests/unit/Lib/MessageFilterTest.php new file mode 100644 index 000000000..dbdc9e517 --- /dev/null +++ b/tests/unit/Lib/MessageFilterTest.php @@ -0,0 +1,129 @@ + '', + 'body' => "A grasshopper spent the summer hopping about in the sun and singing to his heart's content. One day, an ant went hurrying by, looking very hot and weary.\r\n#story #grasshopper #ant", + 'term' => [ + ['ttype' => TERM_HASHTAG, 'term' => 'story'], + ['ttype' => TERM_HASHTAG, 'term' => 'grasshopper'], + ['ttype' => TERM_HASHTAG, 'term' => 'ant'] + ], + 'verb' => 'Create', + 'obj_type' => 'Note', + 'obj' => [ + + ], + 'item_private' => 1, + 'item_thread_top' => 1 + ]; + + $this->assertEquals($result, MessageFilter::evaluate($item, $incl, $excl)); + } + + public static function evaluate_provider() : array { + return [ + 'body contains incl' => [ + 'summer', + '', + true + ], + 'body contains excl' => [ + '', + 'summer', + false + ], + 'lang=en in incl' => [ + 'lang=en', + '', + true + ], + 'lang=en in excl' => [ + '', + 'lang=en', + false + ], + 'lang=de in incl' => [ + 'lang=de', + '', + false + ], + 'lang=de in excl' => [ + '', + 'lang=de', + true + ], + 'hashtag in incl' => [ + '#grasshopper', + '', + true + ], + 'hashtag in excl' => [ + '', + '#grasshopper', + false + ], + 'any hashtag in excl' => [ + '', + '#*', + false + ], + 'item.verb == Announce in excl' => [ + '', + '?verb == Announce', + true + ], + 'item.verb != Announce in incl' => [ + '?verb != Announce', + '', + true + ], + 'combined body contains word and item.verb == Announce in excl' => [ + '', + "summer\r\n?verb == Announce", + false + ], + 'item.item_thread_top == 1 in excl' => [ + '', + "?item_thread_top == 1", + false + ], + 'combined item_private == 0 and item.item_thread_top == 1 in excl' => [ + '', + "?item_private == 0\r\n?item_thread_top == 1", + false + ], + 'item.item_private < 1 in excl' => [ + '', + "?item_private < 1", + true + ], + 'item.item_thread_top == 1 and item.item_private < 1 in excl' => [ + '', + "?item_thread_top == 1 && ?item_private > 0 ", + true + ], + 'item.item_thread_top == 1 and item.item_private < 1 in excl' => [ + '', + "?item_thread_top == 1 && ?item_private < 1 ", + false + ], + 'item.item_thread_top == 1 or item.item_private < 1 in excl' => [ + '', + "?item_thread_top == 1 || ?item_private = 0", + false + ], + ]; + } +} diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 073738a4d..9aa065800 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'zotlabs/hubzilla', 'pretty_version' => 'dev-10.2RC', 'version' => 'dev-10.2RC', - 'reference' => '0d51ff1906df6e02f2aa028b753f25ae988d5d64', + 'reference' => '17777981ac07c39fcef35e34a53e9160a213c1a4', 'type' => 'application', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -430,7 +430,7 @@ 'zotlabs/hubzilla' => array( 'pretty_version' => 'dev-10.2RC', 'version' => 'dev-10.2RC', - 'reference' => '0d51ff1906df6e02f2aa028b753f25ae988d5d64', + 'reference' => '17777981ac07c39fcef35e34a53e9160a213c1a4', 'type' => 'application', 'install_path' => __DIR__ . '/../../', 'aliases' => array(),