diff --git a/sources/application/search/criterionconversion/criteriontooql.class.inc.php b/sources/application/search/criterionconversion/criteriontooql.class.inc.php index 036124f2fe..8591e3d45f 100644 --- a/sources/application/search/criterionconversion/criteriontooql.class.inc.php +++ b/sources/application/search/criterionconversion/criteriontooql.class.inc.php @@ -24,6 +24,7 @@ namespace Combodo\iTop\Application\Search\CriterionConversion; use Combodo\iTop\Application\Search\CriterionConversionAbstract; +use Combodo\iTop\Application\Search\SearchForm; class CriterionToOQL extends CriterionConversionAbstract { @@ -50,6 +51,7 @@ class CriterionToOQL extends CriterionConversionAbstract self::OP_ENDS_WITH => 'EndsWithToOql', self::OP_EMPTY => 'EmptyToOql', self::OP_NOT_EMPTY => 'NotEmptyToOql', + self::OP_IN => 'InToOql', self::OP_ALL => 'AllToOql', ); @@ -57,7 +59,7 @@ class CriterionToOQL extends CriterionConversionAbstract { $sFct = $aMappedOperators[$sOperator]; - return self::$sFct($sRef, $sOperator, self::GetValues($aCriteria)); + return self::$sFct($sRef, $sOperator, $aCriteria); } $sValue = self::GetValue(self::GetValues($aCriteria), 0); @@ -87,38 +89,72 @@ class CriterionToOQL extends CriterionConversionAbstract return $aValues[$iIndex]['value']; } - protected static function ContainsToOql($sRef, $sOperator, $aValues) + protected static function ContainsToOql($sRef, $sOperator, $aCriteria) { + $aValues = self::GetValues($aCriteria); $sValue = self::GetValue($aValues, 0); return "({$sRef} LIKE '%{$sValue}%')"; } - protected static function StartsWithToOql($sRef, $sOperator, $aValues) + protected static function StartsWithToOql($sRef, $sOperator, $aCriteria) { + $aValues = self::GetValues($aCriteria); $sValue = self::GetValue($aValues, 0); return "({$sRef} LIKE '{$sValue}%')"; } - protected static function EndsWithToOql($sRef, $sOperator, $aValues) + protected static function EndsWithToOql($sRef, $sOperator, $aCriteria) { + $aValues = self::GetValues($aCriteria); $sValue = self::GetValue($aValues, 0); return "({$sRef} LIKE '%{$sValue}')"; } - protected static function EmptyToOql($sRef, $sOperator, $aValues) + protected static function EmptyToOql($sRef, $sOperator, $aCriteria) { return "({$sRef} = '')"; } - protected static function NotEmptyToOql($sRef, $sOperator, $aValues) + protected static function NotEmptyToOql($sRef, $sOperator, $aCriteria) { return "({$sRef} != '')"; } - protected static function AllToOql($sRef, $sOperator, $aValues) + protected static function InToOql($sRef, $sOperator, $aCriteria) + { + $sAttCode = $aCriteria['code']; + $sClass = $aCriteria['class']; + $aValues = $aCriteria['values']; + + $aAttributeDefs = MetaModel::ListAttributeDefs($sClass); + if (array_key_exists($sAttCode, $aAttributeDefs)) + { + $oAttDef = $aAttributeDefs[$sAttCode]; + $aAllowedValues = SearchForm::GetFieldAllowedValues($oAttDef); + if (array_key_exists('values', $aAllowedValues)) + { + $aAllowedValues = $aAllowedValues['values']; + // more selected values than remaining so use NOT IN + if (count($aValues) > (count($aAllowedValues) / 2)) + { + foreach($aValues as $aValue) + { + unset($aAllowedValues[$aValue['value']]); + } + $sOQL = "({$sRef} NOT IN ("; + $s .= implode(',', array_keys($aAllowedValues)); + + } + } + } + + return "({$sRef} != '')"; + } + + protected static function AllToOql($sRef, $sOperator, $aCriteria) { return "1"; } diff --git a/sources/application/search/criterionconversion/criteriontosearchform.class.inc.php b/sources/application/search/criterionconversion/criteriontosearchform.class.inc.php index 4a68e0bc4e..5c1d5620a4 100644 --- a/sources/application/search/criterionconversion/criteriontosearchform.class.inc.php +++ b/sources/application/search/criterionconversion/criteriontosearchform.class.inc.php @@ -20,22 +20,33 @@ */ /** - * Convert OQL expressions into structure for the search form + * Convert structures from OQL expressions into structure for the search form */ namespace Combodo\iTop\Application\Search\CriterionConversion; -use AttributeString; +use AttributeDefinition; use Combodo\iTop\Application\Search\CriterionConversionAbstract; class CriterionToSearchForm extends CriterionConversionAbstract { - public static function Convert($aAndCriterionRaw) + public static function Convert($aAndCriterionRaw, $aFieldsByCategory) { + $aAllFields = array(); + foreach($aFieldsByCategory as $aFields) + { + foreach($aFields as $aField) + { + $sAlias = $aField['class_alias']; + $sCode = $aField['code']; + $aAllFields["$sAlias.$sCode"] = $aField; + } + } $aAndCriterion = array(); $aMappingOperatorToFunction = array( - AttributeString::SEARCH_WIDGET_TYPE => 'TextToSearchForm', + AttributeDefinition::SEARCH_WIDGET_TYPE_STRING => 'TextToSearchForm', + AttributeDefinition::SEARCH_WIDGET_TYPE_ENUM => 'EnumToSearchForm', ); foreach($aAndCriterionRaw as $aCriteria) @@ -45,7 +56,7 @@ class CriterionToSearchForm extends CriterionConversionAbstract if (array_key_exists($aCriteria['widget'], $aMappingOperatorToFunction)) { $sFct = $aMappingOperatorToFunction[$aCriteria['widget']]; - $aAndCriterion[] = self::$sFct($aCriteria); + $aAndCriterion[] = self::$sFct($aCriteria, $aAllFields); } else { @@ -62,7 +73,7 @@ class CriterionToSearchForm extends CriterionConversionAbstract return $aAndCriterion; } - protected static function TextToSearchForm($aCriteria) + protected static function TextToSearchForm($aCriteria, $aFields) { $sOperator = $aCriteria['operator']; $sValue = $aCriteria['values'][0]['value']; @@ -100,4 +111,43 @@ class CriterionToSearchForm extends CriterionConversionAbstract return $aCriteria; } + + protected static function EnumToSearchForm($aCriteria, $aFields) + { + $sOperator = $aCriteria['operator']; + $sRef = $aCriteria['ref']; + $aValues = $aCriteria['values']; + if (array_key_exists($sRef, $aFields)) + { + $aField = $aFields[$sRef]; + if (array_key_exists('allowed_values', $aField) && array_key_exists('values', $aField['allowed_values'])) + { + $aAllowedValues = $aField['allowed_values']['values']; + } + } + + switch (true) + { + case ($sOperator == 'NOT IN'): + if (isset($aAllowedValues)) + { + foreach($aValues as $aValue) + { + $sValue = $aValue['value']; + unset($aAllowedValues[$sValue]); + } + $aCriteria['values'] = array(); + + foreach($aAllowedValues as $sValue => $sLabel) + { + $aValue = array('value' => $sValue, 'label' => $sLabel); + $aCriteria['values'][] = $aValue; + } + $aCriteria['operator'] = 'IN'; + } + break; + } + + return $aCriteria; + } } \ No newline at end of file diff --git a/sources/application/search/criterionconversionabstract.class.inc.php b/sources/application/search/criterionconversionabstract.class.inc.php index 04ede456b4..469cfd6313 100644 --- a/sources/application/search/criterionconversionabstract.class.inc.php +++ b/sources/application/search/criterionconversionabstract.class.inc.php @@ -31,6 +31,7 @@ abstract class CriterionConversionAbstract const OP_ENDS_WITH = 'ends_with'; const OP_EMPTY = 'empty'; const OP_NOT_EMPTY = 'not_empty'; + const OP_IN = 'IN'; const OP_ALL = 'all'; } diff --git a/sources/application/search/searchform.class.inc.php b/sources/application/search/searchform.class.inc.php index ce1c418e00..68eaa90c53 100644 --- a/sources/application/search/searchform.class.inc.php +++ b/sources/application/search/searchform.class.inc.php @@ -127,7 +127,7 @@ class SearchForm $aFields = $this->GetFields($oSet); $oSearch = $oSet->GetFilter(); - $aCriterion = $this->GetCriterion($oSearch); + $aCriterion = $this->GetCriterion($oSearch, $aFields); $oBaseSearch = $oSearch->DeepClone(); $oBaseSearch->ResetCondition(); @@ -173,7 +173,7 @@ class SearchForm $aAuthorizedClasses = array(); foreach($aSelectedClasses as $sAlias => $sClassName) { - if (\UserRights::IsActionAllowed($sClassName, UR_ACTION_BULK_READ, $oSet) != UR_ALLOWED_NO) + if (\UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO) { $aAuthorizedClasses[$sAlias] = $sClassName; } @@ -195,7 +195,7 @@ class SearchForm unset($aAttributeDefs[$sAttCode]); } } - usort($aZList, function ($aItem1, $aItem2) { + uasort($aZList, function ($aItem1, $aItem2) { return strcmp($aItem1['label'], $aItem2['label']); }); $aAllFields['zlist'] = $aZList; @@ -207,7 +207,7 @@ class SearchForm $aOthers = $this->AppendField($sClass, $sAlias, $sAttCode, $oAttDef, $aOthers); } - usort($aOthers, function ($aItem1, $aItem2) { + uasort($aOthers, function ($aItem1, $aItem2) { return strcmp($aItem1['label'], $aItem2['label']); }); @@ -231,7 +231,7 @@ class SearchForm * * @return array */ - public function GetFieldAllowedValues($oAttrDef) + public static function GetFieldAllowedValues($oAttrDef) { if ($oAttrDef->IsExternalKey(EXTKEY_ABSOLUTE)) { @@ -269,8 +269,11 @@ class SearchForm /** * @param \DBSearch $oSearch + * @param array $aFields + * + * @return array */ - public function GetCriterion($oSearch) + public function GetCriterion($oSearch, $aFields) { $oExpression = $oSearch->GetCriteria(); @@ -288,7 +291,7 @@ class SearchForm } $aAndCriterion[] = $oAndSubExpr->GetCriterion($oSearch); } - $aAndCriterion = CriterionToSearchForm::Convert($aAndCriterion); + $aAndCriterion = CriterionToSearchForm::Convert($aAndCriterion, $aFields); $aOrCriterion[] = array('and' => $aAndCriterion); } @@ -317,7 +320,7 @@ class SearchForm $aField['class_alias'] = $sClassAlias; $aField['label'] = $sLabel; $aField['widget'] = $oAttrDef->GetSearchType(); - $aField['allowed_values'] = $this->GetFieldAllowedValues($oAttrDef); + $aField['allowed_values'] = self::GetFieldAllowedValues($oAttrDef); $aFields[$sClassAlias.'.'.$sFilterCode] = $aField; $this->aLabels[$sLabel] = true; } diff --git a/test/application/search/SearchFormTest.php b/test/application/search/SearchFormTest.php index 14e91471e8..97e5d59244 100644 --- a/test/application/search/SearchFormTest.php +++ b/test/application/search/SearchFormTest.php @@ -45,13 +45,16 @@ class SearchFormTest extends ItopDataTestCase public function testGetFields() { $oSearchForm = new SearchForm(); - $aFields = $oSearchForm->GetFields('Contact', 'Contact'); + $oSearch = \DBSearch::FromOQL("SELECT Contact"); + $aFields = $oSearchForm->GetFields(new \DBObjectSet($oSearch)); $this->debug(json_encode($aFields, JSON_PRETTY_PRINT)); $this->assertCount(7, $aFields['zlist']); + $oSearchForm = new SearchForm(); $oSearch = \DBSearch::FromOQL("SELECT Contact AS C WHERE C.status = 'active'"); - $aFields = $oSearchForm->GetFields($oSearch->GetClass(), $oSearch->GetClassAlias()); + $aFields = $oSearchForm->GetFields(new \DBObjectSet($oSearch)); $this->debug(json_encode($aFields, JSON_PRETTY_PRINT)); + $this->assertCount(4, $aFields['others']); } /** @@ -66,7 +69,9 @@ class SearchFormTest extends ItopDataTestCase $oSearchForm = new SearchForm(); try { - $aCriterion = $oSearchForm->GetCriterion(\DBSearch::FromOQL($sOQL)); + $oSearch = \DBSearch::FromOQL($sOQL); + $aFields = $oSearchForm->GetFields(new \DBObjectSet($oSearch)); + $aCriterion = $oSearchForm->GetCriterion($oSearch, $aFields); } catch (\OQLException $e) { $this->assertTrue(false); @@ -89,6 +94,8 @@ class SearchFormTest extends ItopDataTestCase array('OQL' => "SELECT Contact WHERE status = 'active' AND name LIKE 'toto%'", 1), array('OQL' => "SELECT Contact WHERE status = 'active' AND org_id = 3", 1), array('OQL' => "SELECT Contact WHERE status IN ('active', 'inactive')", 1), + array('OQL' => "SELECT Contact WHERE status NOT IN ('active')", 1), + array('OQL' => "SELECT Contact WHERE status NOT IN ('active', 'inactive')", 1), array('OQL' => "SELECT Contact WHERE status = 'active' OR name LIKE 'toto%'", 2), array('OQL' => "SELECT UserRequest WHERE DATE_SUB(NOW(), INTERVAL 14 DAY) < start_date", 1), array('OQL' => "SELECT UserRequest WHERE start_date > '2017-01-01' AND '2018-01-01' >= start_date", 1),