From 7107b5feb33a95c5baedfaa658570e7bd89e58d0 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Fri, 16 Feb 2024 01:19:53 +0100 Subject: [PATCH] Debug custom report filter management --- htdocs/api/class/api.class.php | 4 +- htdocs/core/class/html.form.class.php | 63 ++++++--------- htdocs/core/customreports.php | 14 ++-- htdocs/core/lib/functions.lib.php | 111 +++++++++++++++++++++++--- htdocs/user/class/user.class.php | 8 +- test/phpunit/FunctionsLibTest.php | 58 ++++++++++++++ 6 files changed, 194 insertions(+), 64 deletions(-) diff --git a/htdocs/api/class/api.class.php b/htdocs/api/class/api.class.php index f09ba8ad784..6fe7b1d3398 100644 --- a/htdocs/api/class/api.class.php +++ b/htdocs/api/class/api.class.php @@ -331,8 +331,8 @@ class DolibarrApi protected function _checkFilters($sqlfilters, &$error = '') { // phpcs:enable - - return dolCheckFilters($sqlfilters, $error); + $firstandlastparenthesis = 0; + return dolCheckFilters($sqlfilters, $error, $firstandlastparenthesis); } // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php index 63092257c99..4240e5fbe60 100644 --- a/htdocs/core/class/html.form.class.php +++ b/htdocs/core/class/html.form.class.php @@ -10699,40 +10699,9 @@ class Form // Split the criteria on each AND //var_dump($search_component_params_hidden); - $nbofchars = dol_strlen($search_component_params_hidden); - $arrayofandtags = array(); - $i = 0; - $s = ''; - $countparenthesis = 0; - while ($i < $nbofchars) { - $char = dol_substr($search_component_params_hidden, $i, 1); - - if ($char == '(') { - $countparenthesis++; - } elseif ($char == ')') { - $countparenthesis--; - } - - if ($countparenthesis == 0) { - $char2 = dol_substr($search_component_params_hidden, $i+1, 1); - $char3 = dol_substr($search_component_params_hidden, $i+2, 1); - if ($char == 'A' && $char2 == 'N' && $char3 == 'D') { - // We found a AND - $arrayofandtags[] = trim($s); - $s = ''; - $i+=2; - } else { - $s .= $char; - } - } else { - $s .= $char; - } - $i++; - } - if ($s) { - $arrayofandtags[] = trim($s); - } + $arrayofandtags = dolForgeExplodeAnd($search_component_params_hidden); + // $arrayofandtags is now array( '...' , '...', ...) // Show each AND part foreach ($arrayofandtags as $tmpkey => $tmpval) { $errormessage = ''; @@ -10792,22 +10761,38 @@ class Form $ret .= ''; $ret .= "\n"; - $ret .= ''; + $ret .= ''; $ret .= ''; $ret .= ''; $ret .= ' '; - return $ret; } diff --git a/htdocs/core/customreports.php b/htdocs/core/customreports.php index 6788196e743..e6afd21c3e2 100644 --- a/htdocs/core/customreports.php +++ b/htdocs/core/customreports.php @@ -163,12 +163,16 @@ $extrafields->fetch_name_optionals_label('all'); // We load all extrafields defi $search_array_options = $extrafields->getOptionalsFromPost($object->table_element, '', 'search_'); $search_component_params = array(''); -$search_component_params_hidden = GETPOST('search_component_params_hidden', 'alphanohtml'); +$search_component_params_hidden = trim(GETPOST('search_component_params_hidden', 'alphanohtml')); +$search_component_params_input = trim(GETPOST('search_component_params_input', 'alphanohtml')); +//var_dump($search_component_params_hidden); +//var_dump($search_component_params_input); -// For the case we enter a criteria manually, the search_component_params_input will be defined and must be used in priority -if (GETPOST('search_component_params_input', 'alphanohtml')) { - $search_component_params_hidden = GETPOST('search_component_params_input', 'alphanohtml'); -} +$arrayofandtagshidden = dolForgeExplodeAnd($search_component_params_hidden); +$arrayofandtagsinput = dolForgeExplodeAnd($search_component_params_input); + +$search_component_params_hidden = implode(' AND ', array_merge($arrayofandtagshidden, $arrayofandtagsinput)); +//var_dump($search_component_params_hidden); $MAXUNIQUEVALFORGROUP = 20; $MAXMEASURESINBARGRAPH = 20; diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 1a7e95c6337..43da017f2db 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -12517,9 +12517,10 @@ function jsonOrUnserialize($stringtodecode) /** * forgeSQLFromUniversalSearchCriteria * - * @param string $filter String with universal search string. Must be '(aaa:bbb:...) OR (ccc:ddd:...) ...' with + * @param string $filter String with universal search string. Must be '(aaa:bbb:ccc) OR (ddd:eeee:fff) ...' with * aaa is a field name (with alias or not) and * bbb is one of this operator '=', '<', '>', '<=', '>=', '!=', 'in', 'notin', 'like', 'notlike', 'is', 'isnot'. + * ccc must not contains ( or ) * Example: '((client:=:1) OR ((client:>=:2) AND (client:<=:3))) AND (client:!=:8) AND (nom:like:'a%')' * @param string $errorstr Error message string * @param int $noand 1=Do not add the AND before the condition string. @@ -12535,8 +12536,9 @@ function forgeSQLFromUniversalSearchCriteria($filter, &$errorstr = '', $noand = } $regexstring = '\(([a-zA-Z0-9_\.]+:[<>!=insotlke]+:[^\(\)]+)\)'; // Must be (aaa:bbb:...) with aaa is a field name (with alias or not) and bbb is one of this operator '=', '<', '>', '<=', '>=', '!=', 'in', 'notin', 'like', 'notlike', 'is', 'isnot' + $firstandlastparenthesis = 0; - if (!dolCheckFilters($filter, $errorstr)) { + if (!dolCheckFilters($filter, $errorstr, $firstandlastparenthesis)) { if ($noerror) { return '1 = 2'; } else { @@ -12560,35 +12562,118 @@ function forgeSQLFromUniversalSearchCriteria($filter, &$errorstr = '', $noand = return ($noand ? "" : " AND ").($nopar ? "" : '(').preg_replace_callback('/'.$regexstring.'/i', 'dolForgeCriteriaCallback', $filter).($nopar ? "" : ')'); } +/** + * Explode an universal search string with AND parts + * + * @param string $sqlfilters Universal SQL filter string. Must have been trimmed before. + * @return array Array of AND + */ +function dolForgeExplodeAnd($sqlfilters) +{ + $arrayofandtags = array(); + $nbofchars = dol_strlen($sqlfilters); + + $i = 0; + $s = ''; + $countparenthesis = 0; + while ($i < $nbofchars) { + $char = dol_substr($sqlfilters, $i, 1); + + if ($char == '(') { + $countparenthesis++; + } elseif ($char == ')') { + $countparenthesis--; + } + + if ($countparenthesis == 0) { + $char2 = dol_substr($sqlfilters, $i+1, 1); + $char3 = dol_substr($sqlfilters, $i+2, 1); + if ($char == 'A' && $char2 == 'N' && $char3 == 'D') { + // We found a AND + $s = trim($s); + if (!preg_match('/^\(.*\)$/', $s)) { + $s = '('.$s.')'; + } + $arrayofandtags[] = $s; + $s = ''; + $i+=2; + } else { + $s .= $char; + } + } else { + $s .= $char; + } + $i++; + } + if ($s) { + $s = trim($s); + if (!preg_match('/^\(.*\)$/', $s)) { + $s = '('.$s.')'; + } + $arrayofandtags[] = $s; + } + + return $arrayofandtags; +} + /** * Return if a $sqlfilters parameter has a valid balance of parenthesis * - * @param string $sqlfilters sqlfilter string - * @param string $error Error message - * @return boolean True if valid, False if not valid ($error is filled with the reason in such a case) + * @param string $sqlfilters Universal SQL filter string. Must have been trimmed before. + * @param string $error Returned error message + * @param int $parenthesislevel Returned level of global parenthesis that we can remove/siplify, 0 if error or we cant simplify. + * @return boolean True if valid, False if not valid ($error returned parameter is filled with the reason in such a case) */ -function dolCheckFilters($sqlfilters, &$error = '') +function dolCheckFilters($sqlfilters, &$error = '', &$parenthesislevel = 0) { //$regexstring='\(([^:\'\(\)]+:[^:\'\(\)]+:[^:\(\)]+)\)'; //$tmp=preg_replace_all('/'.$regexstring.'/', '', $sqlfilters); $tmp = $sqlfilters; - $i = 0; - $nb = strlen($tmp); + + $nb = dol_strlen($tmp); $counter = 0; + $parenthesislevel = 0; + + $error = ''; + + $i = 0; while ($i < $nb) { - if ($tmp[$i] == '(') { + $char = dol_substr($tmp, $i, 1); + + if ($char == '(') { + if ($i == $parenthesislevel && $parenthesislevel == $counter) { + // We open a parenthesis and it is the first char + $parenthesislevel++; + } $counter++; - } - if ($tmp[$i] == ')') { + } elseif ($char == ')') { + $nbcharremaining = ($nb - $i - 1); + if ($nbcharremaining >= $counter) { + $parenthesislevel = min($parenthesislevel, $counter - 1); + } + if ($parenthesislevel > $counter && $nbcharremaining >= $counter) { + $parenthesislevel = $counter; + } $counter--; } + if ($counter < 0) { - $error = "Wrond balance of parenthesis in sqlfilters=".$sqlfilters; + $error = "Wrong balance of parenthesis in sqlfilters=".$sqlfilters; + $parenthesislevel = 0; dol_syslog($error, LOG_WARNING); return false; } + $i++; } + + if ($counter > 0) { + $error = "Wrong balance of parenthesis in sqlfilters=".$sqlfilters; + $parenthesislevel = 0; + dol_syslog($error, LOG_WARNING); + return false; + } + return true; } @@ -12703,7 +12788,7 @@ function dolForgeCriteriaCallback($matches) */ function getTimelineIcon($actionstatic, &$histo, $key) { - global $conf, $langs; + global $langs; $out = ''."\n"; $iconClass = 'fa fa-comments'; diff --git a/htdocs/user/class/user.class.php b/htdocs/user/class/user.class.php index 1c974597524..ddc765bfb3d 100644 --- a/htdocs/user/class/user.class.php +++ b/htdocs/user/class/user.class.php @@ -383,8 +383,8 @@ class User extends CommonObject public $fields = array( 'rowid'=>array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'index'=>1, 'position'=>1, 'comment'=>'Id'), - 'lastname'=>array('type'=>'varchar(50)', 'label'=>'LastName', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'showoncombobox'=>1, 'index'=>1, 'position'=>20, 'searchall'=>1), - 'firstname'=>array('type'=>'varchar(50)', 'label'=>'FirstName', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'showoncombobox'=>1, 'index'=>1, 'position'=>10, 'searchall'=>1), + 'lastname'=>array('type'=>'varchar(50)', 'label'=>'Lastname', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'showoncombobox'=>1, 'index'=>1, 'position'=>20, 'searchall'=>1), + 'firstname'=>array('type'=>'varchar(50)', 'label'=>'Firstname', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'showoncombobox'=>1, 'index'=>1, 'position'=>10, 'searchall'=>1), 'ref_employee'=>array('type'=>'varchar(50)', 'label'=>'RefEmployee', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'showoncombobox'=>1, 'index'=>1, 'position'=>30, 'searchall'=>1), 'national_registration_number'=>array('type'=>'varchar(50)', 'label'=>'NationalRegistrationNumber', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'showoncombobox'=>1, 'index'=>1, 'position'=>40, 'searchall'=>1) ); @@ -4132,12 +4132,10 @@ class User extends CommonObject $this->findUserIdByEmailCache[$email] = -1; - global $conf; - $sql = 'SELECT rowid'; $sql .= ' FROM '.$this->db->prefix().'user'; if (getDolGlobalString('AGENDA_DISABLE_EXACT_USER_EMAIL_COMPARE_FOR_EXTERNAL_CALENDAR')) { - $sql .= " WHERE email LIKE '%".$this->db->escape($email)."%'"; + $sql .= " WHERE email LIKE '%".$this->db->escape($this->db->escapeforlike($email))."%'"; } else { $sql .= " WHERE email = '".$this->db->escape($email)."'"; } diff --git a/test/phpunit/FunctionsLibTest.php b/test/phpunit/FunctionsLibTest.php index b28e945e28d..204191e89b9 100644 --- a/test/phpunit/FunctionsLibTest.php +++ b/test/phpunit/FunctionsLibTest.php @@ -176,6 +176,64 @@ class FunctionsLibTest extends PHPUnit\Framework\TestCase } + /** + * testDolCheckFilters + * + * @return boolean + */ + public function testDolCheckFilters() + { + global $conf, $langs, $db; + + // A sql with global parenthesis at level 2 + $error = ''; + $parenthesislevel = 0; + $sql = '(( ... (a:=:1) .éééé. (b:=:1) ... ))'; + $result = dolCheckFilters($sql, $error, $parenthesislevel); + $this->assertEquals(2, $parenthesislevel); + $this->assertTrue($result); + + // A sql with global parenthesis at level 2 + $error = ''; + $parenthesislevel = 0; + $sql = '(((((a:=:1) ... ) .éééé.. (b:=:1) ..) ... ))'; + $result = dolCheckFilters($sql, $error, $parenthesislevel); + $this->assertEquals(2, $parenthesislevel); + $this->assertTrue($result); + + // A sql with global parenthesis at level 2 + $error = ''; + $parenthesislevel = 0; + $sql = '((... (((a:=:1) ... ( .éééé.. (b:=:1) ..)))))'; + $result = dolCheckFilters($sql, $error, $parenthesislevel); + $this->assertEquals(2, $parenthesislevel); + $this->assertTrue($result); + + // A sql with global parenthesis at level 0 + $error = ''; + $parenthesislevel = 0; + $sql = '(a:=:1) ... (b:=:1) éééé ...'; + $result = dolCheckFilters($sql, $error, $parenthesislevel); + $this->assertEquals(0, $parenthesislevel); + $this->assertTrue($result); + + // A sql with bad balance + $error = ''; + $parenthesislevel = 0; + $sql = '((((a:=:1) ... (b:=:1) éééé ..))'; + $result = dolCheckFilters($sql, $error, $parenthesislevel); + $this->assertEquals(0, $parenthesislevel); + $this->assertFalse($result); + + // A sql with bad balance + $error = ''; + $parenthesislevel = 0; + $sql = '(((a:=:1) ... (b:=:1) éééé ..)))'; + $result = dolCheckFilters($sql, $error, $parenthesislevel); + $this->assertEquals(0, $parenthesislevel); + $this->assertFalse($result); + } + /** * testDolForgeCriteriaCallback *