Advanced Search

SVN:b1162[5388]
This commit is contained in:
Eric Espié
2018-03-07 16:10:09 +00:00
parent 9de7b4ba35
commit e496ab06b2
6 changed files with 656 additions and 342 deletions

View File

@@ -45,6 +45,7 @@ require_once(APPROOT.'/application/ui.extkeywidget.class.inc.php');
require_once(APPROOT.'/application/ui.htmleditorwidget.class.inc.php');
require_once(APPROOT.'/application/datatable.class.inc.php');
require_once(APPROOT.'/sources/renderer/console/consoleformrenderer.class.inc.php');
require_once(APPROOT.'/sources/application/search/searchform.class.inc.php');
abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
{
@@ -1530,235 +1531,7 @@ EOF
public static function GetSearchForm(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array())
{
static $iSearchFormId = 0;
$bMultiSelect = false;
$oAppContext = new ApplicationContext();
$sHtml = '';
$numCols=4;
$sClassName = $oSet->GetFilter()->GetClass();
// Romain: temporarily removed the tab "OQL query" because it was not finalized
// (especially when used to add a link)
/*
$sHtml .= "<div class=\"mini_tabs\" id=\"mini_tabs{$iSearchFormId}\"><ul>
<li><a href=\"#\" onClick=\"$('div.mini_tab{$iSearchFormId}').toggle();$('#mini_tabs{$iSearchFormId} ul li a').toggleClass('selected');\">".Dict::S('UI:OQLQueryTab')."</a></li>
<li><a class=\"selected\" href=\"#\" onClick=\"$('div.mini_tab{$iSearchFormId}').toggle();$('#mini_tabs{$iSearchFormId} ul li a').toggleClass('selected');\">".Dict::S('UI:SimpleSearchTab')."</a></li>
</ul></div>\n";
*/
// Simple search form
if (isset($aExtraParams['currentId']))
{
$sSearchFormId = $aExtraParams['currentId'];
}
else
{
$iSearchFormId = $oPage->GetUniqueId();
$sSearchFormId = 'SimpleSearchForm'.$iSearchFormId;
$sHtml .= "<div id=\"ds_$sSearchFormId\" class=\"mini_tab{$iSearchFormId}\">\n";
}
// Check if the current class has some sub-classes
if (isset($aExtraParams['baseClass']))
{
$sRootClass = $aExtraParams['baseClass'];
}
else
{
$sRootClass = $sClassName;
}
$aSubClasses = MetaModel::GetSubclasses($sRootClass);
if (count($aSubClasses) > 0)
{
$aOptions = array();
$aOptions[MetaModel::GetName($sRootClass)] = "<option value=\"$sRootClass\">".MetaModel::GetName($sRootClass)."</options>\n";
foreach($aSubClasses as $sSubclassName)
{
$aOptions[MetaModel::GetName($sSubclassName)] = "<option value=\"$sSubclassName\">".MetaModel::GetName($sSubclassName)."</options>\n";
}
$aOptions[MetaModel::GetName($sClassName)] = "<option selected value=\"$sClassName\">".MetaModel::GetName($sClassName)."</options>\n";
ksort($aOptions);
$sContext = $oAppContext->GetForLink();
$sClassesCombo = "<select name=\"class\" onChange=\"ReloadSearchForm('$sSearchFormId', this.value, '$sRootClass', '$sContext')\">\n".implode('', $aOptions)."</select>\n";
}
else
{
$sClassesCombo = MetaModel::GetName($sClassName);
}
$oUnlimitedFilter = new DBObjectSearch($sClassName);
$sAction = (isset($aExtraParams['action'])) ? $aExtraParams['action'] : utils::GetAbsoluteUrlAppRoot().'pages/UI.php';
$sHtml .= "<form id=\"fs_{$sSearchFormId}\" action=\"{$sAction}\">\n"; // Don't use $_SERVER['SCRIPT_NAME'] since the form may be called asynchronously (from ajax.php)
$sHtml .= "<h2>".Dict::Format('UI:SearchFor_Class_Objects', $sClassesCombo)."</h2>\n";
$index = 0;
$sHtml .= "<div>\n";
$aMapCriteria = array();
$aList = MetaModel::GetZListItems($sClassName, 'standard_search');
$aConsts = $oSet->ListConstantFields(); // Some fields are constants based on the query/context
$sClassAlias = $oSet->GetFilter()->GetClassAlias();
foreach($aList as $sFilterCode)
{
//$oAppContext->Reset($sFilterCode); // Make sure the same parameter will not be passed twice
$sHtml .= '<div class="SearchAttribute" style="white-space: nowrap;padding:5px;display:inline-block;">';
$sFilterValue = isset($aConsts[$sClassAlias][$sFilterCode]) ? $aConsts[$sClassAlias][$sFilterCode] : '';
$sFilterValue = utils::ReadParam($sFilterCode, $sFilterValue, false, 'raw_data');
$sFilterOpCode = null; // Use the default 'loose' OpCode
if (empty($sFilterValue))
{
if (isset($aMapCriteria[$sFilterCode]))
{
if (count($aMapCriteria[$sFilterCode]) > 1)
{
$sFilterValue = Dict::S('UI:SearchValue:Mixed');
}
else
{
$sFilterValue = $aMapCriteria[$sFilterCode][0]['value'];
$sFilterOpCode = $aMapCriteria[$sFilterCode][0]['opcode'];
}
// Todo: Investigate...
if ($sFilterCode != 'company')
{
$oUnlimitedFilter->AddCondition($sFilterCode, $sFilterValue, $sFilterOpCode);
}
}
}
$oAttDef = MetaModel::GetAttributeDef($sClassName, $sFilterCode);
if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE))
{
$oKeyAttDef = $oAttDef->GetFinalAttDef();
$sKeyAttClass = $oKeyAttDef->GetHostClass();
$sKeyAttCode = $oKeyAttDef->GetCode();
$sTargetClass = $oKeyAttDef->GetTargetClass();
$oSearch = new DBObjectSearch($sTargetClass);
$oSearch->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true);
$oAllowedValues = new DBObjectSet($oSearch);
$iFieldSize = $oKeyAttDef->GetMaxSize();
$iMaxComboLength = $oKeyAttDef->GetMaximumComboLength();
$sHtml .= "<label>".MetaModel::GetFilterLabel($sKeyAttClass, $sKeyAttCode).":</label>&nbsp;";
$aExtKeyParams = $aExtraParams;
$aExtKeyParams['iFieldSize'] = $oKeyAttDef->GetMaxSize();
$aExtKeyParams['iMinChars'] = $oKeyAttDef->GetMinAutoCompleteChars();
$sHtml .= UIExtKeyWidget::DisplayFromAttCode($oPage, $sKeyAttCode, $sKeyAttClass, $oAttDef->GetLabel(), $oAllowedValues, $sFilterValue, $sSearchFormId.'search_'.$sFilterCode, false, $sFilterCode, '', $aExtKeyParams, true);
}
else
{
$aAllowedValues = MetaModel::GetAllowedValues_flt($sClassName, $sFilterCode, $aExtraParams);
if (is_null($aAllowedValues))
{
// Any value is possible, display an input box
$sHtml .= "<label>".MetaModel::GetFilterLabel($sClassName, $sFilterCode).":</label>&nbsp;<input class=\"textSearch\" name=\"$sFilterCode\" value=\"".htmlentities($sFilterValue, ENT_QUOTES, 'utf-8')."\"/>\n";
}
else
{
//Enum field, display a multi-select combo
$sValue = "<select class=\"multiselect\" size=\"1\" name=\"{$sFilterCode}[]\" multiple>\n";
$bMultiSelect = true;
//$sValue .= "<option value=\"\">".Dict::S('UI:SearchValue:Any')."</option>\n";
asort($aAllowedValues);
foreach($aAllowedValues as $key => $value)
{
if (is_array($sFilterValue) && in_array($key, $sFilterValue))
{
$sSelected = ' selected';
}
else if ($sFilterValue == $key)
{
$sSelected = ' selected';
}
else
{
$sSelected = '';
}
$sValue .= "<option value=\"$key\"$sSelected>$value</option>\n";
}
$sValue .= "</select>\n";
$sHtml .= "<label>".MetaModel::GetFilterLabel($sClassName, $sFilterCode).":</label>&nbsp;$sValue\n";
}
}
unset($aExtraParams[$sFilterCode]);
// Finally, add a tooltip if one is defined for this attribute definition
$sTip = $oAttDef->GetHelpOnSmartSearch();
if (strlen($sTip) > 0)
{
$sTip = addslashes($sTip);
$sTip = str_replace(array("\n", "\r"), " ", $sTip);
// :input does represent in form visible input (INPUT, SELECT, TEXTAREA)
$oPage->add_ready_script("$('form#fs_$sSearchFormId :input[name={$sFilterCode}]').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
}
$index++;
$sHtml .= '</div> ';
}
$sHtml .= "</div>\n";
$sHtml .= "<p align=\"right\"><input type=\"submit\" value=\"".Dict::S('UI:Button:Search')."\"></p>\n";
if (isset($aExtraParams['table_id']))
{
// Rename to avoid collisions...
$aExtraParams['_table_id_'] = $aExtraParams['table_id'];
unset($aExtraParams['table_id']);
}
foreach($aExtraParams as $sName => $sValue)
{
if (is_scalar($sValue))
{
$sHtml .= "<input type=\"hidden\" name=\"$sName\" value=\"".htmlentities($sValue, ENT_QUOTES, 'UTF-8')."\" />\n";
}
}
$sHtml .= "<input type=\"hidden\" name=\"class\" value=\"$sClassName\" />\n";
$sHtml .= "<input type=\"hidden\" name=\"dosearch\" value=\"1\" />\n";
$sHtml .= "<input type=\"hidden\" name=\"operation\" value=\"search_form\" />\n";
$sHtml .= $oAppContext->GetForForm();
$sHtml .= "</form>\n";
if (!isset($aExtraParams['currentId']))
{
$sHtml .= "</div><!-- Simple search form -->\n";
}
if ($bMultiSelect)
{
$aOptions = array(
'header' => true,
'checkAllText' => Dict::S('UI:SearchValue:CheckAll'),
'uncheckAllText' => Dict::S('UI:SearchValue:UncheckAll'),
'noneSelectedText' => Dict::S('UI:SearchValue:Any'),
'selectedText' => Dict::S('UI:SearchValue:NbSelected'),
'selectedList' => 1,
);
$sJSOptions = json_encode($aOptions);
$oPage->add_ready_script("$('.multiselect').multiselect($sJSOptions);");
}
/*
// OQL query builder
$sHtml .= "<div id=\"OQLQuery{$iSearchFormId}\" style=\"display:none\" class=\"mini_tab{$iSearchFormId}\">\n";
$sHtml .= "<h1>".Dict::S('UI:OQLQueryBuilderTitle')."</h1>\n";
$sHtml .= "<form id=\"formOQL{$iSearchFormId}\"><table style=\"width:80%;\"><tr style=\"vertical-align:top\">\n";
$sHtml .= "<td style=\"text-align:right\"><label>SELECT&nbsp;</label><select name=\"oql_class\">";
$aClasses = MetaModel::EnumChildClasses($sClassName, ENUM_CHILD_CLASSES_ALL);
$sSelectedClass = utils::ReadParam('oql_class', $sClassName, false, 'class');
$sOQLClause = utils::ReadParam('oql_clause', '', false, 'raw_data');
asort($aClasses);
foreach($aClasses as $sChildClass)
{
$sSelected = ($sChildClass == $sSelectedClass) ? 'selected' : '';
$sHtml.= "<option value=\"$sChildClass\" $sSelected>".MetaModel::GetName($sChildClass)."</option>\n";
}
$sHtml .= "</select>&nbsp;</td><td>\n";
$sHtml .= "<textarea name=\"oql_clause\" style=\"width:100%\">$sOQLClause</textarea></td></tr>\n";
$sHtml .= "<tr><td colspan=\"2\" style=\"text-align:right\"><input type=\"submit\" value=\"".Dict::S('UI:Button:Query')."\"></td></tr>\n";
$sHtml .= "<input type=\"hidden\" name=\"dosearch\" value=\"1\" />\n";
foreach($aExtraParams as $sName => $sValue)
{
if (is_scalar($sValue))
{
$sHtml .= "<input type=\"hidden\" name=\"$sName\" value=\"$sValue\" />\n";
}
}
$sHtml .= "<input type=\"hidden\" name=\"operation\" value=\"search_oql\" />\n";
$sHtml .= $oAppContext->GetForForm();
$sHtml .= "</table></form>\n";
$sHtml .= "</div><!-- OQL query form -->\n";
*/
return $sHtml;
return \Combodo\iTop\Application\Search\SearchForm::GetSearchForm($oPage, $oSet, $aExtraParams);
}
/**

View File

@@ -111,6 +111,8 @@ define('LINKSET_EDITMODE_ADDREMOVE', 4); // The "linked" objects can be added/re
*/
abstract class AttributeDefinition
{
const SEARCH_WIDGET_TYPE = 'raw';
public function GetType()
{
return Dict::S('Core:'.get_class($this));
@@ -122,6 +124,16 @@ abstract class AttributeDefinition
abstract public function GetEditClass();
/**
* Return the search widget type corresponding to this attribute
*
* @return string
*/
public function GetSearchType()
{
return static::SEARCH_WIDGET_TYPE;
}
protected $m_sCode;
private $m_aParams = array();
protected $m_sHostClass = '!undefined!';
@@ -2085,6 +2097,8 @@ class AttributeBoolean extends AttributeInteger
*/
class AttributeString extends AttributeDBField
{
const SEARCH_WIDGET_TYPE = 'string';
static public function ListExpectedParams()
{
return parent::ListExpectedParams();
@@ -3433,6 +3447,8 @@ class AttributeTemplateHTML extends AttributeText
*/
class AttributeEnum extends AttributeString
{
const SEARCH_WIDGET_TYPE = 'enum';
static public function ListExpectedParams()
{
return parent::ListExpectedParams();
@@ -4526,6 +4542,8 @@ class AttributeDeadline extends AttributeDateTime
*/
class AttributeExternalKey extends AttributeDBFieldVoid
{
const SEARCH_WIDGET_TYPE = 'external_key';
static public function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("targetclass", "is_null_allowed", "on_target_delete"));

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (c) 2010-2017 Combodo SARL
// Copyright (c) 2010-2018 Combodo SARL
//
// This file is part of iTop.
//
@@ -21,11 +21,12 @@ class MissingQueryArgument extends CoreException
{
}
abstract class Expression
{
/**
* Perform a deep clone (as opposed to "clone" which does copy a reference to the underlying objects)
**/
**/
public function DeepClone()
{
return unserialize(serialize($this));
@@ -38,6 +39,16 @@ abstract class Expression
// recursive rendering (aArgs used as input by default, or used as output if bRetrofitParams set to True
abstract public function Render(&$aArgs = null, $bRetrofitParams = false);
/**
* @param DBObjectSearch $oSearch
* @param array $aArgs
* @param bool $bRetrofitParams
* @param AttributeDefinition $oAttDef
*
* @return array parameters for the search form
*/
abstract public function Display($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null);
/**
* Recursively browse the expression tree
* @param Closure $callback
@@ -46,7 +57,7 @@ abstract class Expression
abstract public function Browse(Closure $callback);
abstract public function ApplyParameters($aArgs);
// recursively builds an array of class => fieldname
abstract public function ListRequiredFields();
@@ -54,10 +65,10 @@ abstract class Expression
abstract public function CollectUsedParents(&$aTable);
abstract public function IsTrue();
// recursively builds an array of [classAlias][fieldName] => value
abstract public function ListConstantFields();
public function RequiresField($sClass, $sFieldName)
{
// #@# todo - optimize : this is called quite often when building a single query !
@@ -71,6 +82,12 @@ abstract class Expression
return base64_encode($this->Render());
}
/**
* @param $sValue
*
* @return Expression
* @throws OQLException
*/
static public function unserialize($sValue)
{
return self::FromOQL(base64_decode($sValue));
@@ -119,22 +136,56 @@ abstract class Expression
{
return new BinaryExpression($this, 'OR', $oExpr);
}
abstract public function RenameParam($sOldName, $sNewName);
abstract public function RenameAlias($sOldName, $sNewName);
/**
* Make the most relevant label, given the value of the expression
*
* @param DBSearch oFilter The context in which this expression has been used
* @param string sValue The value returned by the query, for this expression
* @param string sDefault The default value if no relevant label could be computed
*
* @param DBSearch oFilter The context in which this expression has been used
* @param string sValue The value returned by the query, for this expression
* @param string sDefault The default value if no relevant label could be computed
*
* @return The label
*/
*/
public function MakeValueLabel($oFilter, $sValue, $sDefault)
{
return $sDefault;
}
public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
return array(
'widget' => AttributeDefinition::SEARCH_WIDGET_TYPE,
'oql' => $this->Render($aArgs, $bRetrofitParams),
'source' => get_class($this),
);
}
/**
* Split binary expression on given operator
*
* @param Expression $oExpr
* @param string $sOperator
* @param array $aAndExpr
*
* @return array of expressions
*/
public static function Split($oExpr, $sOperator = 'AND', &$aAndExpr = array())
{
if (($oExpr instanceof BinaryExpression) && ($oExpr->GetOperator() == $sOperator))
{
static::Split($oExpr->GetLeftExpr(), $sOperator, $aAndExpr);
static::Split($oExpr->GetRightExpr(), $sOperator, $aAndExpr);
}
else
{
$aAndExpr[] = $oExpr;
}
return $aAndExpr;
}
}
class SQLExpression extends Expression
@@ -151,6 +202,11 @@ class SQLExpression extends Expression
return false;
}
public function Display($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
return $this->Render($aArgs, $bRetrofitParams);
}
// recursive rendering
public function Render(&$aArgs = null, $bRetrofitParams = false)
{
@@ -165,7 +221,7 @@ class SQLExpression extends Expression
public function ApplyParameters($aArgs)
{
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
}
@@ -183,12 +239,12 @@ class SQLExpression extends Expression
public function CollectUsedParents(&$aTable)
{
}
public function ListConstantFields()
{
return array();
}
public function RenameParam($sOldName, $sNewName)
{
// Do nothing, since there is nothing to rename
@@ -244,7 +300,7 @@ class BinaryExpression extends Expression
}
return false;
}
public function GetLeftExpr()
{
return $this->m_oLeftExpr;
@@ -260,6 +316,27 @@ class BinaryExpression extends Expression
return $this->m_sOperator;
}
// recursive rendering
public function Display($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
$sOperator = Dict::S($this->GetOperator(), " {$this->GetOperator()} ");
$oLeftExpr = $this->GetLeftExpr();
if ($oLeftExpr instanceof FieldExpression)
{
$oAttDef = $oLeftExpr->GetAttDef($oSearch->GetJoinedClasses());
}
$oRightExpr = $this->GetRightExpr();
if ($oRightExpr instanceof FieldExpression)
{
$oAttDef = $oRightExpr->GetAttDef($oSearch->GetJoinedClasses());
}
$sLeft = $oLeftExpr->Display($oSearch, $aArgs, $bRetrofitParams, $oAttDef);
$sRight = $oRightExpr->Display($oSearch, $aArgs, $bRetrofitParams, $oAttDef);
return "({$sLeft}{$sOperator}{$sRight})";
}
// recursive rendering
public function Render(&$aArgs = null, $bRetrofitParams = false)
{
@@ -295,7 +372,7 @@ class BinaryExpression extends Expression
$this->m_oRightExpr->ApplyParameters($aArgs);
}
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
$this->GetLeftExpr()->GetUnresolvedFields($sAlias, $aUnresolved);
@@ -321,7 +398,7 @@ class BinaryExpression extends Expression
$this->GetLeftExpr()->CollectUsedParents($aTable);
$this->GetRightExpr()->CollectUsedParents($aTable);
}
/**
* List all constant expression of the form <field> = <scalar> or <field> = :<variable>
* Could be extended to support <field> = <function><constant_expression>
@@ -355,7 +432,7 @@ class BinaryExpression extends Expression
}
return $aResult;
}
public function RenameParam($sOldName, $sNewName)
{
$this->GetLeftExpr()->RenameParam($sOldName, $sNewName);
@@ -367,6 +444,41 @@ class BinaryExpression extends Expression
$this->GetLeftExpr()->RenameAlias($sOldName, $sNewName);
$this->GetRightExpr()->RenameAlias($sOldName, $sNewName);
}
public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
$oLeftExpr = $this->GetLeftExpr();
$oRightExpr = $this->GetRightExpr();
if ($oLeftExpr instanceof FieldExpression && $oRightExpr instanceof FieldExpression)
{
// Default criterion (Field OPE Field) is not supported
return parent::GetCriterion($oSearch, $aArgs, $bRetrofitParams, $oAttDef);
}
if ($oLeftExpr instanceof FieldExpression)
{
$oAttDef = $oLeftExpr->GetAttDef($oSearch->GetJoinedClasses());
}
if ($oRightExpr instanceof FieldExpression)
{
$oAttDef = $oRightExpr->GetAttDef($oSearch->GetJoinedClasses());
}
if (is_null($oAttDef))
{
return parent::GetCriterion($oSearch, $aArgs, $bRetrofitParams, $oAttDef);
}
$aCriteriaLeft = $oLeftExpr->GetCriterion($oSearch, $aArgs, $bRetrofitParams, $oAttDef);
$aCriteriaRight = $oRightExpr->GetCriterion($oSearch, $aArgs, $bRetrofitParams, $oAttDef);
$aCriteria = array_merge($aCriteriaLeft, $aCriteriaRight);
$aCriteria['operator'] = $this->GetOperator();
$aCriteria['oql'] = $this->Render($aArgs, $bRetrofitParams);
return $aCriteria;
}
}
@@ -390,6 +502,11 @@ class UnaryExpression extends Expression
return $this->m_value;
}
public function Display($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
return $this->Render($aArgs, $bRetrofitParams);
}
// recursive rendering
public function Render(&$aArgs = null, $bRetrofitParams = false)
{
@@ -404,7 +521,7 @@ class UnaryExpression extends Expression
public function ApplyParameters($aArgs)
{
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
}
@@ -427,7 +544,7 @@ class UnaryExpression extends Expression
{
return array();
}
public function RenameParam($sOldName, $sNewName)
{
// Do nothing
@@ -451,6 +568,39 @@ class ScalarExpression extends UnaryExpression
parent::__construct($value);
}
/**
* @param array $oSearch
* @param array $aArgs
* @param bool $bRetrofitParams
* @param AttributeDefinition $oAttDef
*
* @return array|string
* @throws Exception
*/
public function Display($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
if (!is_null($oAttDef))
{
if ($oAttDef->IsExternalKey())
{
try
{
/** @var AttributeExternalKey $oAttDef */
$sTarget = $oAttDef->GetTargetClass();
$oObj = MetaModel::GetObject($sTarget, $this->m_value);
return $oObj->Get("friendlyname");
} catch (CoreException $e)
{
}
}
return $oAttDef->GetAsPlainText($this->m_value);
}
return $this->Render($aArgs, $bRetrofitParams);
}
// recursive rendering
public function Render(&$aArgs = null, $bRetrofitParams = false)
{
@@ -469,6 +619,37 @@ class ScalarExpression extends UnaryExpression
{
return clone $this;
}
public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
$aValue = array('value' => $this->GetValue());
if (!is_null($oAttDef))
{
switch (true)
{
case $oAttDef->IsExternalKey():
try
{
/** @var AttributeExternalKey $oAttDef */
$sTarget = $oAttDef->GetTargetClass();
$oObj = MetaModel::GetObject($sTarget, $this->GetValue());
$aValue['label'] = $oObj->Get("friendlyname");
} catch (Exception $e)
{
IssueLog::Error($e->getMessage());
}
break;
default:
$aValue['label'] = $oAttDef->GetAsPlainText($this->GetValue());
break;
}
}
return array('values' => array($aValue));
}
}
class TrueExpression extends ScalarExpression
@@ -525,6 +706,43 @@ class FieldExpression extends UnaryExpression
$this->m_value = $sParent.'.'.$this->m_sName;
}
private function GetClassName($aClasses = array())
{
if (isset($aClasses[$this->m_sParent]))
{
return $aClasses[$this->m_sParent];
}
else
{
return $this->m_sParent;
}
}
/**
* @param DBObjectSearch $oSearch
* @param array $aArgs
* @param bool $bRetrofitParams
* @param AttributeDefinition $oAttDef
*
* @return array|string
* @throws Exception
*/
public function Display($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
if (empty($this->m_sParent))
{
return "`{$this->m_sName}`";
}
$sClass = $this->GetClassName($oSearch->GetJoinedClasses());
$sAttName = MetaModel::GetLabel($sClass, $this->m_sName);
if ($sClass != $oSearch->GetClass())
{
$sAttName = MetaModel::GetName($sClass).':'.$sAttName;
}
return $sAttName;
}
// recursive rendering
public function Render(&$aArgs = null, $bRetrofitParams = false)
{
@@ -535,6 +753,22 @@ class FieldExpression extends UnaryExpression
return "`{$this->m_sParent}`.`{$this->m_sName}`";
}
public function GetAttDef($aClasses = array())
{
if (!empty($this->m_sParent))
{
$sClass = $this->GetClassName($aClasses);
$aAttDefs = MetaModel::ListAttributeDefs($sClass);
if (isset($aAttDefs[$this->m_sName]))
{
return $aAttDefs[$this->m_sName];
}
}
return null;
}
public function ListRequiredFields()
{
return array($this->m_sParent.'.'.$this->m_sName);
@@ -565,8 +799,9 @@ class FieldExpression extends UnaryExpression
if (!array_key_exists($this->m_sParent, $aTranslationData))
{
if ($bMatchAll) throw new CoreException('Unknown parent id in translation table', array('parent_id' => $this->m_sParent, 'translation_table' => array_keys($aTranslationData)));
return clone $this;
}
}
if (!array_key_exists($this->m_sName, $aTranslationData[$this->m_sParent]))
{
if (!array_key_exists('*', $aTranslationData[$this->m_sParent]))
@@ -595,12 +830,13 @@ class FieldExpression extends UnaryExpression
/**
* Make the most relevant label, given the value of the expression
*
* @param DBSearch oFilter The context in which this expression has been used
* @param string sValue The value returned by the query, for this expression
* @param string sDefault The default value if no relevant label could be computed
*
* @param DBSearch oFilter The context in which this expression has been used
* @param string sValue The value returned by the query, for this expression
* @param string sDefault The default value if no relevant label could be computed
*
* @return The label
*/
*/
public function MakeValueLabel($oFilter, $sValue, $sDefault)
{
$sAttCode = $this->GetName();
@@ -646,6 +882,12 @@ class FieldExpression extends UnaryExpression
$this->m_sParent = $sNewName;
}
}
public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
return array('ref' => $this->GetParent().'.'.$this->GetName());
}
}
// Has been resolved into an SQL expression
@@ -678,7 +920,68 @@ class VariableExpression extends UnaryExpression
return false;
}
public function GetName() {return $this->m_sName;}
public function GetName()
{
return $this->m_sName;
}
public function Display($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
$sValue = $this->m_value;
if (!is_null($aArgs) && (array_key_exists($this->m_sName, $aArgs)))
{
$sValue = $aArgs[$this->m_sName];
}
elseif (($iPos = strpos($this->m_sName, '->')) !== false)
{
$sParamName = substr($this->m_sName, 0, $iPos);
$oObj = null;
$sAttCode = 'id';
if (array_key_exists($sParamName.'->object()', $aArgs))
{
$sAttCode = substr($this->m_sName, $iPos + 2);
$oObj = $aArgs[$sParamName.'->object()'];
}
elseif (array_key_exists($sParamName, $aArgs))
{
$sAttCode = substr($this->m_sName, $iPos + 2);
$oObj = $aArgs[$sParamName];
}
if (!is_null($oObj))
{
if ($sAttCode == 'id')
{
$sValue = $oObj->Get("friendlyname");
}
else
{
$sValue = $oObj->Get($sAttCode);
}
return $sValue;
}
}
if (!is_null($oAttDef))
{
if ($oAttDef->IsExternalKey())
{
try
{
/** @var AttributeExternalKey $oAttDef */
$sTarget = $oAttDef->GetTargetClass();
$oObj = MetaModel::GetObject($sTarget, $sValue);
return $oObj->Get("friendlyname");
} catch (CoreException $e)
{
}
}
return $oAttDef->GetAsPlainText($sValue);
}
return $this->Render($aArgs, $bRetrofitParams);
}
// recursive rendering
public function Render(&$aArgs = null, $bRetrofitParams = false)
@@ -729,7 +1032,7 @@ class VariableExpression extends UnaryExpression
$this->m_sName = $sNewName;
}
}
public function GetAsScalar($aArgs)
{
$oRet = null;
@@ -798,6 +1101,11 @@ class ListExpression extends Expression
return $this->m_aExpressions;
}
public function Display($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
return $this->Render($aArgs, $bRetrofitParams);
}
// recursive rendering
public function Render(&$aArgs = null, $bRetrofitParams = false)
{
@@ -833,7 +1141,7 @@ class ListExpression extends Expression
}
}
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
foreach ($this->m_aExpressions as $oExpr)
@@ -861,7 +1169,7 @@ class ListExpression extends Expression
}
return $aRes;
}
public function CollectUsedParents(&$aTable)
{
foreach ($this->m_aExpressions as $oExpr)
@@ -879,7 +1187,7 @@ class ListExpression extends Expression
}
return $aRes;
}
public function RenameParam($sOldName, $sNewName)
{
$aRes = array();
@@ -887,7 +1195,7 @@ class ListExpression extends Expression
{
$this->m_aExpressions[$key] = $oExpr->RenameParam($sOldName, $sNewName);
}
}
}
public function RenameAlias($sOldName, $sNewName)
{
@@ -896,7 +1204,23 @@ class ListExpression extends Expression
{
$oExpr->RenameAlias($sOldName, $sNewName);
}
}
}
public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
$aValues = array();
foreach($this->m_aExpressions as $oExpression)
{
$aCrit = $oExpression->GetCriterion($oSearch, $aArgs, $bRetrofitParams, $oAttDef);
if (array_key_exists('values', $aCrit))
{
$aValues = array_merge($aValues, $aCrit['values']);
}
}
return array('values' => $aValues);
}
}
@@ -927,6 +1251,11 @@ class FunctionExpression extends Expression
return $this->m_aArgs;
}
public function Display($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
return $this->Render($aArgs, $bRetrofitParams);
}
// recursive rendering
public function Render(&$aArgs = null, $bRetrofitParams = false)
{
@@ -990,7 +1319,7 @@ class FunctionExpression extends Expression
}
return $aRes;
}
public function CollectUsedParents(&$aTable)
{
foreach ($this->m_aArgs as $oExpr)
@@ -1008,7 +1337,7 @@ class FunctionExpression extends Expression
}
return $aRes;
}
public function RenameParam($sOldName, $sNewName)
{
foreach ($this->m_aArgs as $key => $oExpr)
@@ -1027,12 +1356,13 @@ class FunctionExpression extends Expression
/**
* Make the most relevant label, given the value of the expression
*
* @param DBSearch oFilter The context in which this expression has been used
* @param string sValue The value returned by the query, for this expression
* @param string sDefault The default value if no relevant label could be computed
*
* @param DBSearch oFilter The context in which this expression has been used
* @param string sValue The value returned by the query, for this expression
* @param string sDefault The default value if no relevant label could be computed
*
* @return The label
*/
*/
public function MakeValueLabel($oFilter, $sValue, $sDefault)
{
static $aWeekDayToString = null;
@@ -1124,6 +1454,11 @@ class IntervalExpression extends Expression
return $this->m_sUnit;
}
public function Display($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
return $this->Render($aArgs, $bRetrofitParams);
}
// recursive rendering
public function Render(&$aArgs = null, $bRetrofitParams = false)
{
@@ -1147,7 +1482,7 @@ class IntervalExpression extends Expression
$this->m_oValue->ApplyParameters($aArgs);
}
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
$this->m_oValue->GetUnresolvedFields($sAlias, $aUnresolved);
@@ -1171,16 +1506,16 @@ class IntervalExpression extends Expression
{
return array();
}
public function RenameParam($sOldName, $sNewName)
{
$this->m_oValue->RenameParam($sOldName, $sNewName);
}
}
public function RenameAlias($sOldName, $sNewName)
{
$this->m_oValue->RenameAlias($sOldName, $sNewName);
}
}
}
class CharConcatExpression extends Expression
@@ -1203,6 +1538,11 @@ class CharConcatExpression extends Expression
return $this->m_aExpressions;
}
public function Display($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
return $this->Render($aArgs, $bRetrofitParams);
}
// recursive rendering
public function Render(&$aArgs = null, $bRetrofitParams = false)
{
@@ -1210,7 +1550,7 @@ class CharConcatExpression extends Expression
foreach ($this->m_aExpressions as $oExpr)
{
$sCol = $oExpr->Render($aArgs, $bRetrofitParams);
// Concat will be globally NULL if one single argument is null !
// Concat will be globally NULL if one single argument is null !
$aRes[] = "COALESCE($sCol, '')";
}
return "CAST(CONCAT(".implode(', ', $aRes).") AS CHAR)";
@@ -1293,7 +1633,7 @@ class CharConcatExpression extends Expression
{
$this->m_aExpressions[$key] = $oExpr->RenameParam($sOldName, $sNewName);
}
}
}
public function RenameAlias($sOldName, $sNewName)
{
@@ -1301,7 +1641,7 @@ class CharConcatExpression extends Expression
{
$oExpr->RenameAlias($sOldName, $sNewName);
}
}
}
}
@@ -1315,6 +1655,10 @@ class CharConcatWSExpression extends CharConcatExpression
parent::__construct($aExpressions);
}
public function Display($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
return $this->Render($aArgs, $bRetrofitParams);
}
// recursive rendering
public function Render(&$aArgs = null, $bRetrofitParams = false)
{
@@ -1322,7 +1666,7 @@ class CharConcatWSExpression extends CharConcatExpression
foreach ($this->m_aExpressions as $oExpr)
{
$sCol = $oExpr->Render($aArgs, $bRetrofitParams);
// Concat will be globally NULL if one single argument is null !
// Concat will be globally NULL if one single argument is null !
$aRes[] = "COALESCE($sCol, '')";
}
$sSep = CMDBSource::Quote($this->m_separator);
@@ -1502,6 +1846,7 @@ class QueryBuilderExpressions
{
$this->m_aJoinFields[$index] = $oExpression->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
}
foreach($this->m_aClassIds as $sClass => $oExpression)
{
$this->m_aClassIds[$sClass] = $oExpression->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);

View File

@@ -0,0 +1,206 @@
<?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/>
*
*/
namespace Combodo\iTop\Application\Search;
use ApplicationContext;
use AttributeDefinition;
use CMDBObjectSet;
use CoreException;
use DBObjectSearch;
use Dict;
use Expression;
use IssueLog;
use MetaModel;
use TrueExpression;
use utils;
use WebPage;
class SearchForm
{
public static function GetSearchForm(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array())
{
$sHtml = '';
$oAppContext = new ApplicationContext();
$sClassName = $oSet->GetFilter()->GetClass();
// Simple search form
if (isset($aExtraParams['currentId']))
{
$sSearchFormId = $aExtraParams['currentId'];
}
else
{
$iSearchFormId = $oPage->GetUniqueId();
$sSearchFormId = 'SimpleSearchForm'.$iSearchFormId;
$sHtml .= "<div id=\"ds_$sSearchFormId\" class=\"mini_tab{$iSearchFormId}\">\n";
}
// Check if the current class has some sub-classes
if (isset($aExtraParams['baseClass']))
{
$sRootClass = $aExtraParams['baseClass'];
}
else
{
$sRootClass = $sClassName;
}
$aSubClasses = MetaModel::GetSubclasses($sRootClass);
if (count($aSubClasses) > 0)
{
$aOptions = array();
$aOptions[MetaModel::GetName($sRootClass)] = "<option value=\"$sRootClass\">".MetaModel::GetName($sRootClass)."</options>\n";
foreach($aSubClasses as $sSubclassName)
{
$aOptions[MetaModel::GetName($sSubclassName)] = "<option value=\"$sSubclassName\">".MetaModel::GetName($sSubclassName)."</options>\n";
}
$aOptions[MetaModel::GetName($sClassName)] = "<option selected value=\"$sClassName\">".MetaModel::GetName($sClassName)."</options>\n";
ksort($aOptions);
$sContext = $oAppContext->GetForLink();
$sClassesCombo = "<select name=\"class\" onChange=\"ReloadSearchForm('$sSearchFormId', this.value, '$sRootClass', '$sContext')\">\n".implode('',
$aOptions)."</select>\n";
}
else
{
$sClassesCombo = MetaModel::GetName($sClassName);
}
$sAction = (isset($aExtraParams['action'])) ? $aExtraParams['action'] : utils::GetAbsoluteUrlAppRoot().'pages/UI.php';
$sHtml .= "<form id=\"fs_{$sSearchFormId}\" action=\"{$sAction}\">\n"; // Don't use $_SERVER['SCRIPT_NAME'] since the form may be called asynchronously (from ajax.php)
$sHtml .= "<h2>".Dict::Format('UI:SearchFor_Class_Objects', $sClassesCombo)."</h2>\n";
$sHtml .= "<div id=\"fs_{$sSearchFormId}_criterion_outer\">\n";
$sHtml .= "</div>\n";
$sPrimaryClassName = $oSet->GetClass();
$sPrimaryClassAlias = $oSet->GetClassAlias();
$aFields = self::GetFields($sPrimaryClassName, $sPrimaryClassAlias);
$oSearch = $oSet->GetFilter();
$aCriterion = self::GetCriterion($oSearch);
$oBaseSearch = $oSearch->DeepClone();
$oBaseSearch->ResetCondition();
$aSearchParams = array(
'criterion_outer_selector' => "#fs_{$sSearchFormId}_criterion_outer",
array(
'search' => array(
'fields' => $aFields,
'criterion' => $aCriterion,
'base_oql' => $oBaseSearch->ToOQL(),
)
),
);
$oPage->add_ready_script('$("fs_'.$sSearchFormId.'").search_form_handler('.json_encode($aSearchParams).');');
return $sHtml;
}
/**
* @param $sClassName
*
* @param $sClassAlias
*
* @return array
*/
public static function GetFields($sClassName, $sClassAlias)
{
$aFields = array();
try
{
$aList = MetaModel::GetZListItems($sClassName, 'standard_search');
$aAttrDefs = MetaModel::ListAttributeDefs($sClassName);
foreach($aList as $sFilterCode)
{
$aField = array();
$aField['code'] = $sFilterCode;
$aField['class'] = $sClassName;
$aField['class_alias'] = $sClassAlias;
$aField['label'] = Dict::S('Class:'.$sClassName.'/Attribute:'.$sFilterCode);
if (array_key_exists($sFilterCode, $aAttrDefs))
{
$oAttrDef = $aAttrDefs[$sFilterCode];
$aField['widget'] = $oAttrDef->GetSearchType();
$aField['allowed_values'] = self::GetFieldAllowedValues($oAttrDef);
}
else
{
$aField['widget'] = AttributeDefinition::SEARCH_WIDGET_TYPE;
}
$aFields[$sClassAlias.'.'.$sFilterCode] = $aField;
}
} catch (CoreException $e)
{
IssueLog::Error($e->getMessage());
}
return $aFields;
}
/**
* @param $oAttrDef
*
* @return array
*/
public static function GetFieldAllowedValues($oAttrDef)
{
if (method_exists($oAttrDef, 'GetAllowedValuesAsObjectSet'))
{
$oSet = $oAttrDef->GetAllowedValuesAsObjectSet();
if ($oSet->Count() > MetaModel::GetConfig()->Get('max_combo_length'))
{
return array('autocomplete' => true);
}
}
return array('values' => $oAttrDef->GetAllowedValues());
}
/**
* @param DBObjectSearch $oSearch
*/
public static function GetCriterion($oSearch)
{
$oExpression = $oSearch->GetCriteria();
$aOrCriterion = array();
$aORExpressions = Expression::Split($oExpression, 'OR');
foreach($aORExpressions as $oORSubExpr)
{
$aAndCriterion = array();
$aAndExpressions = Expression::Split($oORSubExpr, 'AND');
foreach($aAndExpressions as $oAndSubExpr)
{
if ($oAndSubExpr instanceof TrueExpression)
{
continue;
}
$aAndCriterion[] = $oAndSubExpr->GetCriterion($oSearch);
}
$aOrCriterion[] = array('and' => $aAndCriterion);
}
return array('or' => $aOrCriterion);
}
}

View File

@@ -1,63 +0,0 @@
<?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/>
*
*/
namespace Combodo\iTop\Application\Search;
use CMDBObjectSet;
use CoreException;
use Dict;
use MetaModel;
use WebPage;
class SearchForm
{
public static function GetSearchForm(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array())
{
$sHtml = '';
return $sHtml;
}
/**
* @param $sClassName
*
* @throws CoreException
*/
public static function GetFields($sClassName)
{
$aFields = array();
$aList = MetaModel::GetZListItems($sClassName, 'standard_search');
$aAttrDefs = MetaModel::ListAttributeDefs($sClassName);
foreach($aList as $sFilterCode)
{
$aField = array();
$aField['code'] = $sFilterCode;
$aField['class'] = $sClassName;
$aField['class_alias'] = $sClassName;
$aField['label'] = Dict::S('Class:'.$sClassName.'/Attribute:'.$sFilterCode);
}
return $aFields;
}
}

View File

@@ -36,11 +36,46 @@ class SearchFormTest extends ItopDataTestCase
{
parent::setUp();
require_once(APPROOT."sources/application/search/searchform.class.php");
require_once(APPROOT."sources/application/search/searchform.class.inc.php");
}
/**
*/
public function testGetFields()
{
$this->debug(SearchForm::GetFields('Contact'));
$aFields = SearchForm::GetFields('Contact', 'Contact');
$this->debug(json_encode($aFields, JSON_PRETTY_PRINT));
$this->assertCount(7, $aFields);
$oSearch = \DBSearch::FromOQL("SELECT Contact AS C WHERE C.status = 'active'");
$aFields = SearchForm::GetFields($oSearch->GetClass(), $oSearch->GetClassAlias());
$this->debug(json_encode($aFields, JSON_PRETTY_PRINT));
}
/**
* @dataProvider GetCriterionProvider
* @throws \OQLException
*/
public function testGetCriterion($sOQL, $iOrCount)
{
$aCriterion = SearchForm::GetCriterion(\DBObjectSearch::FromOQL($sOQL));
$this->debug($sOQL);
$this->debug(json_encode($aCriterion, JSON_PRETTY_PRINT));
$this->assertCount($iOrCount, $aCriterion['or']);
}
public function GetCriterionProvider()
{
return array(
array('OQL' => "SELECT Contact", 1),
array('OQL' => "SELECT Contact WHERE status = 'active'", 1),
array('OQL' => "SELECT Contact AS C WHERE C.status = 'active'", 1),
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 = '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),
);
}
}