* */ namespace Combodo\iTop\Application\Search\CriterionConversion; use AttributeDate; use AttributeDateTime; use AttributeDefinition; use AttributeEnum; use AttributeExternalKey; use Combodo\iTop\Application\Search\AjaxSearchException; use Combodo\iTop\Application\Search\CriterionConversionAbstract; use Combodo\iTop\Application\Search\SearchForm; use DBObjectSearch; use Exception; use Expression; use MetaModel; use utils; class CriterionToOQL extends CriterionConversionAbstract { public static function Convert($oSearch, $aCriteria) { if (!empty($aCriteria['oql'])) { return $aCriteria['oql']; } $aRef = explode('.', $aCriteria['ref']); $aCriteria['code'] = $aRef[1]; for ($i = 0; $i < count($aRef); $i++) { $aRef[$i] = '`'.$aRef[$i].'`'; } $sRef = implode('.', $aRef); if (isset($aCriteria['operator'])) { $sOperator = $aCriteria['operator']; } else { $sOperator = self::OP_ALL; } $aMappedOperators = [ self::OP_CONTAINS => 'ContainsToOql', self::OP_EQUALS => 'EqualsToOql', self::OP_STARTS_WITH => 'StartsWithToOql', self::OP_ENDS_WITH => 'EndsWithToOql', self::OP_EMPTY => 'EmptyToOql', self::OP_NOT_EMPTY => 'NotEmptyToOql', self::OP_BETWEEN_DATES => 'BetweenDatesToOql', self::OP_BETWEEN => 'BetweenToOql', self::OP_REGEXP => 'RegexpToOql', self::OP_IN => 'InToOql', self::OP_MATCHES => 'MatchesToOql', self::OP_ALL => 'AllToOql', ]; if (array_key_exists($sOperator, $aMappedOperators)) { $sFct = $aMappedOperators[$sOperator]; return self::$sFct($oSearch, $sRef, $aCriteria); } $sValue = self::GetValue(self::GetValues($aCriteria), 0); return "({$sRef} {$sOperator} '{$sValue}')"; } private static function GetValues($aCriteria) { if (!array_key_exists('values', $aCriteria)) { return []; } return $aCriteria['values']; } private static function GetValue($aValues, $iIndex) { if (!array_key_exists($iIndex, $aValues)) { return null; } if (!array_key_exists('value', $aValues[$iIndex])) { return null; } return addslashes($aValues[$iIndex]['value']); } protected static function ContainsToOql($oSearch, $sRef, $aCriteria) { $aValues = self::GetValues($aCriteria); $sValue = self::GetValue($aValues, 0); if (!utils::StrLen($sValue)) { return "1"; } return "({$sRef} LIKE '%{$sValue}%')"; } protected static function StartsWithToOql($oSearch, $sRef, $aCriteria) { $aValues = self::GetValues($aCriteria); $sValue = self::GetValue($aValues, 0); if (!utils::StrLen($sValue)) { return "1"; } return "({$sRef} LIKE '{$sValue}%')"; } protected static function EndsWithToOql($oSearch, $sRef, $aCriteria) { $aValues = self::GetValues($aCriteria); $sValue = self::GetValue($aValues, 0); if (!utils::StrLen($sValue)) { return "1"; } return "({$sRef} LIKE '%{$sValue}')"; } protected static function EqualsToOql($oSearch, $sRef, $aCriteria) { $aValues = self::GetValues($aCriteria); $sValue = self::GetValue($aValues, 0); if (($aCriteria['widget'] == AttributeDefinition::SEARCH_WIDGET_TYPE_NUMERIC) && ($sValue === '0')) { return "({$sRef} = '0')"; } if (!utils::StrLen($sValue) && (!(isset($aCriteria['has_undefined'])) || !($aCriteria['has_undefined']))) { return "1"; } return "({$sRef} = '{$sValue}')"; } protected static function RegexpToOql($oSearch, $sRef, $aCriteria) { $aValues = self::GetValues($aCriteria); $sValue = self::GetValue($aValues, 0); if (!utils::StrLen($sValue)) { return "1"; } return "({$sRef} REGEXP '{$sValue}')"; } protected static function MatchesToOql($oSearch, $sRef, $aCriteria) { $aValues = self::GetValues($aCriteria); $aRawValues = []; $bHasUnDefined = isset($aCriteria['has_undefined']) ? $aCriteria['has_undefined'] : false; for ($i = 0; $i < count($aValues); $i++) { $sRawValue = self::GetValue($aValues, $i); if (!utils::StrLen($sRawValue)) { $bHasUnDefined = true; } else { $aRawValues[] = $sRawValue; } } // This allow to search for complete words if (!empty($aRawValues)) { $sValue = implode(' ', $aRawValues).' _'; } else { if ($bHasUnDefined) { return "({$sRef} = '')"; } return "1"; } if ($bHasUnDefined) { return "((({$sRef} MATCHES '{$sValue}') OR ({$sRef} = '')) AND 1)"; } return "({$sRef} MATCHES '{$sValue}')"; } protected static function EmptyToOql($oSearch, $sRef, $aCriteria) { if (isset($aCriteria['widget'])) { switch ($aCriteria['widget']) { case AttributeDefinition::SEARCH_WIDGET_TYPE_NUMERIC: case AttributeDefinition::SEARCH_WIDGET_TYPE_EXTERNAL_FIELD: case AttributeDefinition::SEARCH_WIDGET_TYPE_DATE: case AttributeDefinition::SEARCH_WIDGET_TYPE_DATE_TIME: return "ISNULL({$sRef})"; } } return "({$sRef} = '')"; } protected static function NotEmptyToOql($oSearch, $sRef, $aCriteria) { if (isset($aCriteria['widget'])) { switch ($aCriteria['widget']) { case AttributeDefinition::SEARCH_WIDGET_TYPE_NUMERIC: case AttributeDefinition::SEARCH_WIDGET_TYPE_EXTERNAL_FIELD: case AttributeDefinition::SEARCH_WIDGET_TYPE_DATE: case AttributeDefinition::SEARCH_WIDGET_TYPE_DATE_TIME: return "ISNULL({$sRef}) = 0"; } } return "({$sRef} != '')"; } /** * @param \DBObjectSearch $oSearch * @param string $sRef * @param array $aCriteria * * @return mixed|string * * @throws \CoreException * @throws \MissingQueryArgument * @throws \MySQLException * @throws \MySQLHasGoneAwayException */ protected static function InToOql($oSearch, $sRef, $aCriteria) { $sAttCode = $aCriteria['code']; $sClass = $aCriteria['class']; $aValues = self::GetValues($aCriteria); if (count($aValues) == 0) { // Ignore when nothing is selected return "1"; } $oAttDef = null; try { $aAttributeDefs = MetaModel::ListAttributeDefs($sClass); } catch (\CoreException $e) { return "1"; } if (array_key_exists($sAttCode, $aAttributeDefs)) { $oAttDef = $aAttributeDefs[$sAttCode]; } // Hierarchical keys $sHierarchicalKeyCode = false; $sTargetClass = ''; if (isset($oAttDef) && $oAttDef->IsExternalKey() && ($aCriteria['widget'] == AttributeDefinition::SEARCH_WIDGET_TYPE_HIERARCHICAL_KEY)) { if ($oAttDef instanceof AttributeExternalKey) { $sTargetClass = $oAttDef->GetTargetClass(); } else { /** @var AttributeExternalKey $oFinalAttDef */ $oFinalAttDef = $oAttDef->GetFinalAttDef(); $sTargetClass = $oFinalAttDef->GetTargetClass(); } try { $sHierarchicalKeyCode = MetaModel::IsHierarchicalClass($sTargetClass); } catch (\CoreException $e) { } } $sFilterOnUndefined = ''; if ($oAttDef instanceof AttributeEnum) { $aAllowedValues = SearchForm::GetFieldAllowedValues($oAttDef); if (array_key_exists('values', $aAllowedValues)) { // Can't invert the test if NULL is allowed if (!$oAttDef->IsNullAllowed()) { $aAllowedValues = $aAllowedValues['values']; if (count($aValues) == count($aAllowedValues)) { // All entries are selected return "1"; } // more selected values than remaining so use NOT IN else { if (count($aValues) > (count($aAllowedValues) / 2)) { foreach ($aValues as $aValue) { unset($aAllowedValues[$aValue['value']]); } $sInList = implode("','", array_keys($aAllowedValues)); return "({$sRef} NOT IN ('$sInList'))"; } } } // search for "undefined" for ($i = 0; $i < count($aValues); $i++) { $aValue = $aValues[$i]; if (isset($aValue['value']) && ($aValue['value'] === 'null')) { $sFilterOnUndefined = "ISNULL({$sRef})"; unset($aValues[$i]); break; } } } } if ($sHierarchicalKeyCode !== false) { // search for "undefined" for ($i = 0; $i < count($aValues); $i++) { $aValue = $aValues[$i]; if (isset($aValue['value']) && ($aValue['value'] === '0')) { $sFilterOnUndefined = "({$sRef} = '0')"; unset($aValues[$i]); break; } } } $aInValues = []; foreach ($aValues as $aValue) { $aInValues[] = $aValue['value']; } $sInList = implode("','", $aInValues); $sCondition = '1'; if (count($aInValues) == 1) { $sCondition = "({$sRef} = '$sInList')"; } elseif (count($aInValues) > 1) { $sCondition = "({$sRef} IN ('$sInList'))"; } // Hierarchical keys try { if (($sHierarchicalKeyCode !== false) && ($oSearch instanceof DBObjectSearch)) { // NOTE: The hierarchy does not work for unions for now. It'll be done with the full support of unions in search. // Add all the joins for hierarchical key $oFilter = new DBObjectSearch($sTargetClass); $sFilterAlias = $oFilter->GetClassAlias(); // Filter on hierarchy $sCondition = str_replace("$sRef", $sFilterAlias.'.id', $sCondition); $oCondition = Expression::FromOQL($sCondition); $oFilter->AddConditionExpression($oCondition); $oHKFilter = new DBObjectSearch($sTargetClass); $oHKFilter->AddCondition_PointingTo($oFilter, $sHierarchicalKeyCode, TREE_OPERATOR_BELOW); // Use the 'below' operator by default $oSearch->AddCondition_PointingTo($oHKFilter, $sAttCode); $oCriteria = $oSearch->GetCriteria(); $aArgs = MetaModel::PrepareQueryArguments([], $oSearch->GetInternalParams(), $oSearch->GetExpectedArguments()); $oSearch->ResetCondition(); $sCondition = $oCriteria->RenderExpression(false, $aArgs); } } catch (Exception $e) { } if (!empty($sFilterOnUndefined)) { if (count($aValues) === 0) { $sCondition = $sFilterOnUndefined; } else { // Add 'AND 1' to group the 'OR' inside an AND list for OQL parsing $sCondition = "(({$sCondition} OR {$sFilterOnUndefined}) AND 1)"; } } return $sCondition; } protected static function BetweenDatesToOql($oSearch, $sRef, $aCriteria) { $aOQL = []; $aValues = self::GetValues($aCriteria); if (count($aValues) != 2) { return "1"; } $sWidget = $aCriteria['widget']; if ($sWidget == AttributeDefinition::SEARCH_WIDGET_TYPE_DATE_TIME) { $sAttributeClass = AttributeDateTime::class; } else { $sAttributeClass = AttributeDate::class; } $oFormat = $sAttributeClass::GetFormat(); $sStartDate = $aValues[0]['value']; if (!empty($sStartDate)) { try { $oDate = $oFormat->parse($sStartDate); $sStartDate = $oDate->format($sAttributeClass::GetSQLFormat()); $aOQL[] = "({$sRef} >= '$sStartDate')"; } catch (Exception $e) { } } $sEndDate = $aValues[1]['value']; if (!empty($sEndDate)) { try { $oDate = $oFormat->parse($sEndDate); $sEndDate = $oDate->format($sAttributeClass::GetSQLFormat()); $aOQL[] = "({$sRef} <= '$sEndDate')"; } catch (Exception $e) { } } $sOQL = implode(' AND ', $aOQL); if (empty($sOQL)) { $sOQL = "1"; } return $sOQL; } /** * @param DBObjectSearch $oSearch * @param $sRef * @param $aCriteria * * @return string * @throws \Combodo\iTop\Application\Search\AjaxSearchException */ protected static function BetweenToOql($oSearch, $sRef, $aCriteria) { $aOQL = []; $aValues = self::GetValues($aCriteria); if (count($aValues) != 2) { return "1"; } if (isset($aValues[0]['value'])) { $sStartNum = trim($aValues[0]['value']); if (is_numeric($sStartNum)) { $aOQL[] = "({$sRef} >= '$sStartNum')"; } else { if (!empty($sStartNum)) { throw new AjaxSearchException("'$sStartNum' is not a numeric value", 400); } } } if (isset($aValues[1]['value'])) { $sEndNum = trim($aValues[1]['value']); if (is_numeric($sEndNum)) { $aOQL[] = "({$sRef} <= '$sEndNum')"; } else { if (!empty($sEndNum)) { throw new AjaxSearchException("'$sEndNum' is not a numeric value", 400); } } } $sOQL = implode(' AND ', $aOQL); if (empty($sOQL)) { $sOQL = "1"; } return $sOQL; } protected static function AllToOql($oSearch, $sRef, $aCriteria) { return "1"; } }