Advanced Search: Dates between

SVN:b1162[5432]
This commit is contained in:
Eric Espié
2018-03-15 13:35:35 +00:00
parent 6ef31b7983
commit e48e4e7139
6 changed files with 360 additions and 43 deletions

View File

@@ -23,8 +23,11 @@
namespace Combodo\iTop\Application\Search\CriterionConversion;
use AttributeDateTime;
use AttributeDefinition;
use Combodo\iTop\Application\Search\CriterionConversionAbstract;
use Combodo\iTop\Application\Search\SearchForm;
use DateInterval;
class CriterionToOQL extends CriterionConversionAbstract
{
@@ -51,6 +54,8 @@ class CriterionToOQL extends CriterionConversionAbstract
self::OP_ENDS_WITH => 'EndsWithToOql',
self::OP_EMPTY => 'EmptyToOql',
self::OP_NOT_EMPTY => 'NotEmptyToOql',
self::OP_BETWEEN_DAYS => 'BetweenDaysToOql',
self::OP_BETWEEN_HOURS => 'BetweenHoursToOql',
self::OP_IN => 'InToOql',
self::OP_ALL => 'AllToOql',
);
@@ -59,7 +64,7 @@ class CriterionToOQL extends CriterionConversionAbstract
{
$sFct = $aMappedOperators[$sOperator];
return self::$sFct($sRef, $sOperator, $aCriteria);
return self::$sFct($sRef, $aCriteria);
}
$sValue = self::GetValue(self::GetValues($aCriteria), 0);
@@ -73,6 +78,7 @@ class CriterionToOQL extends CriterionConversionAbstract
{
return array();
}
return $aCriteria['values'];
}
@@ -86,10 +92,11 @@ class CriterionToOQL extends CriterionConversionAbstract
{
return null;
}
return $aValues[$iIndex]['value'];
}
protected static function ContainsToOql($sRef, $sOperator, $aCriteria)
protected static function ContainsToOql($sRef, $aCriteria)
{
$aValues = self::GetValues($aCriteria);
$sValue = self::GetValue($aValues, 0);
@@ -97,7 +104,7 @@ class CriterionToOQL extends CriterionConversionAbstract
return "({$sRef} LIKE '%{$sValue}%')";
}
protected static function StartsWithToOql($sRef, $sOperator, $aCriteria)
protected static function StartsWithToOql($sRef, $aCriteria)
{
$aValues = self::GetValues($aCriteria);
$sValue = self::GetValue($aValues, 0);
@@ -105,7 +112,7 @@ class CriterionToOQL extends CriterionConversionAbstract
return "({$sRef} LIKE '{$sValue}%')";
}
protected static function EndsWithToOql($sRef, $sOperator, $aCriteria)
protected static function EndsWithToOql($sRef, $aCriteria)
{
$aValues = self::GetValues($aCriteria);
$sValue = self::GetValue($aValues, 0);
@@ -113,25 +120,26 @@ class CriterionToOQL extends CriterionConversionAbstract
return "({$sRef} LIKE '%{$sValue}')";
}
protected static function EmptyToOql($sRef, $sOperator, $aCriteria)
protected static function EmptyToOql($sRef, $aCriteria)
{
return "({$sRef} = '')";
}
protected static function NotEmptyToOql($sRef, $sOperator, $aCriteria)
protected static function NotEmptyToOql($sRef, $aCriteria)
{
return "({$sRef} != '')";
}
protected static function InToOql($sRef, $sOperator, $aCriteria)
protected static function InToOql($sRef, $aCriteria)
{
$sAttCode = $aCriteria['code'];
$sClass = $aCriteria['class'];
$aValues = $aCriteria['values'];
$aValues = self::GetValues($aCriteria);
if (count($aValues) == 0)
{
return "({$sRef} = '')";
// Ignore when nothing is selected
return "1";
}
try
@@ -143,17 +151,21 @@ class CriterionToOQL extends CriterionConversionAbstract
$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))
// Can't invert the test if NULL is allowed
if (!$oAttDef->IsNullAllowed())
{
foreach($aValues as $aValue)
$aAllowedValues = $aAllowedValues['values'];
// more selected values than remaining so use NOT IN
if (count($aValues) > (count($aAllowedValues) / 2))
{
unset($aAllowedValues[$aValue['value']]);
}
$sInList = implode(',', array_keys($aAllowedValues));
foreach($aValues as $aValue)
{
unset($aAllowedValues[$aValue['value']]);
}
$sInList = implode("','", array_keys($aAllowedValues));
return "({$sRef} NOT IN ($sInList))";
return "({$sRef} NOT IN ('$sInList'))";
}
}
}
}
@@ -166,17 +178,104 @@ class CriterionToOQL extends CriterionConversionAbstract
{
$aInValues[] = $aValue['value'];
}
$sInList = implode(',', $aInValues);
$sInList = implode("','", $aInValues);
if (count($aInValues) == 1)
{
return "({$sRef} = '$sInList')";
}
return "({$sRef} IN ($sInList))";
return "({$sRef} IN ('$sInList'))";
}
protected static function AllToOql($sRef, $sOperator, $aCriteria)
protected static function BetweenDaysToOql($sRef, $aCriteria)
{
$aOQL = array();
$aValues = self::GetValues($aCriteria);
if (count($aValues) != 2)
{
return "1";
}
$oFormat = AttributeDateTime::GetFormat();
$sStartDate = $aValues[0]['value'];
if (!empty($sStartDate))
{
$oDate = $oFormat->parse($sStartDate);
$sStartDate = $oDate->format(AttributeDateTime::GetSQLFormat());
$aOQL[] = "({$sRef} >= '$sStartDate')";
}
$sEndDate = $aValues[1]['value'];
if (!empty($sEndDate))
{
$oDate = $oFormat->parse($sEndDate);
if ($aCriteria['widget'] == AttributeDefinition::SEARCH_WIDGET_TYPE_DATE_TIME)
{
$oDate->add(DateInterval::createFromDateString('1 day'));
$sEndDate = $oDate->format(AttributeDateTime::GetSQLFormat());
$aOQL[] = "({$sRef} < '$sEndDate')";
}
else
{
$sEndDate = $oDate->format(AttributeDateTime::GetSQLFormat());
$aOQL[] = "({$sRef} <= '$sEndDate')";
}
}
$sOQL = implode(' AND ', $aOQL);
if (empty($sOQL))
{
$sOQL = "1";
}
return $sOQL;
}
protected static function BetweenHoursToOql($sRef, $aCriteria)
{
$aOQL = array();
$aValues = self::GetValues($aCriteria);
if (count($aValues) != 2)
{
return "1";
}
$oFormat = AttributeDateTime::GetFormat();
$sStartDate = $aValues[0]['value'];
if (!empty($sStartDate))
{
$oDate = $oFormat->parse($sStartDate);
$sStartDate = $oDate->format(AttributeDateTime::GetSQLFormat());
$aOQL[] = "({$sRef} >= '$sStartDate')";
}
$sEndDate = $aValues[1]['value'];
if (!empty($sEndDate))
{
$oDate = $oFormat->parse($sEndDate);
$oDate->add(DateInterval::createFromDateString('1 second'));
$sEndDate = $oDate->format(AttributeDateTime::GetSQLFormat());
$aOQL[] = "({$sRef} < '$sEndDate')";
}
$sOQL = implode(' AND ', $aOQL);
if (empty($sOQL))
{
$sOQL = "1";
}
return $sOQL;
}
protected static function AllToOql($sRef, $aCriteria)
{
return "1";
}

View File

@@ -25,8 +25,11 @@
namespace Combodo\iTop\Application\Search\CriterionConversion;
use AttributeDateTime;
use AttributeDefinition;
use Combodo\iTop\Application\Search\CriterionConversionAbstract;
use DateInterval;
use DateTime;
class CriterionToSearchForm extends CriterionConversionAbstract
{
@@ -67,12 +70,158 @@ class CriterionToSearchForm extends CriterionConversionAbstract
// Regroup criterion by variable name
usort($aAndCriterion, function ($a, $b) {
return strcmp($a['ref'], $b['ref']);
$iRefCmp = strcmp($a['ref'], $b['ref']);
if ($iRefCmp != 0) return $iRefCmp;
$iOpCmp = strcmp($a['operator'], $b['operator']);
return $iOpCmp;
});
return $aAndCriterion;
$aMergeFctByWidget = array(
AttributeDefinition::SEARCH_WIDGET_TYPE_DATE => 'MergeDate',
AttributeDefinition::SEARCH_WIDGET_TYPE_DATE_TIME => 'MergeDateTime',
);
$aPrevCriterion = null;
$aMergedCriterion = array();
foreach($aAndCriterion as $aCurrCriterion)
{
if (!is_null($aPrevCriterion))
{
if (strcmp($aPrevCriterion['ref'], $aCurrCriterion['ref']) == 0)
{
// Same attribute, try to merge
if (array_key_exists('widget', $aCurrCriterion))
{
if (array_key_exists($aCurrCriterion['widget'], $aMergeFctByWidget))
{
$sFct = $aMergeFctByWidget[$aCurrCriterion['widget']];
$aPrevCriterion = self::$sFct($aPrevCriterion, $aCurrCriterion, $aMergedCriterion);
continue;
}
}
}
$aMergedCriterion[] = $aPrevCriterion;
}
$aPrevCriterion = $aCurrCriterion;
}
if (!is_null($aPrevCriterion))
{
$aMergedCriterion[] = $aPrevCriterion;
}
return $aMergedCriterion;
}
/**
* @param $aPrevCriterion
* @param $aCurrCriterion
* @param $aMergedCriterion
*
* @return null
* @throws \Exception
*/
protected static function MergeDate($aPrevCriterion, $aCurrCriterion, &$aMergedCriterion)
{
$sPrevOperator = $aPrevCriterion['operator'];
$sCurrOperator = $aCurrCriterion['operator'];
if ((($sPrevOperator != '<') && ($sPrevOperator != '<=')) || (($sCurrOperator != '>') && ($sCurrOperator != '>=')))
{
$aMergedCriterion[] = $aPrevCriterion;
return $aCurrCriterion;
}
// Merge into 'between' operation.
// The ends of the interval are included
$aCurrCriterion['operator'] = 'between_days';
$sFormat = AttributeDateTime::GetFormat()->ToMomentJS();
$sLastDate = $aPrevCriterion['values'][0]['value'];
if ($sPrevOperator == '<')
{
// previous day to include ends
$oDate = new DateTime($sLastDate);
$oDate->sub(DateInterval::createFromDateString('1 day'));
$sLastDate = $oDate->format($sFormat);
}
$sFirstDate = $aCurrCriterion['values'][0]['value'];
if ($sCurrOperator == '>')
{
// next day to include ends
$oDate = new DateTime($sFirstDate);
$oDate->add(DateInterval::createFromDateString('1 day'));
$sFirstDate = $oDate->format($sFormat);
}
$aCurrCriterion['values'] = array();
$aCurrCriterion['values'][] = array('value' => $sFirstDate, 'label' => $sFirstDate);
$aCurrCriterion['values'][] = array('value' => $sLastDate, 'label' => $sLastDate);
$aCurrCriterion['oql'] = "({$aPrevCriterion['oql']} AND {$aCurrCriterion['oql']})";
$aMergedCriterion[] = $aCurrCriterion;
return null;
}
protected static function MergeDateTime($aPrevCriterion, $aCurrCriterion, &$aMergedCriterion)
{
$sPrevOperator = $aPrevCriterion['operator'];
$sCurrOperator = $aCurrCriterion['operator'];
if ((($sPrevOperator != '<') && ($sPrevOperator != '<=')) || (($sCurrOperator != '>') && ($sCurrOperator != '>=')))
{
$aMergedCriterion[] = $aPrevCriterion;
return $aCurrCriterion;
}
// Merge into 'between' operation.
// The ends of the interval are included
$sLastDate = $aPrevCriterion['values'][0]['value'];
$sFirstDate = $aCurrCriterion['values'][0]['value'];
$oDate = new DateTime($sLastDate);
if ((strpos($sFirstDate, '00:00:00') != false) && (strpos($sLastDate, '00:00:00') != false))
{
$aCurrCriterion['operator'] = 'between_days';
$sInterval = '1 day';
}
else
{
$aCurrCriterion['operator'] = 'between_hours';
$sInterval = '1 second';
}
if ($sPrevOperator == '<')
{
// previous day/second to include ends
$oDate->sub(DateInterval::createFromDateString($sInterval));
}
$sLastDate = $oDate->format(AttributeDateTime::GetSQLFormat());
$sLastDate = AttributeDateTime::GetFormat()->Format($sLastDate);
$oDate = new DateTime($sFirstDate);
if ($sCurrOperator == '>')
{
// next day/second to include ends
$oDate->add(DateInterval::createFromDateString($sInterval));
}
$sFirstDate = $oDate->format(AttributeDateTime::GetSQLFormat());
$sFirstDate = AttributeDateTime::GetFormat()->Format($sFirstDate);
$aCurrCriterion['values'] = array();
$aCurrCriterion['values'][] = array('value' => $sFirstDate, 'label' => $sFirstDate);
$aCurrCriterion['values'][] = array('value' => $sLastDate, 'label' => $sLastDate);
$aCurrCriterion['oql'] = "({$aPrevCriterion['oql']} AND {$aCurrCriterion['oql']})";
$aMergedCriterion[] = $aCurrCriterion;
return null;
}
protected static function TextToSearchForm($aCriteria, $aFields)
{
$sOperator = $aCriteria['operator'];
@@ -115,6 +264,14 @@ class CriterionToSearchForm extends CriterionConversionAbstract
protected static function EnumToSearchForm($aCriteria, $aFields)
{
$sOperator = $aCriteria['operator'];
if ($sOperator == '=')
{
$aCriteria['operator'] = 'IN';
}
if ($sOperator != 'NOT IN')
{
return $aCriteria;
}
$sRef = $aCriteria['ref'];
$aValues = $aCriteria['values'];
if (array_key_exists($sRef, $aFields))
@@ -126,26 +283,21 @@ class CriterionToSearchForm extends CriterionConversionAbstract
}
}
switch (true)
if (isset($aAllowedValues))
{
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;
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';
}
return $aCriteria;

View File

@@ -32,6 +32,8 @@ abstract class CriterionConversionAbstract
const OP_EMPTY = 'empty';
const OP_NOT_EMPTY = 'not_empty';
const OP_IN = 'IN';
const OP_BETWEEN_DAYS = 'between_days';
const OP_BETWEEN_HOURS = 'between_hours';
const OP_ALL = 'all';
}

View File

@@ -24,6 +24,7 @@ namespace Combodo\iTop\Application\Search;
use ApplicationContext;
use AttributeDateTime;
use AttributeDefinition;
use CMDBObjectSet;
use Combodo\iTop\Application\Search\CriterionConversion\CriterionToSearchForm;
@@ -149,6 +150,7 @@ class SearchForm
'result_list_outer_selector' => "#{$aExtraParams['table_id']}",
'data_config_list_selector' => null, //this one will be set just bellow, it mean to tell the widget where to find the initial list configuration
'endpoint' => utils::GetAbsoluteUrlAppRoot().'pages/ajax.searchform.php',
'date_format' => AttributeDateTime::GetFormat()->ToMomentJS(),
'list_params' => $aListParams,
'search' => array(
'fields' => $aFields,

View File

@@ -30,6 +30,7 @@ namespace Combodo\iTop\Test\UnitTest\Application\Search;
use Combodo\iTop\Application\Search\CriterionConversion\CriterionToOQL;
use Combodo\iTop\Application\Search\CriterionConversion\CriterionToSearchForm;
use Combodo\iTop\Application\Search\CriterionParser;
use Combodo\iTop\Application\Search\SearchForm;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
@@ -293,4 +294,62 @@ class CriterionConversionTest extends ItopDataTestCase
),
);
}
/**
* @dataProvider OqlProvider
* @throws \OQLException
*/
function testOqlToForlSearchToOql($sOQL)
{
$oSearchForm = new SearchForm();
$oSearch = \DBSearch::FromOQL($sOQL);
$aFields = $oSearchForm->GetFields(new \DBObjectSet($oSearch));
$aCriterion = $oSearchForm->GetCriterion($oSearch, $aFields);
$this->debug($sOQL);
$aAndCriterion = $aCriterion['or'][0]['and'];
$aNewCriterion = array();
foreach($aAndCriterion as $aCriteria)
{
if (($aCriteria['widget'] == \AttributeDefinition::SEARCH_WIDGET_TYPE_STRING)
|| ($aCriteria['widget'] == \AttributeDefinition::SEARCH_WIDGET_TYPE_DATE_TIME)
|| ($aCriteria['widget'] == \AttributeDefinition::SEARCH_WIDGET_TYPE_ENUM))
{
unset($aCriteria['oql']);
$aField = $aFields['zlist'][$aCriteria['ref']];
$aCriteria['code'] = $aField['code'];
$aCriteria['class'] = $aField['class'];
}
$aNewCriterion[] = $aCriteria;
}
$this->debug($aNewCriterion);
$aCriterion['or'][0]['and'] = $aNewCriterion;
$oSearch->ResetCondition();
$oFilter = CriterionParser::Parse($oSearch->ToOQL(), $aCriterion);
$this->debug($oFilter->ToOQL());
$this->assertTrue(true);
}
function OqlProvider()
{
return array(
array('OQL' => "SELECT Contact WHERE status = 'active'"),
array('OQL' => "SELECT Contact WHERE status = 'active' AND name LIKE 'toto%'"),
array('OQL' => "SELECT Contact WHERE status = 'active' AND org_id = 3"),
array('OQL' => "SELECT Contact WHERE status IN ('active', 'inactive')"),
array('OQL' => "SELECT Contact WHERE status NOT IN ('active')"),
array('OQL' => "SELECT UserRequest WHERE DATE_SUB(NOW(), INTERVAL 14 DAY) < start_date"),
array('OQL' => "SELECT UserRequest WHERE start_date > '2017-01-01 00:00:00' AND '2018-01-01 00:00:00' >= start_date"),
array('OQL' => "SELECT UserRequest WHERE start_date > '2017-01-01 00:00:00' AND status = 'active' AND org_id = 3 AND '2018-01-01 00:00:00' >= start_date"),
array('OQL' => "SELECT UserRequest WHERE start_date >= '2017-01-01 00:00:00' AND '2017-01-01 00:00:00' >= start_date"),
array('OQL' => "SELECT UserRequest WHERE start_date >= '2017-01-01 00:00:00' AND '2017-01-01 01:00:00' > start_date"),
array('OQL' => "SELECT UserRequest WHERE start_date >= '2017-01-01 00:00:00' AND '2017-01-02 00:00:00' > start_date"),
);
}
}

View File

@@ -98,8 +98,11 @@ class SearchFormTest extends ItopDataTestCase
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),
array('OQL' => "SELECT UserRequest WHERE start_date > '2017-01-01' AND status = 'active' AND org_id = 3 AND '2018-01-01' >= start_date", 1),
array('OQL' => "SELECT UserRequest WHERE start_date > '2017-01-01 00:00:00' AND '2018-01-01 00:00:00' >= start_date", 1),
array('OQL' => "SELECT UserRequest WHERE start_date > '2017-01-01 00:00:00' AND status = 'active' AND org_id = 3 AND '2018-01-01 00:00:00' >= start_date", 1),
array('OQL' => "SELECT UserRequest WHERE start_date >= '2017-01-01 00:00:00' AND '2017-01-01 00:00:00' >= start_date", 1),
array('OQL' => "SELECT UserRequest WHERE start_date >= '2017-01-01 00:00:00' AND '2017-01-01 01:00:00' > start_date", 1),
array('OQL' => "SELECT UserRequest WHERE start_date >= '2017-01-01 00:00:00' AND '2017-01-02 00:00:00' > start_date", 1),
);
}
}