Advanced Search

SVN:b1162[5392]
This commit is contained in:
Eric Espié
2018-03-08 13:49:00 +00:00
parent e9bcd170c0
commit fa60322ff1
8 changed files with 384 additions and 2 deletions

View File

@@ -448,6 +448,7 @@ class BinaryExpression extends Expression
public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
$bReverseOperator = false;
$oLeftExpr = $this->GetLeftExpr();
$oRightExpr = $this->GetRightExpr();
if ($oLeftExpr instanceof FieldExpression && $oRightExpr instanceof FieldExpression)
@@ -462,6 +463,7 @@ class BinaryExpression extends Expression
if ($oRightExpr instanceof FieldExpression)
{
$oAttDef = $oRightExpr->GetAttDef($oSearch->GetJoinedClasses());
$bReverseOperator = true;
}
if (is_null($oAttDef))
@@ -474,7 +476,33 @@ class BinaryExpression extends Expression
$aCriteria = array_merge($aCriteriaLeft, $aCriteriaRight);
$aCriteria['operator'] = $this->GetOperator();
if ($bReverseOperator)
{
// switch left and right expressions so reverse the operator
// Note that the operation is the same so < becomes > and not >=
switch ($this->GetOperator())
{
case '>':
$aCriteria['operator'] = '<';
break;
case '<':
$aCriteria['operator'] = '>';
break;
case '>=':
$aCriteria['operator'] = '<=';
break;
case '<=':
$aCriteria['operator'] = '>=';
break;
default:
$aCriteria['operator'] = $this->GetOperator();
break;
}
}
else
{
$aCriteria['operator'] = $this->GetOperator();
}
$aCriteria['oql'] = $this->Render($aArgs, $bRetrofitParams);
return $aCriteria;

81
pages/ajax.searchform.php Normal file
View File

@@ -0,0 +1,81 @@
<?php
/**
* Copyright (C) 2010-2018 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*
*/
use Combodo\iTop\Application\Search\AjaxSearchException;
use Combodo\iTop\Application\Search\CriterionParser;
require_once('../approot.inc.php');
require_once(APPROOT.'/application/application.inc.php');
require_once(APPROOT.'/application/webpage.class.inc.php');
require_once(APPROOT.'/application/ajaxwebpage.class.inc.php');
require_once(APPROOT.'/application/startup.inc.php');
require_once(APPROOT.'/application/user.preferences.class.inc.php');
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
require_once(APPROOT.'/sources/application/search/ajaxsearchexception.class.inc.php');
require_once(APPROOT.'/sources/application/search/criterionparser.class.inc.php');
try
{
if (LoginWebPage::EXIT_CODE_OK != LoginWebPage::DoLoginEx(null /* any portal */, false, LoginWebPage::EXIT_RETURN))
{
throw new SecurityException('You must be logged in');
}
$sParams = stripslashes(utils::ReadParam('params', '', false, 'raw_data'));
if (!$sParams)
{
throw new AjaxSearchException("Invalid query (empty filter)", 400);
}
$oPage = new ajax_page("");
$oPage->no_cache();
$oPage->SetContentType('text/html');
$aParams = json_decode($sParams, true);
$sOQL = CriterionParser::Parse($aParams['base_oql'], $aParams['criterion']);
$oFilter = DBSearch::FromOQL($sOQL);
$oDisplayBlock = new DisplayBlock($oFilter, 'list', false);
$aExtraParams['display_limit'] = true;
$aExtraParams['truncated'] = true;
$oDisplayBlock->RenderContent($oPage, $aExtraParams);
$oPage->output();
} catch (AjaxSearchException $e)
{
http_response_code($e->getCode());
// note: transform to cope with XSS attacks
echo htmlentities($e->GetMessage(), ENT_QUOTES, 'utf-8');
IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString());
} catch (SecurityException $e)
{
http_response_code(403);
// note: transform to cope with XSS attacks
echo htmlentities($e->GetMessage(), ENT_QUOTES, 'utf-8');
IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString());
} catch (Exception $e)
{
http_response_code(500);
// note: transform to cope with XSS attacks
echo htmlentities($e->GetMessage(), ENT_QUOTES, 'utf-8');
IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString());
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* Copyright (C) 2010-2018 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*
*/
/**
* Created by PhpStorm.
* User: Eric
* Date: 08/03/2018
* Time: 11:18
*/
namespace Combodo\iTop\Application\Search;
class AjaxSearchException extends \Exception
{
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* Copyright (C) 2010-2018 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*
*/
/**
* Created by PhpStorm.
* User: Eric
* Date: 08/03/2018
* Time: 14:47
*/
namespace Combodo\iTop\Application\Search;
class CriterionConversion
{
}

View File

@@ -0,0 +1,114 @@
<?php
/**
* Copyright (C) 2010-2018 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*
*/
/**
* Created by PhpStorm.
* User: Eric
* Date: 08/03/2018
* Time: 11:25
*/
namespace Combodo\iTop\Application\Search;
use DBObjectSearch;
use IssueLog;
use OQLException;
class CriterionParser
{
/**
* @param $sBaseOql
* @param $aCriterion
*
* @return string
*/
public static function Parse($sBaseOql, $aCriterion)
{
$aExpression = array();
$aOr = $aCriterion['or'];
foreach($aOr as $aAndList)
{
$sExpression = self::ParseAndList($aAndList['and']);
if (!empty($sExpression))
{
$aExpression[] = $sExpression;
}
}
if (empty($aExpression))
{
return $sBaseOql;
}
// Sanitize the base OQL
if (strpos($sBaseOql, ' WHERE '))
{
try
{
$oSearch = DBObjectSearch::FromOQL($sBaseOql);
$oSearch->ResetCondition();
$sBaseOql = $oSearch->ToOQL();
} catch (OQLException $e)
{
IssueLog::Error($e->getMessage());
}
}
return $sBaseOql.' WHERE '.implode(" OR ", $aExpression).'';
}
private static function ParseAndList($aAnd)
{
$aExpression = array();
foreach($aAnd as $aCriteria)
{
$aExpression[] = self::ParseCriteria($aCriteria);
}
if (empty($aExpression))
{
return '';
}
return '('.implode(" AND ", $aExpression).')';
}
private static function ParseCriteria($aCriteria)
{
if (!empty($aCriteria['oql']))
{
return $aCriteria['oql'];
}
// TODO Manage more complicated case
$aRef = explode('.', $aCriteria['ref']);
$sRef = '`'.$aRef[0].'`.`'.$aRef[1].'`';
$sOperator = $aCriteria['operator'];
$sValue = $aCriteria['values'][0]['value'];
return "({$sRef} {$sOperator} '{$sValue}')";
}
}

View File

@@ -112,6 +112,7 @@ class SearchForm
$aSearchParams = array(
'criterion_outer_selector' => "#fs_{$sSearchFormId}_criterion_outer",
'endpoint' => utils::GetAbsoluteUrlAppRoot().'pages/ajax.searchform.php',
'search' => array(
'fields' => $aFields,
'criterion' => $aCriterion,

View File

@@ -0,0 +1,86 @@
<?php
/**
* Copyright (C) 2010-2018 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*
*/
/**
* Created by PhpStorm.
* User: Eric
* Date: 08/03/2018
* Time: 11:28
*/
namespace Combodo\iTop\Test\UnitTest\Application\Search;
use Combodo\iTop\Application\Search\CriterionParser;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
class CriterionParserTest extends ItopDataTestCase
{
/**
* @throws Exception
*/
protected function setUp()
{
parent::setUp();
require_once(APPROOT."sources/application/search/criterionparser.class.inc.php");
}
public function testParse()
{
$sBaseOql = 'SELECT UserRequest';
$aCriterion = json_decode('{
"or": [
{
"and": [
{
"ref": "UserRequest.start_date",
"values": [
{
"value": "2017-01-01",
"label": "2017-01-01 00:00:00"
}
],
"operator": ">",
"oql": ""
},
{
"ref": "UserRequest.start_date",
"values": [
{
"value": "2018-01-01",
"label": "2018-01-01 00:00:00"
}
],
"operator": "<",
"oql": "(`UserRequest`.`start_date` < \'2018-01-01\')"
}
]
}
]
}
', true);
$sOQL = CriterionParser::Parse($sBaseOql, $aCriterion);
$this->debug($sOQL);
$this->markTestIncomplete();
}
}

View File

@@ -59,6 +59,8 @@ class SearchFormTest extends ItopDataTestCase
public function testGetCriterion($sOQL, $iOrCount)
{
$aCriterion = SearchForm::GetCriterion(\DBObjectSearch::FromOQL($sOQL));
$aRes = array('base_oql' => $sOQL, 'criterion' => $aCriterion);
$this->debug(json_encode($aRes));
$this->debug($sOQL);
$this->debug(json_encode($aCriterion, JSON_PRETTY_PRINT));
$this->assertCount($iOrCount, $aCriterion['or']);
@@ -75,7 +77,7 @@ class SearchFormTest extends ItopDataTestCase
array('OQL' => "SELECT Contact WHERE status 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 start_date < '2018-01-01'", 1),
array('OQL' => "SELECT UserRequest WHERE start_date > '2017-01-01' AND '2018-01-01' >= start_date", 1),
);
}
}