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
*