mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-23 02:28:44 +02:00
Advanced search: Alpha version.
SVN:trunk[5608]
This commit is contained in:
@@ -45,6 +45,11 @@ 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');
|
||||
require_once(APPROOT.'/sources/application/search/criterionparser.class.inc.php');
|
||||
require_once(APPROOT.'/sources/application/search/criterionconversionabstract.class.inc.php');
|
||||
require_once(APPROOT.'/sources/application/search/criterionconversion/criteriontooql.class.inc.php');
|
||||
require_once(APPROOT.'/sources/application/search/criterionconversion/criteriontosearchform.class.inc.php');
|
||||
|
||||
abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
|
||||
{
|
||||
@@ -604,7 +609,7 @@ EOF
|
||||
function GetBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix, $aExtraParams = array())
|
||||
{
|
||||
$sHtml = '';
|
||||
$oAppContext = new ApplicationContext();
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
|
||||
$aDetails = array();
|
||||
$sClass = get_class($this);
|
||||
@@ -620,28 +625,28 @@ EOF
|
||||
$aFieldsComments = (isset($aExtraParams['fieldsComments'])) ? $aExtraParams['fieldsComments'] : array();
|
||||
$aExtraFlags = (isset($aExtraParams['fieldsFlags'])) ? $aExtraParams['fieldsFlags'] : array();
|
||||
$bFieldComments = (count($aFieldsComments) > 0);
|
||||
|
||||
foreach($aDetailsStruct as $sTab => $aCols )
|
||||
|
||||
foreach($aDetailsStruct as $sTab => $aCols)
|
||||
{
|
||||
$aDetails[$sTab] = array();
|
||||
$aTableStyles[] = 'vertical-align:top';
|
||||
$aTableClasses = array();
|
||||
$aTableStyles[] = 'vertical-align:top';
|
||||
$aTableClasses = array();
|
||||
$aColStyles[] = 'vertical-align:top';
|
||||
$aColClasses = array();
|
||||
|
||||
ksort($aCols);
|
||||
ksort($aCols);
|
||||
$iColCount = count($aCols);
|
||||
if($iColCount > 1)
|
||||
{
|
||||
$aTableClasses[] = 'n-cols-details';
|
||||
$aTableClasses[] = $iColCount.'-cols-details';
|
||||
if ($iColCount > 1)
|
||||
{
|
||||
$aTableClasses[] = 'n-cols-details';
|
||||
$aTableClasses[] = $iColCount.'-cols-details';
|
||||
|
||||
$aColStyles[] = 'width:'.floor(100/$iColCount).'%';
|
||||
}
|
||||
else
|
||||
{
|
||||
$aTableClasses[] = 'one-col-details';
|
||||
}
|
||||
$aColStyles[] = 'width:'.floor(100 / $iColCount).'%';
|
||||
}
|
||||
else
|
||||
{
|
||||
$aTableClasses[] = 'one-col-details';
|
||||
}
|
||||
|
||||
$oPage->SetCurrentTab(Dict::S($sTab));
|
||||
$oPage->add('<table style="'.implode('; ', $aTableStyles).'" class="'.implode(' ', $aTableClasses).'" data-mode="'.$sEditMode.'"><tr>');
|
||||
@@ -654,7 +659,7 @@ EOF
|
||||
$aDetails[$sTab][$sColIndex] = array();
|
||||
foreach($aFieldsets as $sFieldsetName => $aFields)
|
||||
{
|
||||
if (!empty($sFieldsetName) && ($sFieldsetName[0] != '_'))
|
||||
if (!empty($sFieldsetName) && ($sFieldsetName[0] != '_'))
|
||||
{
|
||||
$sLabel = $sFieldsetName;
|
||||
}
|
||||
@@ -683,90 +688,93 @@ EOF
|
||||
{
|
||||
|
||||
|
||||
$sComments = isset($aFieldsComments[$sAttCode]) ? $aFieldsComments[$sAttCode] : '';
|
||||
$sInfos = '';
|
||||
$iFlags = $this->GetFormAttributeFlags($sAttCode);
|
||||
if (array_key_exists($sAttCode, $aExtraFlags))
|
||||
{
|
||||
// the caller may override some flags if needed
|
||||
$iFlags = $iFlags | $aExtraFlags[$sAttCode];
|
||||
}
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
if ( (!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0))
|
||||
{
|
||||
$sInputId = $this->m_iFormId.'_'.$sAttCode;
|
||||
if ($oAttDef->IsWritable())
|
||||
$sComments = isset($aFieldsComments[$sAttCode]) ? $aFieldsComments[$sAttCode] : '';
|
||||
$sInfos = '';
|
||||
$iFlags = $this->GetFormAttributeFlags($sAttCode);
|
||||
if (array_key_exists($sAttCode, $aExtraFlags))
|
||||
{
|
||||
if ($sStateAttCode == $sAttCode)
|
||||
// the caller may override some flags if needed
|
||||
$iFlags = $iFlags | $aExtraFlags[$sAttCode];
|
||||
}
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
if ((!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0))
|
||||
{
|
||||
$sInputId = $this->m_iFormId.'_'.$sAttCode;
|
||||
if ($oAttDef->IsWritable())
|
||||
{
|
||||
// State attribute is always read-only from the UI
|
||||
$sHTMLValue = $this->GetStateLabel();
|
||||
$val = array('label' => '<label>'.$oAttDef->GetLabel().'</label>', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos, 'attcode' => $sAttCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($iFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE))
|
||||
if ($sStateAttCode == $sAttCode)
|
||||
{
|
||||
// Check if the attribute is not read-only because of a synchro...
|
||||
if ($iFlags & OPT_ATT_SLAVE)
|
||||
{
|
||||
$aReasons = array();
|
||||
$iSynchroFlags = $this->GetSynchroReplicaFlags($sAttCode, $aReasons);
|
||||
$sSynchroIcon = " <img id=\"synchro_$sInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>";
|
||||
$sTip = '';
|
||||
foreach($aReasons as $aRow)
|
||||
{
|
||||
$sDescription = htmlentities($aRow['description'], ENT_QUOTES, 'UTF-8');
|
||||
$sDescription = str_replace(array("\r\n", "\n"), "<br/>", $sDescription);
|
||||
$sTip .= "<div class='synchro-source'>";
|
||||
$sTip .= "<div class='synchro-source-title'>Synchronized with {$aRow['name']}</div>";
|
||||
$sTip .= "<div class='synchro-source-description'>$sDescription</div>";
|
||||
}
|
||||
$sTip = addslashes($sTip);
|
||||
$oPage->add_ready_script("$('#synchro_$sInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
|
||||
$sComments = $sSynchroIcon;
|
||||
}
|
||||
|
||||
// Attribute is read-only
|
||||
$sHTMLValue = "<span id=\"field_{$sInputId}\">".$this->GetAsHTML($sAttCode).'</span>';
|
||||
// State attribute is always read-only from the UI
|
||||
$sHTMLValue = $this->GetStateLabel();
|
||||
$val = array('label' => '<label>'.$oAttDef->GetLabel().'</label>', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos, 'attcode' => $sAttCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sValue = $this->Get($sAttCode);
|
||||
$sDisplayValue = $this->GetEditValue($sAttCode);
|
||||
$aArgs = array('this' => $this, 'formPrefix' => $sPrefix);
|
||||
$sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).'';
|
||||
if ($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE))
|
||||
{
|
||||
// Check if the attribute is not read-only because of a synchro...
|
||||
if ($iFlags & OPT_ATT_SLAVE)
|
||||
{
|
||||
$aReasons = array();
|
||||
$iSynchroFlags = $this->GetSynchroReplicaFlags($sAttCode, $aReasons);
|
||||
$sSynchroIcon = " <img id=\"synchro_$sInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>";
|
||||
$sTip = '';
|
||||
foreach($aReasons as $aRow)
|
||||
{
|
||||
$sDescription = htmlentities($aRow['description'], ENT_QUOTES, 'UTF-8');
|
||||
$sDescription = str_replace(array("\r\n", "\n"), "<br/>", $sDescription);
|
||||
$sTip .= "<div class='synchro-source'>";
|
||||
$sTip .= "<div class='synchro-source-title'>Synchronized with {$aRow['name']}</div>";
|
||||
$sTip .= "<div class='synchro-source-description'>$sDescription</div>";
|
||||
}
|
||||
$sTip = addslashes($sTip);
|
||||
$oPage->add_ready_script("$('#synchro_$sInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
|
||||
$sComments = $sSynchroIcon;
|
||||
}
|
||||
|
||||
// Attribute is read-only
|
||||
$sHTMLValue = "<span id=\"field_{$sInputId}\">".$this->GetAsHTML($sAttCode).'</span>';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sValue = $this->Get($sAttCode);
|
||||
$sDisplayValue = $this->GetEditValue($sAttCode);
|
||||
$aArgs = array('this' => $this, 'formPrefix' => $sPrefix);
|
||||
$sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).'';
|
||||
}
|
||||
$aFieldsMap[$sAttCode] = $sInputId;
|
||||
$val = array('label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos, 'attcode' => $sAttCode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$val = array(
|
||||
'label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>',
|
||||
'value' => "<span id=\"field_{$sInputId}\">".$this->GetAsHTML($sAttCode)."</span>",
|
||||
'comments' => $sComments,
|
||||
'infos' => $sInfos,
|
||||
'attcode' => $sAttCode
|
||||
);
|
||||
$aFieldsMap[$sAttCode] = $sInputId;
|
||||
$val = array('label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos, 'attcode' => $sAttCode);
|
||||
}
|
||||
|
||||
// Checking how the field should be rendered
|
||||
// Note: For view mode, this is done in cmdbAbstractObject::GetFieldAsHtml()
|
||||
// Note 2: Shouldn't this be a property of the AttDef instead an array that we have to maintain?
|
||||
if (in_array($oAttDef->GetEditClass(), array('Text', 'HTML', 'CaseLog', 'CustomFields', 'OQLExpression')))
|
||||
{
|
||||
$val['layout'] = 'large';
|
||||
}
|
||||
else
|
||||
{
|
||||
$val['layout'] = 'small';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$val = array('label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 'value' => "<span id=\"field_{$sInputId}\">".$this->GetAsHTML($sAttCode)."</span>", 'comments' => $sComments, 'infos' => $sInfos, 'attcode' => $sAttCode);
|
||||
$aFieldsMap[$sAttCode] = $sInputId;
|
||||
$val = null; // Skip this field
|
||||
}
|
||||
|
||||
// Checking how the field should be rendered
|
||||
// Note: For view mode, this is done in cmdbAbstractObject::GetFieldAsHtml()
|
||||
// Note 2: Shouldn't this be a property of the AttDef instead an array that we have to maintain?
|
||||
if(in_array($oAttDef->GetEditClass(), array('Text', 'HTML', 'CaseLog', 'CustomFields', 'OQLExpression')))
|
||||
{
|
||||
$val['layout'] = 'large';
|
||||
}
|
||||
else
|
||||
{
|
||||
$val['layout'] = 'small';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$val = null; // Skip this field
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -776,10 +784,10 @@ EOF
|
||||
|
||||
if ($val != null)
|
||||
{
|
||||
// The field is visible, add it to the current column
|
||||
// The field is visible, add it to the current column
|
||||
$aDetails[$sTab][$sColIndex][] = $val;
|
||||
$iInputId++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($sPreviousLabel))
|
||||
@@ -796,6 +804,7 @@ EOF
|
||||
}
|
||||
$oPage->add('</tr></table>');
|
||||
}
|
||||
|
||||
return $aFieldsMap;
|
||||
}
|
||||
|
||||
@@ -814,6 +823,7 @@ EOF
|
||||
{
|
||||
// Object's details
|
||||
// template not found display the object using the *old style*
|
||||
$oPage->add('<div id="search-widget-results-outer">');
|
||||
$this->DisplayBareHeader($oPage, $bEditMode);
|
||||
$oPage->AddTabContainer(OBJECT_PROPERTIES_TAB);
|
||||
$oPage->SetCurrentTabContainer(OBJECT_PROPERTIES_TAB);
|
||||
@@ -823,6 +833,7 @@ EOF
|
||||
//$oPage->SetCurrentTab(Dict::S('UI:HistoryTab'));
|
||||
//$this->DisplayBareHistory($oPage, $bEditMode);
|
||||
$oPage->AddAjaxTab(Dict::S('UI:HistoryTab'), utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=history&class='.get_class($this).'&id='.$this->GetKey());
|
||||
$oPage->add('</div>');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1527,238 +1538,21 @@ EOF
|
||||
|
||||
$oPage->add(self::GetSearchForm($oPage, $oSet, $aExtraParams));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param CMDBObjectSet $oSet
|
||||
* @param array $aExtraParams
|
||||
*
|
||||
* @return string
|
||||
* @throws CoreException
|
||||
* @throws DictExceptionMissingString
|
||||
*/
|
||||
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();
|
||||
$oSearchForm = new \Combodo\iTop\Application\Search\SearchForm();
|
||||
|
||||
// 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> ";
|
||||
$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> <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> $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 </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> </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 $oSearchForm->GetSearchForm($oPage, $oSet, $aExtraParams);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -571,33 +571,6 @@ EOF
|
||||
{
|
||||
$oPage->add_ready_script("oTable.trigger(\"fakesorton\", [$sFakeSortList]);");
|
||||
}
|
||||
//if ($iNbPages == 1)
|
||||
if (false)
|
||||
{
|
||||
if (isset($aExtraParams['cssCount']))
|
||||
{
|
||||
$sCssCount = $aExtraParams['cssCount'];
|
||||
if ($sSelectMode == 'single')
|
||||
{
|
||||
$sSelectSelector = ":radio[name^=selectObj]";
|
||||
}
|
||||
else if ($sSelectMode == 'multiple')
|
||||
{
|
||||
$sSelectSelector = ":checkbox[name^=selectObj]";
|
||||
}
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$('#{$this->iListId} table.listResults $sSelectSelector').change(function() {
|
||||
var c = $('{$sCssCount}');
|
||||
var v = $('#{$this->iListId} table.listResults $sSelectSelector:checked').length;
|
||||
c.val(v);
|
||||
$('#{$this->iListId} .selectedCount').text(v);
|
||||
c.trigger('change');
|
||||
});
|
||||
EOF
|
||||
);
|
||||
}
|
||||
}
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
|
||||
@@ -220,46 +220,13 @@ class DisplayBlock
|
||||
$aExtraParams['currentId'] = $sId;
|
||||
$sExtraParams = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them
|
||||
|
||||
$bAutoReload = false;
|
||||
if (isset($aExtraParams['auto_reload']))
|
||||
{
|
||||
if ($aExtraParams['auto_reload'] === true)
|
||||
{
|
||||
// Note: does not work in the switch (case true) because a positive number evaluates to true!!!
|
||||
$aExtraParams['auto_reload'] = 'standard';
|
||||
}
|
||||
switch($aExtraParams['auto_reload'])
|
||||
{
|
||||
case 'fast':
|
||||
$bAutoReload = true;
|
||||
$iReloadInterval = MetaModel::GetConfig()->GetFastReloadInterval()*1000;
|
||||
break;
|
||||
|
||||
case 'standard':
|
||||
case 'true':
|
||||
$bAutoReload = true;
|
||||
$iReloadInterval = MetaModel::GetConfig()->GetStandardReloadInterval()*1000;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (is_numeric($aExtraParams['auto_reload']) && ($aExtraParams['auto_reload'] > 0))
|
||||
{
|
||||
$bAutoReload = true;
|
||||
$iReloadInterval = max(MetaModel::GetConfig()->Get('min_reload_interval'), $aExtraParams['auto_reload'])*1000;
|
||||
}
|
||||
else
|
||||
{
|
||||
// incorrect config, ignore it
|
||||
$bAutoReload = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$sFilter = $this->m_oFilter->serialize(); // Used either for asynchronous or auto_reload
|
||||
if (!$this->m_bAsynchronous)
|
||||
{
|
||||
// render now
|
||||
$sHtml .= "<div id=\"$sId\" class=\"display_block\">\n";
|
||||
$sHtml .= "<div id=\"$sId\" class=\"display_block\" >\n";
|
||||
$sHtml .= $this->GetRenderContent($oPage, $aExtraParams, $sId);
|
||||
$sHtml .= "</div>\n";
|
||||
}
|
||||
@@ -273,17 +240,36 @@ class DisplayBlock
|
||||
$.post("ajax.render.php?style='.$this->m_sStyle.'",
|
||||
{ operation: "ajax", filter: "'.$sFilter.'", extra_params: "'.$sExtraParams.'" },
|
||||
function(data){
|
||||
$("#'.$sId.'").empty();
|
||||
$("#'.$sId.'").append(data);
|
||||
$("#'.$sId.'").removeClass("loading");
|
||||
$("#'.$sId.'")
|
||||
.empty()
|
||||
.append(data)
|
||||
.removeClass("loading")
|
||||
;
|
||||
}
|
||||
);
|
||||
');
|
||||
}
|
||||
if (($bAutoReload) && ($this->m_sStyle != 'search')) // Search form do NOT auto-reload
|
||||
{
|
||||
$oPage->add_script('setInterval("ReloadBlock(\''.$sId.'\', \''.$this->m_sStyle.'\', \''.$sFilter.'\', \"'.$sExtraParams.'\")", '.$iReloadInterval.');');
|
||||
}
|
||||
|
||||
|
||||
if ($this->m_sStyle == 'list') // Search form need to extract result list extra data, the simplest way is to expose this configuration
|
||||
{
|
||||
|
||||
$listJsonExtraParams = json_encode(json_encode($aExtraParams));
|
||||
$oPage->add_ready_script("
|
||||
$('#$sId').data('sExtraParams', ".$listJsonExtraParams.");
|
||||
// console.debug($('#$sId').data());
|
||||
// console.debug($('#$sId'));
|
||||
// console.debug('#$sId');
|
||||
");
|
||||
|
||||
|
||||
|
||||
|
||||
// $oPage->add_ready_script("console.debug($('#Menu_UserRequest_OpenRequests').data());");
|
||||
|
||||
}
|
||||
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
@@ -919,21 +905,10 @@ class DisplayBlock
|
||||
case 'search':
|
||||
if (!$oPage->IsPrintableVersion())
|
||||
{
|
||||
$sStyle = (isset($aExtraParams['open']) && ($aExtraParams['open'] == 'true')) ? 'SearchDrawer' : 'SearchDrawer DrawerClosed';
|
||||
$sHtml .= "<div id=\"ds_$sId\" class=\"$sStyle\">\n";
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$("#dh_$sId").click( function() {
|
||||
$("#ds_$sId").slideToggle('normal', function() { $("#ds_$sId").parent().resize(); FixSearchFormsDisposition(); $("#dh_$sId").trigger('toggle_complete'); } );
|
||||
$("#dh_$sId").toggleClass('open');
|
||||
});
|
||||
EOF
|
||||
);
|
||||
$sHtml .= "<div id=\"ds_$sId\" class=\"search_box\">\n";
|
||||
$aExtraParams['currentId'] = $sId;
|
||||
$sHtml .= cmdbAbstractObject::GetSearchForm($oPage, $this->m_oSet, $aExtraParams);
|
||||
$sHtml .= "</div>\n";
|
||||
$sHtml .= "<div class=\"HRDrawer\"></div>\n";
|
||||
$sHtml .= "<div id=\"dh_$sId\" class=\"DrawerHandle\">".Dict::S('UI:SearchToggle')."</div>\n";
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1126,6 +1101,56 @@ EOF
|
||||
// Unsupported style, do nothing.
|
||||
$sHtml .= Dict::format('UI:Error:UnsupportedStyleOfBlock', $this->m_sStyle);
|
||||
}
|
||||
|
||||
|
||||
$bAutoReload = false;
|
||||
if (isset($aExtraParams['auto_reload']))
|
||||
{
|
||||
if ($aExtraParams['auto_reload'] === true)
|
||||
{
|
||||
// Note: does not work in the switch (case true) because a positive number evaluates to true!!!
|
||||
$aExtraParams['auto_reload'] = 'standard';
|
||||
}
|
||||
switch($aExtraParams['auto_reload'])
|
||||
{
|
||||
case 'fast':
|
||||
$bAutoReload = true;
|
||||
$iReloadInterval = MetaModel::GetConfig()->GetFastReloadInterval()*1000;
|
||||
break;
|
||||
|
||||
case 'standard':
|
||||
case 'true':
|
||||
$bAutoReload = true;
|
||||
$iReloadInterval = MetaModel::GetConfig()->GetStandardReloadInterval()*1000;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (is_numeric($aExtraParams['auto_reload']) && ($aExtraParams['auto_reload'] > 0))
|
||||
{
|
||||
$bAutoReload = true;
|
||||
$iReloadInterval = max(MetaModel::GetConfig()->Get('min_reload_interval'), $aExtraParams['auto_reload'])*1000;
|
||||
}
|
||||
else
|
||||
{
|
||||
// incorrect config, ignore it
|
||||
$bAutoReload = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (($bAutoReload) && ($this->m_sStyle != 'search')) // Search form do NOT auto-reload
|
||||
{
|
||||
$sFilter = $this->m_oFilter->serialize(); // Used either for asynchronous or auto_reload
|
||||
$sExtraParams = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them
|
||||
|
||||
$oPage->add_script('if (typeof window.oAutoReloadBlock == "undefined") {
|
||||
window.oAutoReloadBlock = {};
|
||||
}
|
||||
if (typeof window.oAutoReloadBlock[\''.$sId.'\'] != "undefined") {
|
||||
clearInterval(window.oAutoReloadBlock[\''.$sId.'\']);
|
||||
}
|
||||
window.oAutoReloadBlock[\''.$sId.'\'] = setInterval("ReloadBlock(\''.$sId.'\', \''.$this->m_sStyle.'\', \''.$sFilter.'\', \"'.$sExtraParams.'\")", '.$iReloadInterval.');');
|
||||
}
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
|
||||
@@ -111,8 +111,13 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
$sSearchAny = addslashes(Dict::S('UI:SearchValue:Any'));
|
||||
$sSearchNbSelected = addslashes(Dict::S('UI:SearchValue:NbSelected'));
|
||||
$this->add_dict_entry('UI:FillAllMandatoryFields');
|
||||
$this->add_dict_entry('UI:Button:Cancel');
|
||||
$this->add_dict_entry('UI:Button:Done');
|
||||
|
||||
$this->add_dict_entries('Error:');
|
||||
$this->add_dict_entries('UI:Button:');
|
||||
$this->add_dict_entries('UI:Search:');
|
||||
$this->add_dict_entry('UI:UndefinedObject');
|
||||
$this->add_dict_entries('Enum:Undefined');
|
||||
|
||||
|
||||
if (!$this->IsPrintableVersion())
|
||||
{
|
||||
|
||||
@@ -41,6 +41,7 @@ class NiceWebPage extends WebPage
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-migrate-1.4.1.min.js'); // Needed since many other plugins still rely on oldies like $.browser
|
||||
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/ui-lightness/jquery-ui-1.11.4.custom.css');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-ui-1.11.4.custom.min.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/utils.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/hovertip.js');
|
||||
// table sorting
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.tablesorter.js');
|
||||
@@ -50,7 +51,24 @@ class NiceWebPage extends WebPage
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/datatable.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.positionBy.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.popupmenu.js');
|
||||
$this->add_ready_script(
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/searchformforeignkeys.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_handler.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_handler_history.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_raw.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_string.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_external_field.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_numeric.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_enum.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_external_key.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_hierarchical_key.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_date_abstract.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_date.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_date_time.js');
|
||||
|
||||
$this->add_dict_entries('UI:Combo');
|
||||
|
||||
$this->add_ready_script(
|
||||
<<< EOF
|
||||
//add new widget called TruncatedList to properly display truncated lists when they are sorted
|
||||
$.tablesorter.addWidget({
|
||||
|
||||
@@ -19,15 +19,15 @@
|
||||
* Class UIExtKeyWidget
|
||||
* UI wdiget for displaying and editing external keys when
|
||||
* A simple drop-down list is not enough...
|
||||
*
|
||||
*
|
||||
* The layout is the following
|
||||
*
|
||||
*
|
||||
* +-- #label_<id> (input)-------+ +-----------+
|
||||
* | | | Browse... |
|
||||
* +-----------------------------+ +-----------+
|
||||
*
|
||||
*
|
||||
* And the popup dialog has the following layout:
|
||||
*
|
||||
*
|
||||
* +------------------- ac_dlg_<id> (div)-----------+
|
||||
* + +--- ds_<id> (div)---------------------------+ |
|
||||
* | | +------------- fs_<id> (form)------------+ | |
|
||||
@@ -61,13 +61,16 @@
|
||||
require_once(APPROOT.'/application/webpage.class.inc.php');
|
||||
require_once(APPROOT.'/application/displayblock.class.inc.php');
|
||||
|
||||
class UIExtKeyWidget
|
||||
class UIExtKeyWidget
|
||||
{
|
||||
const ENUM_OUTPUT_FORMAT_CSV = 'csv';
|
||||
const ENUM_OUTPUT_FORMAT_JSON = 'json';
|
||||
|
||||
protected $iId;
|
||||
protected $sTargetClass;
|
||||
protected $sAttCode;
|
||||
protected $bSearchMode;
|
||||
|
||||
|
||||
//public function __construct($sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sNameSuffix = '', $sFieldPrefix = '', $sFormPrefix = '')
|
||||
static public function DisplayFromAttCode($oPage, $sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName = '', $sFormPrefix = '', $aArgs, $bSearchMode = false)
|
||||
{
|
||||
@@ -94,7 +97,7 @@ class UIExtKeyWidget
|
||||
$this->sAttCode = $sAttCode;
|
||||
$this->bSearchMode = $bSearchMode;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the HTML fragment corresponding to the ext key editing widget
|
||||
* @param WebPage $oP The web page used for all the output
|
||||
@@ -107,10 +110,10 @@ class UIExtKeyWidget
|
||||
{
|
||||
$this->bSearchMode = $bSearchMode;
|
||||
}
|
||||
$sTitle = addslashes($sTitle);
|
||||
$sTitle = addslashes($sTitle);
|
||||
$oPage->add_linked_script('../js/extkeywidget.js');
|
||||
$oPage->add_linked_script('../js/forms-json-utils.js');
|
||||
|
||||
|
||||
$bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
|
||||
$bExtensions = true;
|
||||
$sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm');
|
||||
@@ -123,7 +126,7 @@ class UIExtKeyWidget
|
||||
$sWizHelper = 'null';
|
||||
$sWizHelperJSON = "''";
|
||||
$sJSSearchMode = 'true';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isset($aArgs['wizHelper']))
|
||||
@@ -159,7 +162,7 @@ class UIExtKeyWidget
|
||||
while($oObj = $oAllowedValues->Fetch())
|
||||
{
|
||||
$aAllowedValues[$oObj->GetKey()] = $oObj->GetName();
|
||||
}
|
||||
}
|
||||
$sHTMLValue .= $oPage->GetRadioButtons($aAllowedValues, $value, $this->iId, "{$sAttrFieldPrefix}{$sFieldName}", false /* $bMandatory will be placed manually */, $bVertical, $sValidationField);
|
||||
$aEventsList[] ='change';
|
||||
break;
|
||||
@@ -168,7 +171,7 @@ class UIExtKeyWidget
|
||||
case 'list':
|
||||
default:
|
||||
$sSelectMode = 'true';
|
||||
|
||||
|
||||
$sHelpText = ''; //$this->oAttDef->GetHelpOnEdition();
|
||||
$sHTMLValue .= "<div class=\"field_select_wrapper\">\n";
|
||||
|
||||
@@ -196,7 +199,7 @@ class UIExtKeyWidget
|
||||
{
|
||||
$key = $oObj->GetKey();
|
||||
$display_value = $oObj->GetName();
|
||||
|
||||
|
||||
if (($oAllowedValues->Count() == 1) && ($bMandatory == 'true') )
|
||||
{
|
||||
// When there is only once choice, select it by default
|
||||
@@ -239,7 +242,7 @@ EOF
|
||||
{
|
||||
// Too many choices, use an autocomplete
|
||||
$sSelectMode = 'false';
|
||||
|
||||
|
||||
// Check that the given value is allowed
|
||||
$oSearch = $oAllowedValues->GetFilter();
|
||||
$oSearch->AddCondition('id', $value);
|
||||
@@ -259,15 +262,15 @@ EOF
|
||||
}
|
||||
$iMinChars = isset($aArgs['iMinChars']) ? $aArgs['iMinChars'] : 3; //@@@ $this->oAttDef->GetMinAutoCompleteChars();
|
||||
$iFieldSize = isset($aArgs['iFieldSize']) ? $aArgs['iFieldSize'] : 20; //@@@ $this->oAttDef->GetMaxSize();
|
||||
|
||||
|
||||
// the input for the auto-complete
|
||||
$sHTMLValue .= "<input class=\"field_autocomplete\" count=\"".$oAllowedValues->Count()."\" type=\"text\" id=\"label_$this->iId\" value=\"$sDisplayValue\"/>";
|
||||
$sHTMLValue .= "<span class=\"field_input_btn\"><img id=\"mini_search_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_search.gif?itopversion=".ITOP_VERSION."\" onClick=\"oACWidget_{$this->iId}.Search();\"/></span>";
|
||||
|
||||
|
||||
// another hidden input to store & pass the object's Id
|
||||
$sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" />\n";
|
||||
|
||||
$JSSearchMode = $this->bSearchMode ? 'true' : 'false';
|
||||
$JSSearchMode = $this->bSearchMode ? 'true' : 'false';
|
||||
// Scripts to start the autocomplete and bind some events to it
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
@@ -320,7 +323,7 @@ EOF
|
||||
|
||||
return $sHTMLValue;
|
||||
}
|
||||
|
||||
|
||||
public function GetSearchDialog(WebPage $oPage, $sTitle, $oCurrObject = null)
|
||||
{
|
||||
$sHTML = '<div class="wizContainer" style="vertical-align:top;"><div id="dc_'.$this->iId.'">';
|
||||
@@ -341,7 +344,17 @@ EOF
|
||||
$bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open');
|
||||
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
|
||||
$oBlock = new DisplayBlock($oFilter, 'search', false, $aParams);
|
||||
$sHTML .= $oBlock->GetDisplay($oPage, $this->iId, array('open' => $bOpen, 'currentId' => $this->iId));
|
||||
$sHTML .= $oBlock->GetDisplay($oPage, $this->iId,
|
||||
array(
|
||||
'menu' => false,
|
||||
'open' => $bOpen,
|
||||
'currentId' => $this->iId,
|
||||
'table_id' => "dr_{$this->iId}",
|
||||
'table_inner_id' => "{$this->iId}_results",
|
||||
'selection_mode' => true,
|
||||
'selection_type' => 'single',
|
||||
'cssCount' => '#count_'.$this->iId)
|
||||
);
|
||||
$sHTML .= "<form id=\"fr_{$this->iId}\" OnSubmit=\"return oACWidget_{$this->iId}.DoOk();\">\n";
|
||||
$sHTML .= "<div id=\"dr_{$this->iId}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
|
||||
$sHTML .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
|
||||
@@ -375,7 +388,7 @@ EOF
|
||||
{
|
||||
throw new Exception('Implementation: null value for allowed values definition');
|
||||
}
|
||||
|
||||
|
||||
$oFilter = DBObjectSearch::FromOQL($sFilter);
|
||||
if (strlen($sRemoteClass) > 0)
|
||||
{
|
||||
@@ -389,7 +402,7 @@ EOF
|
||||
$oBlock = new DisplayBlock($oFilter, 'list', false, array('query_params' => array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId)));
|
||||
$oBlock->Display($oP, $this->iId.'_results', array('this' => $oObj, 'cssCount'=> '#count_'.$this->iId, 'menu' => false, 'selection_mode' => true, 'selection_type' => 'single', 'table_id' => 'select_'.$this->sAttCode)); // Don't display the 'Actions' menu on the results
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Search for objects to be selected
|
||||
* @param WebPage $oP The page used for the output (usually an AjaxWebPage)
|
||||
@@ -397,7 +410,7 @@ EOF
|
||||
* @param DBObject $oObj The current object for the OQL context
|
||||
* @param string $sContains The text of the autocomplete to filter the results
|
||||
*/
|
||||
public function AutoComplete(WebPage $oP, $sFilter, $oObj = null, $sContains)
|
||||
public function AutoComplete(WebPage $oP, $sFilter, $oObj = null, $sContains, $sOutputFormat = self::ENUM_OUTPUT_FORMAT_CSV)
|
||||
{
|
||||
if (is_null($sFilter))
|
||||
{
|
||||
@@ -410,12 +423,27 @@ EOF
|
||||
$oValuesSet = new ValueSetObjects($sFilter, 'friendlyname'); // Bypass GetName() to avoid the encoding by htmlentities
|
||||
$oValuesSet->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
|
||||
$aValues = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains);
|
||||
foreach($aValues as $sKey => $sFriendlyName)
|
||||
|
||||
switch($sOutputFormat)
|
||||
{
|
||||
$oP->add(trim($sFriendlyName)."\t".$sKey."\n");
|
||||
case static::ENUM_OUTPUT_FORMAT_JSON:
|
||||
// Array flip to preserve values order on the label, otherwise the JS will re-order regarding the keys.
|
||||
$oP->SetContentType('application/json');
|
||||
$oP->add(json_encode(array_flip($aValues)));
|
||||
break;
|
||||
|
||||
case static::ENUM_OUTPUT_FORMAT_CSV:
|
||||
foreach($aValues as $sKey => $sFriendlyName)
|
||||
{
|
||||
$oP->add(trim($sFriendlyName)."\t".$sKey."\n");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Invalid output format, "'.$sOutputFormat.'" given.');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the display name of the selected object, to fill back the autocomplete
|
||||
*/
|
||||
@@ -505,7 +533,7 @@ EOF
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 3rd - set values from the page argument 'default'
|
||||
$oNewObj->UpdateObjectFromArg('default');
|
||||
|
||||
@@ -522,7 +550,7 @@ EOF
|
||||
$aFieldsComments[$sAttCode] = ' <img src="../images/transp-lock.png" style="vertical-align:middle" title="'.htmlentities(Dict::S('UI:UploadNotSupportedInThisMode')).'"/>';
|
||||
}
|
||||
}
|
||||
cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, array(), array('formPrefix' => $this->iId, 'noRelations' => true, 'fieldsFlags' => $aFieldsFlags, 'fieldsComments' => $aFieldsComments));
|
||||
cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, array(), array('formPrefix' => $this->iId, 'noRelations' => true, 'fieldsFlags' => $aFieldsFlags, 'fieldsComments' => $aFieldsComments));
|
||||
$oPage->add('</div></div></div>');
|
||||
// $oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: $(window).width()*0.8, height: 'auto', autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
|
||||
$oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
|
||||
@@ -566,7 +594,7 @@ EOF
|
||||
$oPage->add('</div>');
|
||||
$oPage->add("<input type=\"button\" id=\"btn_cancel_{$this->iId}\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_tree_{$this->iId}').dialog('close');\"> ");
|
||||
$oPage->add("<input type=\"button\" id=\"btn_ok_{$this->iId}\" value=\"".Dict::S('UI:Button:Ok')."\" onClick=\"oACWidget_{$this->iId}.DoHKOk();\">");
|
||||
|
||||
|
||||
$oPage->add('</div></div>');
|
||||
$oPage->add_ready_script("\$('#tree_$this->iId ul').treeview();\n");
|
||||
$oPage->add_ready_script("\$('#dlg_tree_$this->iId').dialog({ width: 'auto', height: 'auto', autoOpen: true, modal: true, title: '$sDialogTitle', resizeStop: oACWidget_{$this->iId}.OnHKResize, close: oACWidget_{$this->iId}.OnHKClose });\n");
|
||||
@@ -588,7 +616,7 @@ EOF
|
||||
}
|
||||
else
|
||||
{
|
||||
return array('error' => implode(' ', $aErrors), 'id' => 0);
|
||||
return array('error' => implode(' ', $aErrors), 'id' => 0);
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
@@ -611,7 +639,7 @@ EOF
|
||||
$aTree[$iParentId][$oObj->GetKey()] = $oObj->GetName();
|
||||
$aNodes[$oObj->GetKey()] = $oObj;
|
||||
}
|
||||
|
||||
|
||||
$aParents = array_keys($aTree);
|
||||
$aRoots = array();
|
||||
foreach($aParents as $id)
|
||||
@@ -626,7 +654,7 @@ EOF
|
||||
$this->DumpNodes($oP, $iRootId, $aTree, $aNodes, $currValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function DumpNodes($oP, $iRootId, $aTree, $aNodes, $currValue)
|
||||
{
|
||||
$bSelect = true;
|
||||
|
||||
@@ -278,14 +278,36 @@ class UILinksWidgetDirect
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param DBObject $oCurrentObj
|
||||
* @throws Exception
|
||||
* @param $aAlreadyLinked
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \Exception
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function GetObjectsSelectionDlg($oPage, $oCurrentObj)
|
||||
public function GetObjectsSelectionDlg($oPage, $oCurrentObj, $aAlreadyLinked)
|
||||
{
|
||||
$sHtml = "<div class=\"wizContainer\" style=\"vertical-align:top;\">\n";
|
||||
|
||||
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
$valuesDef = $oLinksetDef->GetValuesDef();
|
||||
|
||||
$oHiddenFilter = new DBObjectSearch($this->sLinkedClass);
|
||||
if (($oCurrentObj != null) && MetaModel::IsSameFamilyBranch($this->sLinkedClass, $this->sClass))
|
||||
{
|
||||
// Prevent linking to self if the linked object is of the same family
|
||||
// and already present in the database
|
||||
if (!$oCurrentObj->IsNew())
|
||||
{
|
||||
$oHiddenFilter->AddCondition('id', $oCurrentObj->GetKey(), '!=');
|
||||
}
|
||||
}
|
||||
if (count($aAlreadyLinked) > 0)
|
||||
{
|
||||
$oHiddenFilter->AddCondition('id', $aAlreadyLinked, 'NOTIN');
|
||||
}
|
||||
$oHiddenCriteria = $oHiddenFilter->GetCriteria();
|
||||
$aArgs = $oHiddenFilter->GetInternalParams();
|
||||
$sHiddenCriteria = $oHiddenCriteria->Render($aArgs);
|
||||
|
||||
$oLinkSetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
$valuesDef = $oLinkSetDef->GetValuesDef();
|
||||
if ($valuesDef === null)
|
||||
{
|
||||
$oFilter = new DBObjectSearch($this->sLinkedClass);
|
||||
@@ -298,13 +320,27 @@ class UILinksWidgetDirect
|
||||
}
|
||||
$oFilter = DBObjectSearch::FromOQL($valuesDef->GetFilterExpression());
|
||||
}
|
||||
|
||||
if ($oCurrentObj != null)
|
||||
{
|
||||
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
|
||||
|
||||
$aArgs = array_merge($oCurrentObj->ToArgs('this'), $oFilter->GetInternalParams());
|
||||
$oFilter->SetInternalParams($aArgs);
|
||||
}
|
||||
$bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open');
|
||||
$oBlock = new DisplayBlock($oFilter, 'search', false);
|
||||
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->sInputid}", array('open' => $bOpen));
|
||||
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->sInputid}",
|
||||
array(
|
||||
'open' => $bOpen,
|
||||
'result_list_outer_selector' => "SearchResultsToAdd_{$this->sInputid}",
|
||||
'table_id' => "add_{$this->sInputid}",
|
||||
'table_inner_id' => "ResultsToAdd_{$this->sInputid}",
|
||||
'cssCount' => "#count_{$this->sInputid}",
|
||||
'query_params' => $oFilter->GetInternalParams(),
|
||||
'hidden_criteria' => $sHiddenCriteria,
|
||||
)
|
||||
);
|
||||
$sHtml .= "<form id=\"ObjectsAddForm_{$this->sInputid}\">\n";
|
||||
$sHtml .= "<div id=\"SearchResultsToAdd_{$this->sInputid}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
|
||||
$sHtml .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
|
||||
@@ -330,8 +366,8 @@ class UILinksWidgetDirect
|
||||
{
|
||||
$sRemoteClass = $this->sLinkedClass;
|
||||
}
|
||||
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
$valuesDef = $oLinksetDef->GetValuesDef();
|
||||
$oLinkSetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
$valuesDef = $oLinkSetDef->GetValuesDef();
|
||||
if ($valuesDef === null)
|
||||
{
|
||||
$oFilter = new DBObjectSearch($sRemoteClass);
|
||||
@@ -348,7 +384,7 @@ class UILinksWidgetDirect
|
||||
if (($oCurrentObj != null) && MetaModel::IsSameFamilyBranch($sRemoteClass, $this->sClass))
|
||||
{
|
||||
// Prevent linking to self if the linked object is of the same family
|
||||
// and laready present in the database
|
||||
// and already present in the database
|
||||
if (!$oCurrentObj->IsNew())
|
||||
{
|
||||
$oFilter->AddCondition('id', $oCurrentObj->GetKey(), '!=');
|
||||
@@ -360,6 +396,8 @@ class UILinksWidgetDirect
|
||||
}
|
||||
if ($oCurrentObj != null)
|
||||
{
|
||||
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
|
||||
|
||||
$aArgs = array_merge($oCurrentObj->ToArgs('this'), $oFilter->GetInternalParams());
|
||||
$oFilter->SetInternalParams($aArgs);
|
||||
}
|
||||
|
||||
@@ -386,21 +386,55 @@ EOF
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param DBObject $oCurrentObj
|
||||
* @param $sJson
|
||||
* @param array $aAlreadyLinkedIds
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionMissingString
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function GetObjectPickerDialog($oPage, $oCurrentObj)
|
||||
public function GetObjectPickerDialog($oPage, $oCurrentObj, $sJson, $aAlreadyLinkedIds = array())
|
||||
{
|
||||
$bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open');
|
||||
$sHtml = "<div class=\"wizContainer\" style=\"vertical-align:top;\">\n";
|
||||
|
||||
$oAlreadyLinkedFilter = new DBObjectSearch($this->m_sRemoteClass);
|
||||
if (!$this->m_bDuplicatesAllowed && count($aAlreadyLinkedIds) > 0)
|
||||
{
|
||||
$oAlreadyLinkedFilter->AddCondition('id', $aAlreadyLinkedIds, 'NOTIN');
|
||||
$oAlreadyLinkedExpression = $oAlreadyLinkedFilter->GetCriteria();
|
||||
$sAlreadyLinkedExpression = $oAlreadyLinkedExpression->Render();
|
||||
}
|
||||
else
|
||||
{
|
||||
$sAlreadyLinkedExpression = '';
|
||||
}
|
||||
|
||||
$oFilter = new DBObjectSearch($this->m_sRemoteClass);
|
||||
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
|
||||
if ($oCurrentObj != null)
|
||||
{
|
||||
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
|
||||
}
|
||||
$oBlock = new DisplayBlock($oFilter, 'search', false);
|
||||
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}", array('open' => $bOpen));
|
||||
$sHtml .= "<form id=\"ObjectsAddForm_{$this->m_sAttCode}{$this->m_sNameSuffix}\" OnSubmit=\"return oWidget{$this->m_iInputId}.DoAddObjects(this.id);\">\n";
|
||||
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}",
|
||||
array(
|
||||
'open' => $bOpen,
|
||||
'menu' => false,
|
||||
'result_list_outer_selector' => "SearchResultsToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}",
|
||||
'table_id' => 'add_'.$this->m_sAttCode,
|
||||
'table_inner_id' => "ResultsToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}",
|
||||
'selection_mode' => true,
|
||||
'json' => $sJson,
|
||||
'cssCount' => '#count_'.$this->m_sAttCode.$this->m_sNameSuffix,
|
||||
'query_params' => $oFilter->GetInternalParams(),
|
||||
'hidden_criteria' => $sAlreadyLinkedExpression,
|
||||
));
|
||||
$sHtml .= "<form id=\"ObjectsAddForm_{$this->m_sAttCode}{$this->m_sNameSuffix}\">\n";
|
||||
$sHtml .= "<div id=\"SearchResultsToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
|
||||
$sHtml .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
|
||||
$sHtml .= "</div>\n";
|
||||
$sHtml .= "<input type=\"hidden\" id=\"count_{$this->m_sAttCode}{$this->m_sNameSuffix}\" value=\"0\"/>";
|
||||
$sHtml .= "<input type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('close');\"> <input id=\"btn_ok_{$this->m_sAttCode}{$this->m_sNameSuffix}\" disabled=\"disabled\" type=\"submit\" value=\"".Dict::S('UI:Button:Add')."\">";
|
||||
$sHtml .= "<input type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('close');\"> <input id=\"btn_ok_{$this->m_sAttCode}{$this->m_sNameSuffix}\" disabled=\"disabled\" type=\"button\" onclick=\"return oWidget{$this->m_iInputId}.DoAddObjects(this.id);\" value=\"".Dict::S('UI:Button:Add')."\">";
|
||||
$sHtml .= "</div>\n";
|
||||
$sHtml .= "</form>\n";
|
||||
$oPage->add($sHtml);
|
||||
@@ -416,7 +450,7 @@ EOF
|
||||
* @param string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of m_sRemoteClass
|
||||
* @param Array $aAlreadyLinkedIds List of IDs of objects of "remote" class already linked, to be filtered out of the search
|
||||
*/
|
||||
public function SearchObjectsToAdd(WebPage $oP, $sRemoteClass = '', $aAlreadyLinkedIds = array())
|
||||
public function SearchObjectsToAdd(WebPage $oP, $sRemoteClass = '', $aAlreadyLinkedIds = array(), $oCurrentObj = null)
|
||||
{
|
||||
if ($sRemoteClass != '')
|
||||
{
|
||||
@@ -432,6 +466,7 @@ EOF
|
||||
{
|
||||
$oFilter->AddCondition('id', $aAlreadyLinkedIds, 'NOTIN');
|
||||
}
|
||||
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
|
||||
$oBlock = new DisplayBlock($oFilter, 'list', false);
|
||||
$oBlock->Display($oP, "ResultsToAdd_{$this->m_sAttCode}", array('menu' => false, 'cssCount'=> '#count_'.$this->m_sAttCode.$this->m_sNameSuffix , 'selection_mode' => true, 'table_id' => 'add_'.$this->m_sAttCode)); // Don't display the 'Actions' menu on the results
|
||||
}
|
||||
|
||||
117
application/ui.searchformforeignkeys.class.inc.php
Normal file
117
application/ui.searchformforeignkeys.class.inc.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?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/>
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
require_once(APPROOT.'/application/webpage.class.inc.php');
|
||||
require_once(APPROOT.'/application/displayblock.class.inc.php');
|
||||
|
||||
class UISearchFormForeignKeys
|
||||
{
|
||||
public function __construct($sTargetClass, $iInputId = null)
|
||||
{
|
||||
$this->m_sRemoteClass = $sTargetClass;
|
||||
$this->m_iInputId = $iInputId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
*
|
||||
* @param $sTitle
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function ShowModalSearchForeignKeys($oPage, $sTitle)
|
||||
{
|
||||
$bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open');
|
||||
$sHtml = "<div class=\"wizContainer\" style=\"vertical-align:top;\">\n";
|
||||
|
||||
$oFilter = new DBObjectSearch($this->m_sRemoteClass);
|
||||
|
||||
$oBlock = new DisplayBlock($oFilter, 'search', false);
|
||||
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_iInputId}",
|
||||
array(
|
||||
'open' => $bOpen,
|
||||
'menu' => false,
|
||||
'result_list_outer_selector' => "SearchResultsToAdd_{$this->m_iInputId}",
|
||||
'table_id' => "add_{$this->m_iInputId}",
|
||||
'table_inner_id' => "ResultsToAdd_{$this->m_iInputId}",
|
||||
'selection_mode' => true,
|
||||
'cssCount' => "#count_{$this->m_iInputId}",
|
||||
'query_params' => $oFilter->GetInternalParams(),
|
||||
));
|
||||
$sHtml .= "<form id=\"ObjectsAddForm_{$this->m_iInputId}\">\n";
|
||||
$sHtml .= "<div id=\"SearchResultsToAdd_{$this->m_iInputId}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
|
||||
$sHtml .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
|
||||
$sHtml .= "</div>\n";
|
||||
$sHtml .= "<input type=\"hidden\" id=\"count_{$this->m_iInputId}\" value=\"0\"/>";
|
||||
$sHtml .= "<input type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_{$this->m_iInputId}').dialog('close');\"> <input id=\"btn_ok_{$this->m_iInputId}\" disabled=\"disabled\" type=\"button\" onclick=\"return oForeignKeysWidget{$this->m_iInputId}.DoAddObjects(this.id);\" value=\"".Dict::S('UI:Button:Add')."\">";
|
||||
$sHtml .= "</div>\n";
|
||||
$sHtml .= "</form>\n";
|
||||
$oPage->add($sHtml);
|
||||
$oPage->add_ready_script("$('#dlg_{$this->m_iInputId}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, resizeStop: oForeignKeysWidget{$this->m_iInputId}.UpdateSizes });");
|
||||
$oPage->add_ready_script("$('#dlg_{$this->m_iInputId}').dialog('option', {title:'$sTitle'});");
|
||||
$oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_iInputId} form').bind('submit.uilinksWizard', oForeignKeysWidget{$this->m_iInputId}.SearchObjectsToAdd);");
|
||||
$oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_iInputId}').resize(oForeignKeysWidget{$this->m_iInputId}.UpdateSizes);");
|
||||
}
|
||||
|
||||
public function GetFullListForeignKeysFromSelection($oPage, $oFullSetFilter)
|
||||
{
|
||||
try
|
||||
{
|
||||
$aLinkedObjects = utils::ReadMultipleSelectionWithFriendlyname($oFullSetFilter);
|
||||
$oPage->add(json_encode($aLinkedObjects));
|
||||
}
|
||||
catch (CoreException $e)
|
||||
{
|
||||
http_response_code(500);
|
||||
$oPage->add(json_encode(array('error' => $e->GetMessage())));
|
||||
IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for objects to be linked to the current object (i.e "remote" objects)
|
||||
*
|
||||
* @param WebPage $oP The page used for the output (usually an AjaxWebPage)
|
||||
* @param string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of m_sRemoteClass
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function ListResultsSearchForeignKeys(WebPage $oP, $sRemoteClass = '')
|
||||
{
|
||||
if ($sRemoteClass != '')
|
||||
{
|
||||
// assert(MetaModel::IsParentClass($this->m_sRemoteClass, $sRemoteClass));
|
||||
$oFilter = new DBObjectSearch($sRemoteClass);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No remote class specified use the one defined in the linkedset
|
||||
$oFilter = new DBObjectSearch($this->m_sRemoteClass);
|
||||
}
|
||||
|
||||
$oBlock = new DisplayBlock($oFilter, 'list', false);
|
||||
$oBlock->Display($oP, "ResultsToAdd_{$this->m_iInputId}",
|
||||
array('menu' => false, 'cssCount' => "#count_{$this->m_iInputId}", 'selection_mode' => true, 'table_id' => "add_{$this->m_iInputId}"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -413,8 +413,10 @@ class utils
|
||||
|
||||
/**
|
||||
* Interprets the results posted by a normal or paginated list (in multiple selection mode)
|
||||
*
|
||||
* @param $oFullSetFilter DBSearch The criteria defining the whole sets of objects being selected
|
||||
* @return Array An arry of object IDs corresponding to the objects selected in the set
|
||||
*
|
||||
* @return Array An array of object IDs corresponding to the objects selected in the set
|
||||
*/
|
||||
public static function ReadMultipleSelection($oFullSetFilter)
|
||||
{
|
||||
@@ -448,6 +450,47 @@ class utils
|
||||
return $aSelectedObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interprets the results posted by a normal or paginated list (in multiple selection mode)
|
||||
*
|
||||
* @param DBSearch $oFullSetFilter The criteria defining the whole sets of objects being selected
|
||||
*
|
||||
* @return Array An array of object IDs:friendlyname corresponding to the objects selected in the set
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public static function ReadMultipleSelectionWithFriendlyname($oFullSetFilter)
|
||||
{
|
||||
$sSelectionMode = utils::ReadParam('selectionMode', '');
|
||||
|
||||
if ($sSelectionMode === '')
|
||||
{
|
||||
throw new CoreException('selectionMode is mandatory');
|
||||
}
|
||||
|
||||
// Paginated selection
|
||||
$aSelectedIds = utils::ReadParam('storedSelection', array());
|
||||
if ($sSelectionMode == 'positive')
|
||||
{
|
||||
// Only the explicitly listed items are selected
|
||||
$oFullSetFilter->AddCondition('id', $aSelectedIds, 'IN');
|
||||
}
|
||||
else
|
||||
{
|
||||
// All items of the set are selected, except the one explicitly listed
|
||||
$oFullSetFilter->AddCondition('id', $aSelectedIds, 'NOTIN');
|
||||
}
|
||||
$aSelectedObj = array();
|
||||
$oFullSet = new DBObjectSet($oFullSetFilter);
|
||||
$sClassAlias = $oFullSetFilter->GetClassAlias();
|
||||
$oFullSet->OptimizeColumnLoad(array($sClassAlias => array('friendlyname'))); // We really need only the IDs but it does not work since id is not a real field
|
||||
while ($oObj = $oFullSet->Fetch())
|
||||
{
|
||||
$aSelectedObj[$oObj->GetKey()] = $oObj->Get('friendlyname');
|
||||
}
|
||||
|
||||
return $aSelectedObj;
|
||||
}
|
||||
|
||||
public static function GetNewTransactionId()
|
||||
{
|
||||
return privUITransaction::GetNewTransactionId();
|
||||
|
||||
@@ -58,6 +58,7 @@ class WebPage implements Page
|
||||
protected $s_deferred_content;
|
||||
protected $a_scripts;
|
||||
protected $a_dict_entries;
|
||||
protected $a_dict_entries_prefixes;
|
||||
protected $a_styles;
|
||||
protected $a_include_scripts;
|
||||
protected $a_include_stylesheets;
|
||||
@@ -80,6 +81,7 @@ class WebPage implements Page
|
||||
$this->s_deferred_content = '';
|
||||
$this->a_scripts = array();
|
||||
$this->a_dict_entries = array();
|
||||
$this->a_dict_entries_prefixes = array();
|
||||
$this->a_styles = array();
|
||||
$this->a_linked_scripts = array();
|
||||
$this->a_linked_stylesheets = array();
|
||||
@@ -243,10 +245,39 @@ class WebPage implements Page
|
||||
/**
|
||||
* Add a dictionary entry for the Javascript side
|
||||
*/
|
||||
public function add_dict_entry($s_entryId)
|
||||
{
|
||||
$this->a_dict_entries[$s_entryId] = Dict::S($s_entryId);
|
||||
}
|
||||
public function add_dict_entry($s_entryId)
|
||||
{
|
||||
$this->a_dict_entries[] = $s_entryId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a set of dictionary entries (based on the given prefix) for the Javascript side
|
||||
*/
|
||||
public function add_dict_entries($s_entriesPrefix)
|
||||
{
|
||||
$this->a_dict_entries_prefixes[] = $s_entriesPrefix;
|
||||
}
|
||||
|
||||
protected function get_dict_signature()
|
||||
{
|
||||
return str_replace('_', '', Dict::GetUserLanguage()).'-'.md5(implode(',', $this->a_dict_entries).'|'.implode(',', $this->a_dict_entries_prefixes));
|
||||
}
|
||||
|
||||
protected function get_dict_file_content()
|
||||
{
|
||||
$aEntries = array();
|
||||
foreach($this->a_dict_entries as $sCode)
|
||||
{
|
||||
$aEntries[$sCode] = Dict::S($sCode);
|
||||
}
|
||||
foreach($this->a_dict_entries_prefixes as $sPrefix)
|
||||
{
|
||||
$aEntries = array_merge($aEntries, Dict::ExportEntries($sPrefix));
|
||||
}
|
||||
$sJSFile = 'var aDictEntries = '.json_encode($aEntries);
|
||||
|
||||
return $sJSFile;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -347,7 +378,7 @@ class WebPage implements Page
|
||||
*/
|
||||
public function GetDetails($aFields)
|
||||
{
|
||||
$sHtml = "<div class=\"details\">\n";
|
||||
$sHtml = "<div class=\"details\" id='search-widget-results-outer'>\n";
|
||||
foreach($aFields as $aAttrib)
|
||||
{
|
||||
$sDataAttCode = isset($aAttrib['attcode']) ? "data-attcode=\"{$aAttrib['attcode']}\"" : '';
|
||||
@@ -501,6 +532,9 @@ class WebPage implements Page
|
||||
echo "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, shrink-to-fit=no\" />";
|
||||
echo "<title>".htmlentities($this->s_title, ENT_QUOTES, 'UTF-8')."</title>\n";
|
||||
echo $this->get_base_tag();
|
||||
|
||||
$this->output_dict_entries();
|
||||
|
||||
foreach($this->a_linked_scripts as $s_script)
|
||||
{
|
||||
// Make sure that the URL to the script contains the application's version number
|
||||
@@ -524,7 +558,6 @@ class WebPage implements Page
|
||||
}
|
||||
echo "</script>\n";
|
||||
}
|
||||
$this->output_dict_entries();
|
||||
foreach($this->a_linked_stylesheets as $a_stylesheet)
|
||||
{
|
||||
if (strpos($a_stylesheet['link'], '?') === false)
|
||||
@@ -778,36 +811,21 @@ class WebPage implements Page
|
||||
|
||||
protected function output_dict_entries($bReturnOutput = false)
|
||||
{
|
||||
$sHtml = '';
|
||||
if (count($this->a_dict_entries)>0)
|
||||
if ((count($this->a_dict_entries) > 0) || (count($this->a_dict_entries_prefixes) > 0))
|
||||
{
|
||||
$sHtml .= "<script type=\"text/javascript\">\n";
|
||||
$sHtml .= "var Dict = {};\n";
|
||||
$sHtml .= "Dict._entries = {};\n";
|
||||
$sHtml .= "Dict.S = function(sEntry) {\n";
|
||||
$sHtml .= " if (sEntry in Dict._entries)\n";
|
||||
$sHtml .= " {\n";
|
||||
$sHtml .= " return Dict._entries[sEntry];\n";
|
||||
$sHtml .= " }\n";
|
||||
$sHtml .= " else\n";
|
||||
$sHtml .= " {\n";
|
||||
$sHtml .= " return sEntry;\n";
|
||||
$sHtml .= " }\n";
|
||||
$sHtml .= "};\n";
|
||||
foreach($this->a_dict_entries as $s_entry => $s_value)
|
||||
if (class_exists('Dict'))
|
||||
{
|
||||
$sHtml .= "Dict._entries['$s_entry'] = '".addslashes($s_value)."';\n";
|
||||
// The dictionary may not be available for example during the setup...
|
||||
// Create a specific dictionary file and load it as a JS script
|
||||
$sSignature = $this->get_dict_signature();
|
||||
$sJSFileName = utils::GetCachePath().$sSignature.'.js';
|
||||
if (!file_exists($sJSFileName) && is_writable(utils::GetCachePath()))
|
||||
{
|
||||
file_put_contents($sJSFileName, $this->get_dict_file_content());
|
||||
}
|
||||
// Load the dictionary as the first javascript file, so that other JS file benefit from the translations
|
||||
array_unshift($this->a_linked_scripts, utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php/?operation=dict&s='.$sSignature);
|
||||
}
|
||||
$sHtml .= "</script>\n";
|
||||
}
|
||||
|
||||
if ($bReturnOutput)
|
||||
{
|
||||
return $sHtml;
|
||||
}
|
||||
else
|
||||
{
|
||||
echo $sHtml;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,18 @@ define('LINKSET_EDITMODE_ADDREMOVE', 4); // The "linked" objects can be added/re
|
||||
*/
|
||||
abstract class AttributeDefinition
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE_RAW = 'raw';
|
||||
const SEARCH_WIDGET_TYPE_STRING = 'string';
|
||||
const SEARCH_WIDGET_TYPE_NUMERIC = 'numeric';
|
||||
const SEARCH_WIDGET_TYPE_ENUM = 'enum';
|
||||
const SEARCH_WIDGET_TYPE_EXTERNAL_KEY = 'external_key';
|
||||
const SEARCH_WIDGET_TYPE_HIERARCHICAL_KEY = 'hierarchical_key';
|
||||
const SEARCH_WIDGET_TYPE_EXTERNAL_FIELD = 'external_field';
|
||||
const SEARCH_WIDGET_TYPE_DATE_TIME = 'date_time';
|
||||
const SEARCH_WIDGET_TYPE_DATE = 'date';
|
||||
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
public function GetType()
|
||||
{
|
||||
return Dict::S('Core:'.get_class($this));
|
||||
@@ -122,6 +134,24 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function IsSearchable()
|
||||
{
|
||||
return static::SEARCH_WIDGET_TYPE != static::SEARCH_WIDGET_TYPE_RAW;
|
||||
}
|
||||
|
||||
protected $m_sCode;
|
||||
private $m_aParams = array();
|
||||
protected $m_sHostClass = '!undefined!';
|
||||
@@ -1617,6 +1647,8 @@ class AttributeDBField extends AttributeDBFieldVoid
|
||||
*/
|
||||
class AttributeInteger extends AttributeDBField
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_NUMERIC;
|
||||
|
||||
static public function ListExpectedParams()
|
||||
{
|
||||
return parent::ListExpectedParams();
|
||||
@@ -1710,6 +1742,8 @@ class AttributeInteger extends AttributeDBField
|
||||
*/
|
||||
class AttributeObjectKey extends AttributeDBFieldVoid
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_EXTERNAL_KEY;
|
||||
|
||||
static public function ListExpectedParams()
|
||||
{
|
||||
return array_merge(parent::ListExpectedParams(), array('class_attcode', 'is_null_allowed'));
|
||||
@@ -1765,6 +1799,8 @@ class AttributeObjectKey extends AttributeDBFieldVoid
|
||||
*/
|
||||
class AttributePercentage extends AttributeInteger
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_NUMERIC;
|
||||
|
||||
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
|
||||
{
|
||||
$iWidth = 5; // Total width of the percentage bar graph, in em...
|
||||
@@ -1804,6 +1840,8 @@ class AttributePercentage extends AttributeInteger
|
||||
*/
|
||||
class AttributeDecimal extends AttributeDBField
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_NUMERIC;
|
||||
|
||||
static public function ListExpectedParams()
|
||||
{
|
||||
return array_merge(parent::ListExpectedParams(), array('digits', 'decimals' /* including precision */));
|
||||
@@ -1902,6 +1940,8 @@ class AttributeDecimal extends AttributeDBField
|
||||
*/
|
||||
class AttributeBoolean extends AttributeInteger
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
static public function ListExpectedParams()
|
||||
{
|
||||
return parent::ListExpectedParams();
|
||||
@@ -2098,6 +2138,8 @@ class AttributeBoolean extends AttributeInteger
|
||||
*/
|
||||
class AttributeString extends AttributeDBField
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
|
||||
|
||||
static public function ListExpectedParams()
|
||||
{
|
||||
return parent::ListExpectedParams();
|
||||
@@ -2248,6 +2290,8 @@ class AttributeString extends AttributeDBField
|
||||
*/
|
||||
class AttributeClass extends AttributeString
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
static public function ListExpectedParams()
|
||||
{
|
||||
return array_merge(parent::ListExpectedParams(), array("class_category", "more_values"));
|
||||
@@ -2300,6 +2344,8 @@ class AttributeClass extends AttributeString
|
||||
*/
|
||||
class AttributeApplicationLanguage extends AttributeString
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
static public function ListExpectedParams()
|
||||
{
|
||||
return parent::ListExpectedParams();
|
||||
@@ -2336,6 +2382,8 @@ class AttributeApplicationLanguage extends AttributeString
|
||||
*/
|
||||
class AttributeFinalClass extends AttributeString
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
public function __construct($sCode, $aParams)
|
||||
{
|
||||
$this->m_sCode = $sCode;
|
||||
@@ -2490,6 +2538,8 @@ class AttributeFinalClass extends AttributeString
|
||||
*/
|
||||
class AttributePassword extends AttributeString
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
static public function ListExpectedParams()
|
||||
{
|
||||
return parent::ListExpectedParams();
|
||||
@@ -2542,6 +2592,8 @@ class AttributePassword extends AttributeString
|
||||
*/
|
||||
class AttributeEncryptedString extends AttributeString
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
static $sKey = null; // Encryption key used for all encrypted fields
|
||||
|
||||
public function __construct($sCode, $aParams)
|
||||
@@ -2984,6 +3036,8 @@ class AttributeLongText extends AttributeText
|
||||
*/
|
||||
class AttributeCaseLog extends AttributeLongText
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
public function GetNullValue()
|
||||
{
|
||||
return '';
|
||||
@@ -3403,6 +3457,8 @@ class AttributeIPAddress extends AttributeString
|
||||
*/
|
||||
class AttributeOQL extends AttributeText
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
public function GetEditClass() {return "OQLExpression";}
|
||||
}
|
||||
|
||||
@@ -3413,6 +3469,7 @@ class AttributeOQL extends AttributeText
|
||||
*/
|
||||
class AttributeTemplateString extends AttributeString
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3422,6 +3479,7 @@ class AttributeTemplateString extends AttributeString
|
||||
*/
|
||||
class AttributeTemplateText extends AttributeText
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3431,6 +3489,8 @@ class AttributeTemplateText extends AttributeText
|
||||
*/
|
||||
class AttributeTemplateHTML extends AttributeText
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
public function GetSQLColumns($bFullSpec = false)
|
||||
{
|
||||
$aColumns = array();
|
||||
@@ -3465,6 +3525,8 @@ class AttributeTemplateHTML extends AttributeText
|
||||
*/
|
||||
class AttributeEnum extends AttributeString
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_ENUM;
|
||||
|
||||
static public function ListExpectedParams()
|
||||
{
|
||||
return parent::ListExpectedParams();
|
||||
@@ -3892,6 +3954,8 @@ class AttributeMetaEnum extends AttributeEnum
|
||||
*/
|
||||
class AttributeDateTime extends AttributeDBField
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_DATE_TIME;
|
||||
|
||||
static $oFormat = null;
|
||||
|
||||
static public function GetFormat()
|
||||
@@ -4418,6 +4482,8 @@ class AttributeDuration extends AttributeInteger
|
||||
*/
|
||||
class AttributeDate extends AttributeDateTime
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_DATE;
|
||||
|
||||
static $oDateFormat = null;
|
||||
|
||||
static public function GetFormat()
|
||||
@@ -4562,6 +4628,8 @@ class AttributeDeadline extends AttributeDateTime
|
||||
*/
|
||||
class AttributeExternalKey extends AttributeDBFieldVoid
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_EXTERNAL_KEY;
|
||||
|
||||
static public function ListExpectedParams()
|
||||
{
|
||||
return array_merge(parent::ListExpectedParams(), array("targetclass", "is_null_allowed", "on_target_delete"));
|
||||
@@ -4767,6 +4835,8 @@ class AttributeExternalKey extends AttributeDBFieldVoid
|
||||
*/
|
||||
class AttributeHierarchicalKey extends AttributeExternalKey
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_HIERARCHICAL_KEY;
|
||||
|
||||
protected $m_sTargetClass;
|
||||
|
||||
static public function ListExpectedParams()
|
||||
@@ -4914,6 +4984,35 @@ class AttributeHierarchicalKey extends AttributeExternalKey
|
||||
*/
|
||||
class AttributeExternalField extends AttributeDefinition
|
||||
{
|
||||
/**
|
||||
* Return the search widget type corresponding to this attribute
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function GetSearchType()
|
||||
{
|
||||
// Not necessary the external key is already present
|
||||
if ($this->IsFriendlyName())
|
||||
{
|
||||
return self::SEARCH_WIDGET_TYPE_RAW;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$oRemoteAtt = $this->GetExtAttDef();
|
||||
if ($oRemoteAtt instanceof AttributeString)
|
||||
{
|
||||
return self::SEARCH_WIDGET_TYPE_EXTERNAL_FIELD;
|
||||
}
|
||||
}
|
||||
catch (CoreException $e)
|
||||
{
|
||||
}
|
||||
|
||||
return self::SEARCH_WIDGET_TYPE_RAW;
|
||||
}
|
||||
|
||||
|
||||
static public function ListExpectedParams()
|
||||
{
|
||||
return array_merge(parent::ListExpectedParams(), array("extkey_attcode", "target_attcode"));
|
||||
@@ -4965,6 +5064,23 @@ class AttributeExternalField extends AttributeDefinition
|
||||
}
|
||||
return $sLabel;
|
||||
}
|
||||
|
||||
public function GetLabelForSearchField()
|
||||
{
|
||||
$sLabel = parent::GetLabel('');
|
||||
if (strlen($sLabel) == 0)
|
||||
{
|
||||
$sKeyAttCode = $this->Get("extkey_attcode");
|
||||
$oExtKeyAttDef = MetaModel::GetAttributeDef($this->GetHostClass(), $sKeyAttCode);
|
||||
$sLabel = $oExtKeyAttDef->GetLabel($this->m_sCode);
|
||||
|
||||
$oRemoteAtt = $this->GetExtAttDef();
|
||||
$sLabel .= '->'.$oRemoteAtt->GetLabel($this->m_sCode);
|
||||
}
|
||||
|
||||
return $sLabel;
|
||||
}
|
||||
|
||||
public function GetDescription($sDefault = null)
|
||||
{
|
||||
$sLabel = parent::GetDescription('');
|
||||
@@ -5285,6 +5401,8 @@ class AttributeURL extends AttributeString
|
||||
*/
|
||||
class AttributeBlob extends AttributeDefinition
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
static public function ListExpectedParams()
|
||||
{
|
||||
return array_merge(parent::ListExpectedParams(), array("depends_on"));
|
||||
@@ -5612,6 +5730,8 @@ class AttributeImage extends AttributeBlob
|
||||
*/
|
||||
class AttributeStopWatch extends AttributeDefinition
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
static public function ListExpectedParams()
|
||||
{
|
||||
// The list of thresholds must be an array of iPercent => array of 'option' => value
|
||||
@@ -6280,6 +6400,8 @@ class AttributeStopWatch extends AttributeDefinition
|
||||
*/
|
||||
class AttributeSubItem extends AttributeDefinition
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
static public function ListExpectedParams()
|
||||
{
|
||||
return array_merge(parent::ListExpectedParams(), array('target_attcode', 'item_code'));
|
||||
@@ -6446,6 +6568,8 @@ class AttributeSubItem extends AttributeDefinition
|
||||
*/
|
||||
class AttributeOneWayPassword extends AttributeDefinition
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
static public function ListExpectedParams()
|
||||
{
|
||||
return array_merge(parent::ListExpectedParams(), array("depends_on"));
|
||||
@@ -6609,6 +6733,8 @@ class AttributeOneWayPassword extends AttributeDefinition
|
||||
// Indexed array having two dimensions
|
||||
class AttributeTable extends AttributeDBField
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
public function GetEditClass() {return "Table";}
|
||||
|
||||
protected function GetSQLCol($bFullSpec = false)
|
||||
@@ -6838,6 +6964,8 @@ class AttributePropertySet extends AttributeTable
|
||||
*/
|
||||
class AttributeFriendlyName extends AttributeDefinition
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
|
||||
|
||||
public function __construct($sCode)
|
||||
{
|
||||
$this->m_sCode = $sCode;
|
||||
@@ -7004,6 +7132,8 @@ class AttributeFriendlyName extends AttributeDefinition
|
||||
*/
|
||||
class AttributeRedundancySettings extends AttributeDBField
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
static public function ListExpectedParams()
|
||||
{
|
||||
return array('sql', 'relation_code', 'from_class', 'neighbour_id', 'enabled', 'enabled_mode', 'min_up', 'min_up_type', 'min_up_mode');
|
||||
@@ -7394,6 +7524,8 @@ class AttributeRedundancySettings extends AttributeDBField
|
||||
*/
|
||||
class AttributeCustomFields extends AttributeDefinition
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
|
||||
|
||||
static public function ListExpectedParams()
|
||||
{
|
||||
return array_merge(parent::ListExpectedParams(), array("handler_class"));
|
||||
|
||||
@@ -241,8 +241,7 @@ class CMDBSource
|
||||
}
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
throw new MySQLException('Could not connect to the DB server',
|
||||
array('host' => $sServer, 'user' => $sUser), $e);
|
||||
throw new MySQLException('Could not connect to the DB server', array('host' => $sServer, 'user' => $sUser), $e);
|
||||
}
|
||||
|
||||
if ($bCheckTlsAfterConnection
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* Class Dict
|
||||
* Management of localizable strings
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2018 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -65,6 +65,11 @@ class Dict
|
||||
protected static $m_aData = array();
|
||||
protected static $m_sApplicationPrefix = null;
|
||||
|
||||
/**
|
||||
* @param $sLanguageCode
|
||||
*
|
||||
* @throws \DictExceptionUnknownLanguage
|
||||
*/
|
||||
public static function SetDefaultLanguage($sLanguageCode)
|
||||
{
|
||||
if (!array_key_exists($sLanguageCode, self::$m_aLanguages))
|
||||
@@ -74,6 +79,11 @@ class Dict
|
||||
self::$m_sDefaultLanguage = $sLanguageCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sLanguageCode
|
||||
*
|
||||
* @throws \DictExceptionUnknownLanguage
|
||||
*/
|
||||
public static function SetUserLanguage($sLanguageCode)
|
||||
{
|
||||
if (!array_key_exists($sLanguageCode, self::$m_aLanguages))
|
||||
@@ -113,7 +123,6 @@ class Dict
|
||||
* @param string $sDefault Default value if there is no match in the dictionary
|
||||
* @param bool $bUserLanguageOnly True to allow the use of the default language as a fallback, false otherwise
|
||||
*
|
||||
* @throws DictExceptionMissingString
|
||||
* @return string
|
||||
*/
|
||||
public static function S($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
|
||||
@@ -124,7 +133,7 @@ class Dict
|
||||
|
||||
if (!array_key_exists(self::GetUserLanguage(), self::$m_aData))
|
||||
{
|
||||
// It may happen, when something happens before the dictionnaries get loaded
|
||||
// It may happen, when something happens before the dictionaries get loaded
|
||||
return $sStringCode;
|
||||
}
|
||||
$aCurrentDictionary = self::$m_aData[self::GetUserLanguage()];
|
||||
@@ -155,25 +164,12 @@ class Dict
|
||||
}
|
||||
// Could not find the string...
|
||||
//
|
||||
switch (self::$m_iErrorMode)
|
||||
if (is_null($sDefault))
|
||||
{
|
||||
case DICT_ERR_STRING:
|
||||
if (is_null($sDefault))
|
||||
{
|
||||
return $sStringCode;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $sDefault;
|
||||
}
|
||||
break;
|
||||
|
||||
case DICT_ERR_EXCEPTION:
|
||||
default:
|
||||
throw new DictExceptionMissingString(self::$m_sCurrentLanguage, $sStringCode);
|
||||
break;
|
||||
return $sStringCode;
|
||||
}
|
||||
return 'bug!';
|
||||
|
||||
return $sDefault;
|
||||
}
|
||||
|
||||
|
||||
@@ -285,6 +281,9 @@ class Dict
|
||||
|
||||
/**
|
||||
* Clone a string in every language (if it exists in that language)
|
||||
*
|
||||
* @param $sSourceCode
|
||||
* @param $sDestCode
|
||||
*/
|
||||
public static function CloneString($sSourceCode, $sDestCode)
|
||||
{
|
||||
@@ -357,5 +356,38 @@ class Dict
|
||||
// No need to actually load the strings since it's only used to know the list of languages
|
||||
// at setup time !!
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all the dictionary entries - of the given language - whose code matches the given prefix
|
||||
* missing entries in the current language will be replaced by entries in the default language
|
||||
* @param string $sStartingWith
|
||||
* @return string[]
|
||||
*/
|
||||
public static function ExportEntries($sStartingWith)
|
||||
{
|
||||
self::InitLangIfNeeded(self::GetUserLanguage());
|
||||
self::InitLangIfNeeded(self::$m_sDefaultLanguage);
|
||||
$aEntries = array();
|
||||
$iLength = strlen($sStartingWith);
|
||||
|
||||
// First prefill the array with entries from the default language
|
||||
foreach(self::$m_aData[self::$m_sDefaultLanguage] as $sCode => $sEntry)
|
||||
{
|
||||
if (substr($sCode, 0, $iLength) == $sStartingWith)
|
||||
{
|
||||
$aEntries[$sCode] = $sEntry;
|
||||
}
|
||||
}
|
||||
|
||||
// Now put (overwrite) the entries for the user language
|
||||
foreach(self::$m_aData[self::GetUserLanguage()] as $sCode => $sEntry)
|
||||
{
|
||||
if (substr($sCode, 0, $iLength) == $sStartingWith)
|
||||
{
|
||||
$aEntries[$sCode] = $sEntry;
|
||||
}
|
||||
}
|
||||
return $aEntries;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -1439,7 +1439,7 @@ class DisplayableGraph extends SimpleGraph
|
||||
$aExcludedByClass[get_class($oObj)][] = $oObj->GetKey();
|
||||
}
|
||||
$oP->add("<div class=\"not-printable\">\n");
|
||||
$oP->add("<div id=\"ds_flash\" class=\"SearchDrawer\" style=\"display:none;\">\n");
|
||||
$oP->add("<div id=\"ds_flash\" class=\"search_box\" style=\"display:none;\">\n");
|
||||
if (!$oP->IsPrintableVersion())
|
||||
{
|
||||
$oP->add_ready_script(
|
||||
|
||||
@@ -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));
|
||||
@@ -33,11 +34,36 @@ abstract class Expression
|
||||
|
||||
// recursive translation of identifiers
|
||||
abstract public function GetUnresolvedFields($sAlias, &$aUnresolved);
|
||||
|
||||
/**
|
||||
* @param array $aTranslationData
|
||||
* @param bool $bMatchAll
|
||||
* @param bool $bMarkFieldsAsResolved
|
||||
*
|
||||
* @return Expression Translated expression
|
||||
*/
|
||||
abstract public function Translate($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true);
|
||||
|
||||
// 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 AttributeDefinition $oAttDef
|
||||
*
|
||||
* @return array parameters for the search form
|
||||
*/
|
||||
public function Display($oSearch, &$aArgs = null, $oAttDef = null)
|
||||
{
|
||||
return $this->Render($aArgs);
|
||||
}
|
||||
|
||||
public function GetAttDef($aClasses = array())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively browse the expression tree
|
||||
* @param Closure $callback
|
||||
@@ -46,7 +72,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 +80,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 +97,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 +151,57 @@ 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_RAW,
|
||||
'oql' => $this->Render($aArgs, $bRetrofitParams),
|
||||
'label' => $this->Display($oSearch, $aArgs, $oAttDef),
|
||||
'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
|
||||
@@ -165,7 +232,7 @@ class SQLExpression extends Expression
|
||||
public function ApplyParameters($aArgs)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public function GetUnresolvedFields($sAlias, &$aUnresolved)
|
||||
{
|
||||
}
|
||||
@@ -183,12 +250,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 +311,7 @@ class BinaryExpression extends Expression
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function GetLeftExpr()
|
||||
{
|
||||
return $this->m_oLeftExpr;
|
||||
@@ -295,7 +362,7 @@ class BinaryExpression extends Expression
|
||||
$this->m_oRightExpr->ApplyParameters($aArgs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function GetUnresolvedFields($sAlias, &$aUnresolved)
|
||||
{
|
||||
$this->GetLeftExpr()->GetUnresolvedFields($sAlias, $aUnresolved);
|
||||
@@ -321,7 +388,15 @@ class BinaryExpression extends Expression
|
||||
$this->GetLeftExpr()->CollectUsedParents($aTable);
|
||||
$this->GetRightExpr()->CollectUsedParents($aTable);
|
||||
}
|
||||
|
||||
|
||||
public function GetAttDef($aClasses = array())
|
||||
{
|
||||
$oAttDef = $this->GetLeftExpr()->GetAttDef($aClasses);
|
||||
if (!is_null($oAttDef)) return $oAttDef;
|
||||
|
||||
return $this->GetRightExpr()->GetAttDef($aClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all constant expression of the form <field> = <scalar> or <field> = :<variable>
|
||||
* Could be extended to support <field> = <function><constant_expression>
|
||||
@@ -355,7 +430,7 @@ class BinaryExpression extends Expression
|
||||
}
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
|
||||
public function RenameParam($sOldName, $sNewName)
|
||||
{
|
||||
$this->GetLeftExpr()->RenameParam($sOldName, $sNewName);
|
||||
@@ -367,6 +442,131 @@ class BinaryExpression extends Expression
|
||||
$this->GetLeftExpr()->RenameAlias($sOldName, $sNewName);
|
||||
$this->GetRightExpr()->RenameAlias($sOldName, $sNewName);
|
||||
}
|
||||
|
||||
// recursive rendering
|
||||
public function Display($oSearch, &$aArgs = null, $oAttDef = null)
|
||||
{
|
||||
$bReverseOperator = false;
|
||||
$oLeftExpr = $this->GetLeftExpr();
|
||||
if ($oLeftExpr instanceof FieldExpression)
|
||||
{
|
||||
$oAttDef = $oLeftExpr->GetAttDef($oSearch->GetJoinedClasses());
|
||||
}
|
||||
$oRightExpr = $this->GetRightExpr();
|
||||
if ($oRightExpr instanceof FieldExpression)
|
||||
{
|
||||
$oAttDef = $oRightExpr->GetAttDef($oSearch->GetJoinedClasses());
|
||||
$bReverseOperator = true;
|
||||
}
|
||||
|
||||
$sLeft = $oLeftExpr->Display($oSearch, $aArgs, $oAttDef);
|
||||
$sRight = $oRightExpr->Display($oSearch, $aArgs, $oAttDef);
|
||||
|
||||
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 '>':
|
||||
$sOperator = '<';
|
||||
break;
|
||||
case '<':
|
||||
$sOperator = '>';
|
||||
break;
|
||||
case '>=':
|
||||
$sOperator = '<=';
|
||||
break;
|
||||
case '<=':
|
||||
$sOperator = '>=';
|
||||
break;
|
||||
default:
|
||||
$sOperator = $this->GetOperator();
|
||||
break;
|
||||
}
|
||||
$sOperator = $this->OperatorToNaturalLanguage($sOperator, $oAttDef);
|
||||
|
||||
return "({$sRight}{$sOperator}{$sLeft})";
|
||||
}
|
||||
|
||||
$sOperator = $this->GetOperator();
|
||||
$sOperator = $this->OperatorToNaturalLanguage($sOperator, $oAttDef);
|
||||
|
||||
return "({$sLeft}{$sOperator}{$sRight})";
|
||||
}
|
||||
|
||||
private function OperatorToNaturalLanguage($sOperator, $oAttDef)
|
||||
{
|
||||
if ($oAttDef instanceof AttributeDateTime)
|
||||
{
|
||||
return Dict::S('Expression:Operator:Date:'.$sOperator, " $sOperator ");
|
||||
}
|
||||
|
||||
return Dict::S('Expression:Operator:'.$sOperator, " $sOperator ");
|
||||
}
|
||||
|
||||
public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
|
||||
{
|
||||
$bReverseOperator = false;
|
||||
$oLeftExpr = $this->GetLeftExpr();
|
||||
$oRightExpr = $this->GetRightExpr();
|
||||
|
||||
$oAttDef = $oLeftExpr->GetAttDef($oSearch->GetJoinedClasses());
|
||||
|
||||
if (is_null($oAttDef))
|
||||
{
|
||||
$oAttDef = $oRightExpr->GetAttDef($oSearch->GetJoinedClasses());
|
||||
$bReverseOperator = true;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if ($bReverseOperator)
|
||||
{
|
||||
$aCriteria = array_merge($aCriteriaRight, $aCriteriaLeft);
|
||||
// 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 = array_merge($aCriteriaLeft, $aCriteriaRight);
|
||||
$aCriteria['operator'] = $this->GetOperator();
|
||||
}
|
||||
$aCriteria['oql'] = $this->Render($aArgs, $bRetrofitParams);
|
||||
$aCriteria['label'] = $this->Display($oSearch, $aArgs, $oAttDef);
|
||||
|
||||
if (isset($aCriteriaLeft['ref']) && isset($aCriteriaRight['ref']) && ($aCriteriaLeft['ref'] != $aCriteriaRight['ref']))
|
||||
{
|
||||
// Only one Field is supported in the expressions
|
||||
$aCriteria['widget'] = AttributeDefinition::SEARCH_WIDGET_TYPE_RAW;
|
||||
}
|
||||
|
||||
return $aCriteria;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -404,7 +604,7 @@ class UnaryExpression extends Expression
|
||||
public function ApplyParameters($aArgs)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public function GetUnresolvedFields($sAlias, &$aUnresolved)
|
||||
{
|
||||
}
|
||||
@@ -427,7 +627,7 @@ class UnaryExpression extends Expression
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
|
||||
public function RenameParam($sOldName, $sNewName)
|
||||
{
|
||||
// Do nothing
|
||||
@@ -451,6 +651,46 @@ class ScalarExpression extends UnaryExpression
|
||||
parent::__construct($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $oSearch
|
||||
* @param array $aArgs
|
||||
* @param AttributeDefinition $oAttDef
|
||||
*
|
||||
* @return array|string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function Display($oSearch, &$aArgs = null, $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)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (!($oAttDef instanceof AttributeDateTime))
|
||||
{
|
||||
return $oAttDef->GetAsPlainText($this->m_value);
|
||||
}
|
||||
}
|
||||
|
||||
if (strpos($this->m_value, '%') === 0)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->Render($aArgs);
|
||||
}
|
||||
|
||||
// recursive rendering
|
||||
public function Render(&$aArgs = null, $bRetrofitParams = false)
|
||||
{
|
||||
@@ -469,6 +709,54 @@ class ScalarExpression extends UnaryExpression
|
||||
{
|
||||
return clone $this;
|
||||
}
|
||||
|
||||
public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
|
||||
{
|
||||
$aCriteria = array();
|
||||
switch ((string)($this->m_value))
|
||||
{
|
||||
case '%Y-%m-%d':
|
||||
$aCriteria['date_type'] = 'd';
|
||||
break;
|
||||
case '%Y-%m':
|
||||
$aCriteria['date_type'] = 'm';
|
||||
break;
|
||||
case '%w':
|
||||
$aCriteria['date_type'] = 'w';
|
||||
break;
|
||||
default:
|
||||
$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;
|
||||
}
|
||||
}
|
||||
$aCriteria['values'] = array($aValue);
|
||||
break;
|
||||
}
|
||||
|
||||
return $aCriteria;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TrueExpression extends ScalarExpression
|
||||
@@ -525,6 +813,44 @@ 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 AttributeDefinition $oAttDef
|
||||
*
|
||||
* @return array|string
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionMissingString
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function Display($oSearch, &$aArgs = null, $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 +861,37 @@ 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];
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($this->m_sName == 'id')
|
||||
{
|
||||
$aParams = array(
|
||||
'default_value' => 0,
|
||||
'is_null_allowed' => false,
|
||||
'allowed_values' => null,
|
||||
'depends_on' => null,
|
||||
'sql' => 'id',
|
||||
);
|
||||
|
||||
return new AttributeInteger($this->m_sName, $aParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public function ListRequiredFields()
|
||||
{
|
||||
return array($this->m_sParent.'.'.$this->m_sName);
|
||||
@@ -565,8 +922,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 +953,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 +1005,47 @@ class FieldExpression extends UnaryExpression
|
||||
$this->m_sParent = $sNewName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $oSearch
|
||||
* @param null $aArgs
|
||||
* @param bool $bRetrofitParams
|
||||
* @param AttributeDefinition $oAttDef
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
|
||||
{
|
||||
$oAttDef = $this->GetAttDef($oSearch->GetJoinedClasses());
|
||||
if (!is_null($oAttDef))
|
||||
{
|
||||
$sSearchType = $oAttDef->GetSearchType();
|
||||
try
|
||||
{
|
||||
if ($sSearchType == AttributeDefinition::SEARCH_WIDGET_TYPE_EXTERNAL_KEY)
|
||||
{
|
||||
// TODO Check the type of external key ? (EXTKEY_ABSOLUTE or EXTKEY_RELATIVE)
|
||||
if (MetaModel::IsHierarchicalClass($oAttDef->GetTargetClass()))
|
||||
{
|
||||
$sSearchType = AttributeDefinition::SEARCH_WIDGET_TYPE_HIERARCHICAL_KEY;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (CoreException $e)
|
||||
{
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$sSearchType = AttributeDefinition::SEARCH_WIDGET_TYPE;
|
||||
}
|
||||
return array(
|
||||
'widget' => $sSearchType,
|
||||
'ref' => $this->GetParent().'.'.$this->GetName(),
|
||||
'class_alias' => $this->GetParent(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Has been resolved into an SQL expression
|
||||
@@ -678,7 +1078,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, $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);
|
||||
}
|
||||
|
||||
// recursive rendering
|
||||
public function Render(&$aArgs = null, $bRetrofitParams = false)
|
||||
@@ -729,7 +1190,7 @@ class VariableExpression extends UnaryExpression
|
||||
$this->m_sName = $sNewName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function GetAsScalar($aArgs)
|
||||
{
|
||||
$oRet = null;
|
||||
@@ -833,7 +1294,7 @@ class ListExpression extends Expression
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function GetUnresolvedFields($sAlias, &$aUnresolved)
|
||||
{
|
||||
foreach ($this->m_aExpressions as $oExpr)
|
||||
@@ -861,7 +1322,7 @@ class ListExpression extends Expression
|
||||
}
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
|
||||
public function CollectUsedParents(&$aTable)
|
||||
{
|
||||
foreach ($this->m_aExpressions as $oExpr)
|
||||
@@ -879,7 +1340,7 @@ class ListExpression extends Expression
|
||||
}
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
|
||||
public function RenameParam($sOldName, $sNewName)
|
||||
{
|
||||
$aRes = array();
|
||||
@@ -887,7 +1348,7 @@ class ListExpression extends Expression
|
||||
{
|
||||
$this->m_aExpressions[$key] = $oExpr->RenameParam($sOldName, $sNewName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function RenameAlias($sOldName, $sNewName)
|
||||
{
|
||||
@@ -896,7 +1357,34 @@ class ListExpression extends Expression
|
||||
{
|
||||
$oExpr->RenameAlias($sOldName, $sNewName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function GetAttDef($aClasses = array())
|
||||
{
|
||||
foreach($this->m_aExpressions as $oExpression)
|
||||
{
|
||||
$oAttDef = $oExpression->GetAttDef($aClasses);
|
||||
if (!is_null($oAttDef)) return $oAttDef;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -990,7 +1478,7 @@ class FunctionExpression extends Expression
|
||||
}
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
|
||||
public function CollectUsedParents(&$aTable)
|
||||
{
|
||||
foreach ($this->m_aArgs as $oExpr)
|
||||
@@ -1008,7 +1496,7 @@ class FunctionExpression extends Expression
|
||||
}
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
|
||||
public function RenameParam($sOldName, $sNewName)
|
||||
{
|
||||
foreach ($this->m_aArgs as $key => $oExpr)
|
||||
@@ -1025,14 +1513,26 @@ class FunctionExpression extends Expression
|
||||
}
|
||||
}
|
||||
|
||||
public function GetAttDef($aClasses = array())
|
||||
{
|
||||
foreach($this->m_aArgs as $oExpression)
|
||||
{
|
||||
$oAttDef = $oExpression->GetAttDef($aClasses);
|
||||
if (!is_null($oAttDef)) return $oAttDef;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
@@ -1095,6 +1595,87 @@ class FunctionExpression extends Expression
|
||||
}
|
||||
return $sRes;
|
||||
}
|
||||
|
||||
public function Display($oSearch, &$aArgs = null, $oAttDef = null)
|
||||
{
|
||||
$sOperation = '';
|
||||
$sVerb = '';
|
||||
switch ($this->m_sVerb)
|
||||
{
|
||||
case 'NOW':
|
||||
$sVerb = $this->VerbToNaturalLanguage();
|
||||
break;
|
||||
case 'DATE_SUB':
|
||||
$sVerb = ' -';
|
||||
break;
|
||||
case 'DATE_ADD':
|
||||
$sVerb = ' +';
|
||||
break;
|
||||
case 'DATE_FORMAT':
|
||||
break;
|
||||
default:
|
||||
return $this->Render($aArgs);
|
||||
}
|
||||
|
||||
foreach($this->m_aArgs as $oExpression)
|
||||
{
|
||||
if ($oExpression instanceof IntervalExpression)
|
||||
{
|
||||
$sOperation .= $sVerb;
|
||||
$sVerb = '';
|
||||
}
|
||||
$sOperation .= $oExpression->Display($oSearch, $aArgs, $oAttDef);
|
||||
}
|
||||
|
||||
if (!empty($sVerb))
|
||||
{
|
||||
$sOperation .= $sVerb;
|
||||
}
|
||||
return $sOperation;
|
||||
}
|
||||
|
||||
private function VerbToNaturalLanguage()
|
||||
{
|
||||
return Dict::S('Expression:Verb:'.$this->m_sVerb, " {$this->m_sVerb} ");
|
||||
}
|
||||
|
||||
public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
|
||||
{
|
||||
$aCriteria = array();
|
||||
switch ($this->m_sVerb)
|
||||
{
|
||||
case 'ISNULL':
|
||||
$aCriteria['operator'] = $this->m_sVerb;
|
||||
foreach($this->m_aArgs as $oExpression)
|
||||
{
|
||||
$aCriteria = array_merge($oExpression->GetCriterion($oSearch, $aArgs, $bRetrofitParams, $oAttDef), $aCriteria);
|
||||
}
|
||||
$aCriteria['has_undefined'] = true;
|
||||
break;
|
||||
|
||||
case 'NOW':
|
||||
$aCriteria = array('widget' => 'date_time');
|
||||
$aCriteria['is_relative'] = true;
|
||||
$aCriteria['verb'] = $this->m_sVerb;
|
||||
break;
|
||||
|
||||
case 'DATE_ADD':
|
||||
case 'DATE_SUB':
|
||||
case 'DATE_FORMAT':
|
||||
$aCriteria = array('widget' => 'date_time');
|
||||
foreach($this->m_aArgs as $oExpression)
|
||||
{
|
||||
$aCriteria = array_merge($oExpression->GetCriterion($oSearch, $aArgs, $bRetrofitParams, $oAttDef), $aCriteria);
|
||||
}
|
||||
$aCriteria['verb'] = $this->m_sVerb;
|
||||
break;
|
||||
|
||||
default:
|
||||
return parent::GetCriterion($oSearch, $aArgs, $bRetrofitParams, $oAttDef);
|
||||
}
|
||||
|
||||
return $aCriteria;
|
||||
}
|
||||
}
|
||||
|
||||
class IntervalExpression extends Expression
|
||||
@@ -1147,7 +1728,7 @@ class IntervalExpression extends Expression
|
||||
$this->m_oValue->ApplyParameters($aArgs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function GetUnresolvedFields($sAlias, &$aUnresolved)
|
||||
{
|
||||
$this->m_oValue->GetUnresolvedFields($sAlias, $aUnresolved);
|
||||
@@ -1171,16 +1752,29 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
|
||||
{
|
||||
$aCriteria = $this->m_oValue->GetCriterion($oSearch, $aArgs, $bRetrofitParams, $oAttDef);
|
||||
$aCriteria['unit'] = $this->m_sUnit;
|
||||
|
||||
return $aCriteria;
|
||||
}
|
||||
|
||||
public function Display($oSearch, &$aArgs = null, $oAttDef = null)
|
||||
{
|
||||
return $this->m_oValue->Render($aArgs).' '.Dict::S('Expression:Unit:Long:'.$this->m_sUnit, $this->m_sUnit);
|
||||
}
|
||||
}
|
||||
|
||||
class CharConcatExpression extends Expression
|
||||
@@ -1210,7 +1804,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 +1887,7 @@ class CharConcatExpression extends Expression
|
||||
{
|
||||
$this->m_aExpressions[$key] = $oExpr->RenameParam($sOldName, $sNewName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function RenameAlias($sOldName, $sNewName)
|
||||
{
|
||||
@@ -1301,7 +1895,7 @@ class CharConcatExpression extends Expression
|
||||
{
|
||||
$oExpr->RenameAlias($sOldName, $sNewName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1322,7 +1916,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 +2096,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);
|
||||
|
||||
@@ -1,8 +1,49 @@
|
||||
$highlight-color: #E87C1E;
|
||||
// Base colors
|
||||
$gray-base: #000 !default;
|
||||
$gray-darker: lighten($gray-base, 13.5%) !default; // #222
|
||||
$gray-dark: #444 !default;
|
||||
$gray: #777 !default;
|
||||
$gray-light: #808080 !default;
|
||||
$gray-lighter: #ddd !default;
|
||||
$gray-extra-light: #F1F1F1 !default;
|
||||
|
||||
$white: #FFFFFF !default;
|
||||
|
||||
$combodo-orange: #EA7D1E !default;
|
||||
$combodo-dark-gray: #585653 !default;
|
||||
|
||||
$combodo-orange-dark: darken($combodo-orange, 13.8%) !default;
|
||||
$combodo-orange-darker: darken($combodo-orange, 18%) !default;
|
||||
$combodo-dark-gray-dark: darken($combodo-dark-gray, 13.5%) !default;
|
||||
$combodo-dark-gray-darker: darken($combodo-dark-gray, 18%) !default;
|
||||
|
||||
// Vars
|
||||
$highlight-color: $combodo-orange;
|
||||
$grey-color: #555555;
|
||||
$complement-color: #1c94c4;
|
||||
$complement-light: #d6e8ef;
|
||||
$frame-background-color: #F1F1F1;
|
||||
$frame-background-color: $gray-extra-light;
|
||||
$text-color: #000;
|
||||
$box-radius: 0px;
|
||||
$box-shadow-regular: 0 1px 1px rgba(0, 0, 0, 0.15);
|
||||
// - Boxes
|
||||
//$search-criteria-box-color: #2D2D2D;
|
||||
//$search-criteria-box-bg-color: #f0f3f5;
|
||||
//$search-criteria-box-border-color: #3f7294;
|
||||
//$search-criteria-box-border: 1px solid $search-criteria-box-border-color;
|
||||
//$search-criteria-box-radius: 1px;
|
||||
//
|
||||
$search-criteria-box-color: #2D2D2D;
|
||||
$search-criteria-box-picto-color: #E87C1E;
|
||||
$search-criteria-box-bg-color: #EEEEEE;
|
||||
$search-criteria-box-hover-color: $white;
|
||||
$search-criteria-box-border-color: #CCCCCC;
|
||||
$search-criteria-box-border: 1px solid $search-criteria-box-border-color;
|
||||
$search-criteria-box-radius: 1px;
|
||||
//
|
||||
$search-add-criteria-box-color: $search-criteria-box-color;
|
||||
$search-add-criteria-box-bg-color: $white;
|
||||
$search-add-criteria-box-hover-color: $gray-extra-light;
|
||||
|
||||
// Beware the version number MUST be enclosed with quotes otherwise v2.3.0 becomes v2 0.3 .0
|
||||
$version: "v2.4.0";
|
||||
$version: "v2.4.0";
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -21,7 +21,7 @@ OS2Version: 0
|
||||
OS2_WeightWidthSlopeOnly: 0
|
||||
OS2_UseTypoMetrics: 1
|
||||
CreationTime: 1463745065
|
||||
ModificationTime: 1506001058
|
||||
ModificationTime: 1522325525
|
||||
OS2TypoAscent: 0
|
||||
OS2TypoAOffset: 1
|
||||
OS2TypoDescent: 0
|
||||
@@ -35,6 +35,7 @@ HheadAscent: 0
|
||||
HheadAOffset: 1
|
||||
HheadDescent: 0
|
||||
HheadDOffset: 1
|
||||
OS2Vendor: 'PfEd'
|
||||
MarkAttachClasses: 1
|
||||
DEI: 91125
|
||||
Encoding: ISO8859-1
|
||||
@@ -46,7 +47,7 @@ FitToEm: 0
|
||||
WinInfo: 0 31 10
|
||||
BeginPrivate: 0
|
||||
EndPrivate
|
||||
BeginChars: 256 11
|
||||
BeginChars: 256 13
|
||||
|
||||
StartChar: zero
|
||||
Encoding: 48 48 0
|
||||
@@ -940,7 +941,135 @@ SplineSet
|
||||
815.574 32.7998 814.774 33.5996 813.175 33.5996 c 2
|
||||
810.774 33.5996 l 1
|
||||
EndSplineSet
|
||||
Validated: 524321
|
||||
Validated: 33
|
||||
EndChar
|
||||
|
||||
StartChar: B
|
||||
Encoding: 66 66 11
|
||||
Width: 1024
|
||||
VWidth: 0
|
||||
HStem: 0 59<210.859 353.111 670.997 813.141> 103 28<483.475 540.525> 326 280<245 398 626 779>
|
||||
VStem: 87 62<121.402 265.716> 415 60<134.336 199.804> 549 60<134.336 199.804> 875 62<121.402 265.716>
|
||||
LayerCount: 3
|
||||
Fore
|
||||
SplineSet
|
||||
376 606 m 0
|
||||
420 606 456 571 456 527 c 0
|
||||
456 514 l 1
|
||||
458 444 l 1
|
||||
566 444 l 1
|
||||
568 514 l 1
|
||||
568 514 568 524 568 527 c 0
|
||||
568 571 604 606 648 606 c 0
|
||||
677 606 704 590 718 564 c 1
|
||||
719 564 l 1
|
||||
754 502 l 1
|
||||
781 491 803 472 817 446 c 1
|
||||
818 446 l 1
|
||||
914 283 l 1
|
||||
929 255 937 224 937 192 c 0
|
||||
937 87 852 0 747 0 c 0
|
||||
666 0 594 54 568 131 c 1
|
||||
555 114 534 103 512 103 c 0
|
||||
490 103 469 114 456 132 c 1
|
||||
431 55 358 0 277 0 c 0
|
||||
172 0 87 87 87 192 c 0
|
||||
87 224 95 255 110 283 c 1
|
||||
206 446 l 1
|
||||
207 446 l 1
|
||||
221 472 243 491 270 502 c 1
|
||||
306 564 l 1
|
||||
320 590 347 606 376 606 c 0
|
||||
282 326 m 0
|
||||
208 326 149 266 149 193 c 0
|
||||
149 119 208 59 282 59 c 0
|
||||
355 59 415 119 415 193 c 0
|
||||
415 266 355 326 282 326 c 0
|
||||
742 326 m 0
|
||||
669 326 609 266 609 193 c 0
|
||||
609 119 669 59 742 59 c 0
|
||||
816 59 875 119 875 193 c 0
|
||||
875 266 816 326 742 326 c 0
|
||||
512 204 m 0
|
||||
492 204 475 188 475 168 c 0
|
||||
475 148 492 131 512 131 c 0
|
||||
532 131 549 148 549 168 c 0
|
||||
549 188 532 204 512 204 c 0
|
||||
EndSplineSet
|
||||
EndChar
|
||||
|
||||
StartChar: b
|
||||
Encoding: 98 98 12
|
||||
Width: 1024
|
||||
VWidth: 0
|
||||
Flags: H
|
||||
HStem: -1 35<183.649 347.721 676.937 838.822> 198 25<478.803 548.794> 294 234<470.756 555.897>
|
||||
VStem: 75 44<83.375 145> 414 42<83.6665 369.146> 432 24<255 473> 569 42<83.6665 369.146> 569 24<255 473> 906 43<84.3258 145>
|
||||
LayerCount: 3
|
||||
Fore
|
||||
SplineSet
|
||||
352 616 m 0xf080
|
||||
390 616 423 593 420 563 c 1
|
||||
422 488 l 1
|
||||
452 468 454 441 454 441 c 1
|
||||
455 305 456 159 456 12 c 1
|
||||
453 -62 369 -121 266 -121 c 0
|
||||
161 -121 74 -59 74 17 c 0
|
||||
74 20 76 22 76 25 c 1
|
||||
74 25 l 1
|
||||
106 196 l 1
|
||||
106 196 l 1
|
||||
113 235 143 270 188 292 c 1
|
||||
186 292 l 1
|
||||
206 408 l 1
|
||||
206 408 228 457 274 487 c 1
|
||||
282 570 l 1
|
||||
284 570 l 1
|
||||
287 596 316 616 352 616 c 0xf080
|
||||
266 114 m 0
|
||||
185 114 118 69 118 14 c 0
|
||||
118 -41 185 -86 266 -86 c 0
|
||||
347 -86 414 -41 414 14 c 0
|
||||
414 69 347 114 266 114 c 0
|
||||
514 408 m 0
|
||||
554 408 589 384 592 353 c 1
|
||||
592 353 l 1
|
||||
592 135 l 1
|
||||
592 135 l 1
|
||||
590 103 555 78 514 78 c 0
|
||||
473 78 436 103 434 135 c 1
|
||||
432 135 l 1
|
||||
432 353 l 1
|
||||
434 353 l 1
|
||||
437 384 474 408 514 408 c 0
|
||||
466 138 m 0
|
||||
466 118 489 103 514 103 c 2
|
||||
539 103 560 118 560 138 c 0
|
||||
560 158 539 174 514 174 c 0
|
||||
489 174 466 158 466 138 c 0
|
||||
672 616 m 0
|
||||
708 616 737 596 740 570 c 1
|
||||
742 570 l 1
|
||||
750 487 l 1
|
||||
796 457 818 408 818 408 c 1
|
||||
838 292 l 1
|
||||
836 292 l 1
|
||||
881 270 911 235 918 196 c 1
|
||||
950 25 l 1
|
||||
948 25 l 1
|
||||
948 22 948 20 948 17 c 0
|
||||
948 -59 863 -121 758 -121 c 0
|
||||
655 -121 571 -62 568 12 c 1
|
||||
568 159 569 305 570 441 c 1
|
||||
570 441 572 468 602 488 c 1
|
||||
604 563 l 1
|
||||
601 593 634 616 672 616 c 0
|
||||
758 114 m 0
|
||||
677 114 610 69 610 14 c 0
|
||||
610 -41 677 -86 758 -86 c 0
|
||||
839 -86 906 -41 906 14 c 0
|
||||
906 69 839 114 758 114 c 0
|
||||
EndSplineSet
|
||||
EndChar
|
||||
EndChars
|
||||
EndSplineFont
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: 'CombodoRegular';
|
||||
src: url('combodo-webfont.woff2?v=2.0') format('woff2'),
|
||||
url('combodo-webfont.woff?v=2.0') format('woff'),
|
||||
url('combodo-webfont.ttf?v=2.0') format('truetype');
|
||||
src: url('combodo-webfont.woff2?v=2.1') format('woff2'),
|
||||
url('combodo-webfont.woff?v=2.1') format('woff'),
|
||||
url('combodo-webfont.ttf?v=2.1') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
|
||||
@@ -187,6 +187,12 @@
|
||||
.fc-closed-request:before {
|
||||
content: "3";
|
||||
}
|
||||
.fc-binoculars:before {
|
||||
content: "B";
|
||||
}
|
||||
.fc-binoculars-alt:before {
|
||||
content: "b";
|
||||
}
|
||||
.fc-combodo-icon-o:before {
|
||||
content: "C";
|
||||
}
|
||||
|
||||
35
css/font-combodo/glyphs/B.svg
Executable file
35
css/font-combodo/glyphs/B.svg
Executable file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg8"
|
||||
version="1.1"
|
||||
viewBox="0 0 264.58333 264.58333"
|
||||
height="1000"
|
||||
width="1000">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer2">
|
||||
<path
|
||||
id="path4516"
|
||||
transform="scale(0.26458333)"
|
||||
d="m 363.57227,214.44727 c -28.72645,0.0164 -55.09978,15.88172 -68.57422,41.25195 l -0.53907,-0.18164 -34.7832,60.49414 c -26.17161,10.74972 -47.91781,30.06712 -61.67773,54.78906 l -0.51367,-0.125 -93.94336,159.60352 0.33984,0.0918 c -14.914544,27.26255 -22.732489,57.83851 -22.734376,88.91407 -3.76e-4,102.32848 82.952766,185.2825 185.281246,185.2832 79.57685,-0.18509 150.14836,-51.16271 175.33008,-126.65039 12.75761,17.44216 33.06009,27.76494 54.66992,27.79687 21.38684,-0.0322 41.50695,-10.14508 54.29297,-27.28906 25.33697,75.22798 95.78021,125.95807 175.16016,126.14258 102.32848,-7e-4 185.28163,-82.95472 185.28125,-185.2832 -0.002,-31.07556 -7.81983,-61.65152 -22.73438,-88.91407 l 0.33985,-0.0918 -93.94336,-159.60352 -0.51367,0.125 c -13.75992,-24.72194 -35.50612,-44.03935 -61.67774,-54.78906 l -34.7832,-60.49414 -0.53906,0.18164 c -13.47444,-25.37023 -39.84777,-41.23555 -68.57422,-41.25195 -42.90975,4.5e-4 -77.69486,34.78556 -77.69531,77.69531 -0.7247,3.23852 -0.16993,12.87304 -0.16993,12.87304 l -2.25,67.8418 H 443.6875 l -2.25,-67.8418 c 0,0 -0.20029,-9.98923 -0.16992,-12.87304 -4.5e-4,-42.90975 -34.78556,-77.69486 -77.69531,-77.69531 z m -92.14454,274.125 c 71.79702,0 130,58.20298 130,130 0,71.79702 -58.20298,130 -130,130 -71.79702,0 -130,-58.20298 -130,-130 0,-71.79702 58.20298,-130 130,-130 z m 449.45313,0 c 71.79702,0 130,58.20298 130,130 0,71.79702 -58.20298,130 -130,130 -71.79702,0 -130,-58.20298 -130,-130 0,-71.79702 58.20298,-130 130,-130 z M 496.1543,607.14258 c 19.72489,-3.1e-4 35.71515,15.98995 35.71484,35.71484 3.1e-4,19.72489 -15.98995,35.71516 -35.71484,35.71485 -19.7249,3.1e-4 -35.71516,-15.98995 -35.71485,-35.71485 -3.1e-4,-19.72489 15.98996,-35.71515 35.71485,-35.71484 z"
|
||||
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:3.77952766;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
280
css/font-combodo/glyphs/b-lowercase.svg
Executable file
280
css/font-combodo/glyphs/b-lowercase.svg
Executable file
@@ -0,0 +1,280 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
id="svg8"
|
||||
version="1.1"
|
||||
viewBox="0 0 264.58333 264.58334"
|
||||
height="1000"
|
||||
width="1000">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
style="display:none"
|
||||
transform="translate(0,-32.41665)"
|
||||
id="layer1">
|
||||
<image
|
||||
width="262.41626"
|
||||
height="211.46167"
|
||||
preserveAspectRatio="none"
|
||||
style="image-rendering:optimizeQuality"
|
||||
xlink:href="data:image/png;base64,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
|
||||
AElFTkSuQmCC
|
||||
"
|
||||
id="image823"
|
||||
x="2.9734159"
|
||||
y="52.273003" />
|
||||
</g>
|
||||
<g
|
||||
id="layer2">
|
||||
<path
|
||||
id="path827-9"
|
||||
d="m 97.624888,39.796021 c -8.178314,0.006 -14.961537,4.568212 -15.54272,10.453627 l -0.226343,-0.0021 -1.86862,19.061368 c -10.48507,6.763065 -15.77113,18.088851 -15.77113,18.088851 l -4.276224,26.459893 0.258381,0.0661 C 50.006857,118.97418 43.274297,126.9138 41.68205,135.75964 l -0.183967,0.014 -7.216098,39.0214 0.316778,0.0109 c -0.07327,0.65029 -0.11842,1.30208 -0.135393,1.9544 -5e-6,17.33952 19.483317,31.39599 43.51724,31.39602 23.49434,-0.0182 42.72694,-13.48746 43.45006,-30.42967 0.0382,-33.56054 -0.12647,-67.01884 -0.28628,-98.079355 0,0 -0.58301,-6.16341 -7.34633,-10.666015 l -0.6258,-17.202566 c 0.0278,-0.242592 0.0447,-0.485771 0.0506,-0.729154 1.9e-4,-6.215145 -6.98334,-11.253585 -15.598012,-11.253579 z M 78.113934,154.49394 c 18.592694,-9e-5 33.665126,10.22596 33.665136,22.84046 0,12.61451 -15.072436,22.84056 -33.665136,22.84047 -18.592498,-1e-4 -33.664614,-10.2261 -33.664612,-22.84047 6e-6,-12.61437 15.07212,-22.84036 33.664612,-22.84046 z"
|
||||
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.36251795;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<path
|
||||
id="path898"
|
||||
d="m 134.63546,87.230775 a 17.907058,13.764381 0 0 0 -17.83715,12.695354 h -0.87127 v 49.712211 h 0.83251 a 17.907058,13.76438 0 0 0 17.87591,13.09635 17.907058,13.76438 0 0 0 17.86403,-13.09635 h 0.31057 V 99.926129 h -0.32711 A 17.907058,13.764381 0 0 0 134.63546,87.230775 Z m 0,53.624635 a 10.557147,8.1148223 0 0 1 10.55749,8.11475 10.557147,8.1148223 0 0 1 -10.55749,8.11475 10.557147,8.1148223 0 0 1 -10.55698,-8.11475 10.557147,8.1148223 0 0 1 10.55698,-8.11475 z"
|
||||
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.00007248;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<path
|
||||
id="path827-9-6"
|
||||
d="m 171.1122,39.796021 c 8.17832,0.006 14.96154,4.568212 15.54273,10.453627 l 0.22634,-0.0021 1.86862,19.061369 c 10.48507,6.763074 15.77113,18.08886 15.77113,18.08886 l 4.27623,26.459883 -0.25839,0.0661 c 10.19138,5.05042 16.92394,12.99004 18.51619,21.83588 l 0.18396,0.014 7.2161,39.0214 -0.31678,0.0109 c 0.0733,0.65029 0.11842,1.30208 0.1354,1.9544 0,17.33952 -19.48332,31.39599 -43.51725,31.39602 -23.49434,-0.0182 -42.72693,-13.48746 -43.45005,-30.42967 -0.0382,-33.56054 0.12647,-67.01884 0.28628,-98.079352 0,0 0.58301,-6.163411 7.34633,-10.666016 l 0.6258,-17.202565 c -0.0278,-0.242592 -0.0447,-0.485771 -0.0506,-0.729154 -1.9e-4,-6.215148 6.98334,-11.253588 15.598,-11.253582 z m 19.51096,114.697919 c -18.59269,-9e-5 -33.66512,10.22596 -33.66513,22.84046 0,12.61451 15.07243,22.84056 33.66513,22.84047 18.5925,-1e-4 33.66462,-10.2261 33.66461,-22.84047 0,-12.61437 -15.07212,-22.84036 -33.66461,-22.84046 z"
|
||||
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.36251807;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 21 KiB |
@@ -58,7 +58,7 @@ label {
|
||||
cursor: pointer;
|
||||
}
|
||||
.hilite, .hilite a, .hilite a:visited {
|
||||
color: #e87c1e;
|
||||
color: #ea7d1e;
|
||||
text-decoration: none;
|
||||
}
|
||||
table.datatable {
|
||||
@@ -114,7 +114,7 @@ table.listResults td .view-image img {
|
||||
cursor: pointer;
|
||||
margin-bottom: 3px;
|
||||
padding: 2px;
|
||||
background-color: #e87c1e;
|
||||
background-color: #ea7d1e;
|
||||
}
|
||||
.edit-image .edit-buttons .button.disabled {
|
||||
cursor: default;
|
||||
@@ -275,7 +275,7 @@ legend.transparent {
|
||||
}
|
||||
.ui-widget-content td a:hover, p a:hover, td a:hover {
|
||||
text-decoration: underline;
|
||||
color: #e87c1e;
|
||||
color: #ea7d1e;
|
||||
}
|
||||
.cke_reset_all *:hover {
|
||||
text-decoration: none;
|
||||
@@ -283,8 +283,8 @@ legend.transparent {
|
||||
}
|
||||
table.cke_dialog_contents a.cke_dialog_ui_button_ok {
|
||||
color: #000;
|
||||
border-color: #e87c1e;
|
||||
background: #e87c1e;
|
||||
border-color: #ea7d1e;
|
||||
background: #ea7d1e;
|
||||
}
|
||||
.cke_notifications_area {
|
||||
display: none;
|
||||
@@ -309,7 +309,7 @@ td a.mailto, td a.mailto:visited {
|
||||
}
|
||||
td a.mailto:hover {
|
||||
text-decoration: underline;
|
||||
color: #e87c1e;
|
||||
color: #ea7d1e;
|
||||
padding-left: 20px;
|
||||
background: url(../images/mail.png?v=v2.4.0) no-repeat left;
|
||||
}
|
||||
@@ -329,7 +329,7 @@ a.small_action {
|
||||
padding-left: 5px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
background: #e87c1e url(../images/actions_left.png?v=v2.4.0) no-repeat left;
|
||||
background: #ea7d1e url(../images/actions_left.png?v=v2.4.0) no-repeat left;
|
||||
}
|
||||
.actions_details span {
|
||||
background: url(../images/actions_right.png?v=v2.4.0) no-repeat right;
|
||||
@@ -424,7 +424,7 @@ div.ui-accordion-content {
|
||||
text-decoration: none;
|
||||
}
|
||||
.ui-accordion-content a:hover {
|
||||
color: #e87c1e;
|
||||
color: #ea7d1e;
|
||||
text-decoration: none;
|
||||
}
|
||||
.ui-accordion-content ul {
|
||||
@@ -455,7 +455,7 @@ a.CollapsibleLabel.open, td a.CollapsibleLabel.open {
|
||||
padding: 0px 0pt 0px 16px;
|
||||
font-size: 8pt;
|
||||
text-decoration: none;
|
||||
color: #e87c1e;
|
||||
color: #ea7d1e;
|
||||
background: url(../images/mini-arrow-orange-open.gif) no-repeat left;
|
||||
}
|
||||
.page_header {
|
||||
@@ -506,7 +506,7 @@ div.actions_menu > ul {
|
||||
nowidth: 70px;
|
||||
padding-left: 5px;
|
||||
/* Nasty work-around for IE... en attendant mieux */
|
||||
background: #e87c1e url(../images/actions_left.png?v=v2.4.0) no-repeat top left;
|
||||
background: #ea7d1e url(../images/actions_left.png?v=v2.4.0) no-repeat top left;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -577,7 +577,7 @@ div.actions_menu > ul > li {
|
||||
position: absolute;
|
||||
display: none;
|
||||
border-top: 1px solid white;
|
||||
z-index: 999;
|
||||
z-index: 1500;
|
||||
}
|
||||
.itop_popup li ul li, #logOffBtn li ul li {
|
||||
float: none;
|
||||
@@ -588,7 +588,7 @@ div.actions_menu > ul > li {
|
||||
text-align: left;
|
||||
}
|
||||
.itop_popup li ul li a:hover, #logOffBtn li ul li a:hover {
|
||||
background: #e87c1e;
|
||||
background: #ea7d1e;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -675,60 +675,6 @@ input.dp-applied {
|
||||
float: left;
|
||||
}
|
||||
/* For search forms */
|
||||
.SearchDrawer {
|
||||
border-top: 5px solid #1c94c4;
|
||||
border-left: 5px solid #1c94c4;
|
||||
border-right: 5px solid #1c94c4;
|
||||
border-bottom: 0;
|
||||
background: #d6e8ef;
|
||||
color: #000;
|
||||
padding: 10px;
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
.SearchDrawer label {
|
||||
background: #d6e8ef;
|
||||
color: #000;
|
||||
text-align: right;
|
||||
}
|
||||
.SearchDrawer h1 {
|
||||
color: #000;
|
||||
}
|
||||
.SearchDrawer .SearchAttribute > .field_input_zone {
|
||||
display: inline-block;
|
||||
}
|
||||
.SearchDrawer .SearchAttribute > .field_input_zone > .field_select_wrapper {
|
||||
display: inline-block;
|
||||
}
|
||||
.DrawerClosed {
|
||||
display: none;
|
||||
}
|
||||
.DrawerHandle {
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
background: url(../images/drawer-handle.gif) bottom no-repeat transparent;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
/* center the block */
|
||||
width: 100px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
}
|
||||
div.HRDrawer {
|
||||
height: 5px;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
background-color: #1c94c4;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
display: block;
|
||||
}
|
||||
.mini_tabs a {
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
@@ -753,6 +699,506 @@ div.HRDrawer {
|
||||
nopadding-right: 1em;
|
||||
margin-top: 0;
|
||||
}
|
||||
/* Search forms v2 */
|
||||
.search_box {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
z-index: 1100;
|
||||
/* To be over the table block/unblock UI. Not very sure about this. */
|
||||
/* Sizing reset */
|
||||
}
|
||||
.search_box * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.search_form_handler {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
font-size: 12px;
|
||||
border: 1px solid #3f7294;
|
||||
/* Sizing reset */
|
||||
/* Hyperlink reset */
|
||||
/* Input reset */
|
||||
/* List helpers */
|
||||
}
|
||||
.search_form_handler * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.search_form_handler a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
.search_form_handler input[type="text"], .search_form_handler select {
|
||||
padding: 1px 2px;
|
||||
}
|
||||
.search_form_handler.opened .sf_title .sft_toggler {
|
||||
transform: rotateX(180deg);
|
||||
}
|
||||
.search_form_handler.opened .sf_criterion_area {
|
||||
/*display: inherit;*/
|
||||
}
|
||||
.search_form_handler .sf_title {
|
||||
padding: 8px 10px;
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
background-color: #3f7294;
|
||||
cursor: pointer;
|
||||
/* Pictogram */
|
||||
}
|
||||
.search_form_handler .sf_title .sft_picto {
|
||||
display: none;
|
||||
/* TODO: Remove this class and the correspondig DOM element if this option is kept. */
|
||||
margin-right: 10px;
|
||||
}
|
||||
.search_form_handler .sf_title .sft_refresh, .search_form_handler .sf_title .sft_toggler {
|
||||
transition: color 0.2s ease-in-out, transform 0.4s ease-in-out;
|
||||
}
|
||||
.search_form_handler .sf_title .sft_refresh:hover, .search_form_handler .sf_title .sft_toggler:hover {
|
||||
color: #f1f1f1;
|
||||
}
|
||||
.search_form_handler .sf_title .sft_refresh {
|
||||
font-size: 10pt;
|
||||
line-height: 13pt;
|
||||
}
|
||||
.search_form_handler .sf_title .sft_toggler {
|
||||
margin-left: 0.7em;
|
||||
}
|
||||
.search_form_handler .sf_message {
|
||||
display: none;
|
||||
margin: 8px 8px 0px 8px;
|
||||
border-radius: 0px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area {
|
||||
/*display: none;*/
|
||||
padding: 8px 8px 3px 8px;
|
||||
/* padding-bottom must equals to padding-top - .search_form_criteria:margin-bottom */
|
||||
background-color: #fff;
|
||||
/* Common style between criterion and more criterion */
|
||||
/* Criteria tags */
|
||||
/* More criterion */
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria, .search_form_handler .sf_criterion_area .sf_more_criterion {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-bottom: 5px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.opened, .search_form_handler .sf_criterion_area .sf_more_criterion.opened {
|
||||
margin-bottom: 0px;
|
||||
/* To compensate the .sfc/.sfm_header:padding-bottom: 13px */
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.opened .sfc_header, .search_form_handler .sf_criterion_area .sf_more_criterion.opened .sfc_header, .search_form_handler .sf_criterion_area .search_form_criteria.opened .sfm_header, .search_form_handler .sf_criterion_area .sf_more_criterion.opened .sfm_header {
|
||||
border-bottom: none !important;
|
||||
box-shadow: none !important;
|
||||
padding-bottom: 13px;
|
||||
/* Must be equal to .search_form_criteria:margin-bottom + this:padding-bottom */
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria > *, .search_form_handler .sf_criterion_area .sf_more_criterion > * {
|
||||
padding: 7px 8px;
|
||||
vertical-align: top;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 1px;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group, .search_form_handler .sf_criterion_area .sf_more_criterion .sfc_form_group, .search_form_handler .sf_criterion_area .search_form_criteria .sfm_content, .search_form_handler .sf_criterion_area .sf_more_criterion .sfm_content {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
min-width: 100%;
|
||||
left: 0px;
|
||||
margin-top: -1px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria {
|
||||
margin-right: 30px;
|
||||
/* Non editable criteria */
|
||||
/* Draft criteria (modifications not applied) */
|
||||
/* Opened criteria (form group displayed) */
|
||||
/* Top left corner icons */
|
||||
/* Special criterion processing */
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.locked {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.locked .sfc_title {
|
||||
user-select: none;
|
||||
cursor: initial;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.draft .sfc_header, .search_form_handler .sf_criterion_area .search_form_criteria.draft .sfc_form_group {
|
||||
border-style: dashed;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.draft .sfc_title {
|
||||
font-style: italic;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.opened {
|
||||
z-index: 1;
|
||||
/* To be over other criterion */
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.opened .sfc_toggle {
|
||||
transform: rotateX(-180deg);
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.opened .sfc_form_group {
|
||||
display: block;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.opened_left .sfc_form_group {
|
||||
left: auto;
|
||||
right: 0px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria:not(:last-of-type)::after {
|
||||
/* TODO: Find an elegant way to do this, without hardcoding the content (could be a <style> in the markup) and margin. Note, only a few languages (hawaiian and stuff like this have more than 4 letters in for "and" word. */
|
||||
content: "and";
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: calc(100% + 0px);
|
||||
margin-right: 5px;
|
||||
margin-left: 5px;
|
||||
text-align: center;
|
||||
color: #808080;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria > * {
|
||||
background-color: #eee;
|
||||
color: #2d2d2d;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_toggle, .search_form_handler .sf_criterion_area .search_form_criteria .sfc_close {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
color: #e87c1e;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_locked {
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
color: #808080;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_toggle {
|
||||
display: inline-block;
|
||||
right: 23px;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_close, .search_form_handler .sf_criterion_area .search_form_criteria .sfc_locked {
|
||||
right: 7px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_title {
|
||||
max-width: 240px;
|
||||
padding-right: 30px;
|
||||
white-space: nowrap;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
cursor: pointer;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group {
|
||||
/* Form group (operators) is displayed only when the criteria is toggled to opened state */
|
||||
display: none;
|
||||
max-height: 520px;
|
||||
overflow: auto;
|
||||
/* Show only first operator in simple mode */
|
||||
/* Show all operators in advanced mode */
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators {
|
||||
font-size: 12px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator.force_hide {
|
||||
display: none !important;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator > label {
|
||||
line-height: 20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator > label > * {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator .sfc_op_radio {
|
||||
width: 12px;
|
||||
margin: 0px;
|
||||
margin-right: 7px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator .sfc_op_name {
|
||||
width: 90px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator .sfc_op_content input[type="text"] {
|
||||
width: 160px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator .sfc_opc_multichoices label > input {
|
||||
vertical-align: middle;
|
||||
margin-left: 0px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator .sfc_opc_multichoices .sfc_opc_mc_items {
|
||||
max-height: 445px;
|
||||
/* Must be less than .sfc_form_group:max-height - .sfc_opc_mc_toggler:height - .sfc_opc_mc_filter:height */
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
margin: 0px -8px;
|
||||
/* Compensate .sfc_opc_multichoices side padding so the hover style can take the full with */
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator .sfc_opc_multichoices .sfc_opc_mc_items .sfc_opc_mc_items_list.sfc_opc_mc_items_selected {
|
||||
position: relative;
|
||||
padding-top: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator .sfc_opc_multichoices .sfc_opc_mc_items .sfc_opc_mc_items_list.sfc_opc_mc_items_selected::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-top: 1px solid #ddd;
|
||||
width: calc(100% - 12px);
|
||||
/* minus margin-left x2 */
|
||||
margin-left: 6px;
|
||||
top: 0px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator .sfc_opc_multichoices .sfc_opc_mc_items .sfc_opc_mc_items_list .sfc_opc_mc_placeholder {
|
||||
padding: 15px 8px;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator .sfc_opc_multichoices .sfc_opc_mc_items .sfc_opc_mc_items_list .sfc_opc_mc_item {
|
||||
padding: 4px 8px;
|
||||
/* Putting back the padding remove by .sfc_opc_mc_items */
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator .sfc_opc_multichoices .sfc_opc_mc_items .sfc_opc_mc_items_list .sfc_opc_mc_item:hover {
|
||||
background-color: #fff;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator .sfc_opc_multichoices .sfc_opc_mc_items .sfc_opc_mc_items_list .sfc_opc_mc_item label {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_apply, .search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_cancel {
|
||||
margin-top: 8px;
|
||||
padding: 3px 6px;
|
||||
font-size: 11px;
|
||||
/* Not bold, so it looks like 10px/bold of more/less buttons */
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_apply {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_more, .search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_less {
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
right: 0px;
|
||||
cursor: pointer;
|
||||
color: #3f7294;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_more > span, .search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_less > span {
|
||||
margin-left: 3px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operator:not(:first-of-type), .search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operator:first-of-type .sfc_op_radio {
|
||||
display: none;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_less {
|
||||
display: none;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_more {
|
||||
display: inline-block;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group.advanced .sfc_fg_operator {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group.advanced .sfc_fg_operator:last-of-type {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group.advanced .sfc_fg_operator:not(:first-of-type), .search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group.advanced .sfc_fg_operator:first-of-type .sfc_op_radio {
|
||||
display: inherit;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group.advanced .sfc_fg_less {
|
||||
display: inline-block;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group.advanced .sfc_fg_more {
|
||||
display: none;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group.advanced .hide_on_advanced {
|
||||
display: none;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group:not(.advanced) .hide_on_less {
|
||||
display: none;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_raw > * {
|
||||
border-color: transparent;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_raw .sfc_title {
|
||||
cursor: initial;
|
||||
padding-right: 20px;
|
||||
/* Less than regular as there is no toggle icon */
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_raw .sfc_form_group {
|
||||
display: none;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_enum .sfc_form_group .sfc_fg_operator_in > label {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
line-height: initial;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_enum .sfc_form_group .sfc_fg_operator_in > label .sfc_op_content {
|
||||
width: 100%;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_numeric .sfc_form_group.advanced .sfc_fg_operator_between {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_numeric .sfc_fg_operator_between label.sfc_op_content_from_label, .search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_numeric .sfc_fg_operator_between label.sfc_op_content_until_label {
|
||||
width: 90px;
|
||||
display: inline-block;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_date_time .sfc_form_group.advanced .sfc_fg_operator_between, .search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_date .sfc_form_group.advanced .sfc_fg_operator_between {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_date_time .sfc_fg_operator_between_days input, .search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_date .sfc_fg_operator_between_days input {
|
||||
width: 135px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_date_time button.ui-datepicker-trigger, .search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_date button.ui-datepicker-trigger {
|
||||
background: none;
|
||||
border: none;
|
||||
height: 100%;
|
||||
padding: 2px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_date_time button.ui-datepicker-trigger img, .search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_date button.ui-datepicker-trigger img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .sf_more_criterion {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .sf_more_criterion.opened {
|
||||
z-index: 2;
|
||||
/* To be over criterion */
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .sf_more_criterion.opened .sfm_content {
|
||||
display: inherit;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .sf_more_criterion.opened_left .sfm_content {
|
||||
left: auto;
|
||||
right: 0px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .sf_more_criterion > * {
|
||||
background-color: #fff;
|
||||
color: #2d2d2d;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .sf_more_criterion .sfm_toggler .sfm_tg_title {
|
||||
margin-right: 7px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .sf_more_criterion .sfm_toggler .sfm_tg_icon {
|
||||
color: #e87c1e;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .sf_more_criterion .sfm_content {
|
||||
display: none;
|
||||
min-width: 200px;
|
||||
/* To avoid element going to thin on filter, not very slick */
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .sf_more_criterion .sfm_content .sfm_lists {
|
||||
margin: 0px -8px;
|
||||
padding: 0px 8px;
|
||||
max-height: 400px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .sf_more_criterion .sfm_content .sfm_lists .sfl_items > li:hover {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .sf_more_criterion .sfm_content .sfm_buttons {
|
||||
display: none;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .sf_more_criterion .sfm_content .sfm_buttons button {
|
||||
margin-top: 8px;
|
||||
margin-right: 5px;
|
||||
padding: 3px 6px;
|
||||
font-size: 11px;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .sf_more_criterion .sfm_content .sfm_buttons button:last-of-type {
|
||||
margin-right: 0px;
|
||||
}
|
||||
.search_form_handler .sf_list:not(:first-of-type) .sfl_title {
|
||||
border-top: 1px solid #ddd;
|
||||
padding-top: 8px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.search_form_handler .sf_list .sfl_title {
|
||||
font-weight: bold;
|
||||
}
|
||||
.search_form_handler .sf_list .sfl_items {
|
||||
margin: 5px -8px 0px -8px;
|
||||
padding: 0px;
|
||||
}
|
||||
.search_form_handler .sf_list .sfl_items > li {
|
||||
padding: 4px 8px;
|
||||
list-style: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.search_form_handler .sf_list .sfl_items > li:hover {
|
||||
background-color: #fff;
|
||||
}
|
||||
.search_form_handler .sf_list .sfl_items > li.sfl_i_placeholder {
|
||||
font-style: italic;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.search_form_handler .sf_list .sfl_items > li > label {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
.search_form_handler .sf_list .sfl_items > li > label > * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.search_form_handler .sf_list .sfl_items > li > label > input[type="checkbox"] {
|
||||
margin-left: 0px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.search_form_handler .sf_filter {
|
||||
position: relative;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.search_form_handler .sf_filter input, .search_form_handler .sf_filter button, .search_form_handler .sf_filter .sff_picto {
|
||||
vertical-align: middle;
|
||||
height: 22px;
|
||||
}
|
||||
.search_form_handler .sf_filter input, .search_form_handler .sf_filter button {
|
||||
border: 1px solid #ababab;
|
||||
}
|
||||
.search_form_handler .sf_filter input {
|
||||
width: 100%;
|
||||
}
|
||||
.search_form_handler .sf_filter button {
|
||||
width: 23px;
|
||||
/* Must be equals to .sf_filter > *:height */
|
||||
background-color: #fff;
|
||||
color: #ea7d1e;
|
||||
font-size: 10px;
|
||||
}
|
||||
.search_form_handler .sf_filter button:first-of-type {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.search_form_handler .sf_filter button:not(:first-of-type) {
|
||||
border-left: transparent;
|
||||
}
|
||||
.search_form_handler .sf_filter .sff_input_wrapper {
|
||||
position: relative;
|
||||
}
|
||||
.search_form_handler .sf_filter .sff_input_wrapper .sff_picto {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 2px;
|
||||
opacity: 0.6;
|
||||
user-select: none;
|
||||
}
|
||||
.search_form_handler .sf_filter .sff_input_wrapper .sff_reset {
|
||||
display: none;
|
||||
}
|
||||
.search_form_handler .sf_filter.sf_with_buttons input {
|
||||
width: calc(100% - 28px) !important;
|
||||
/* Minus button outter width (only one for now) */
|
||||
min-width: 120px;
|
||||
}
|
||||
.sf_results_area .sf_results_placeholder {
|
||||
margin-top: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
.sf_results_area .sf_results_placeholder button > span {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
.visible {
|
||||
display: initial !important;
|
||||
}
|
||||
.mandatory {
|
||||
border: 1px solid #f00;
|
||||
}
|
||||
@@ -892,7 +1338,7 @@ div#logo div {
|
||||
margin-top: 2px;
|
||||
margin-right: 4px;
|
||||
padding: 6px 9px;
|
||||
background-color: #e87c1e;
|
||||
background-color: #ea7d1e;
|
||||
color: white;
|
||||
border-radius: 6px;
|
||||
text-align: left;
|
||||
@@ -1499,7 +1945,7 @@ img.prev, img.first, img.next, img.last {
|
||||
}
|
||||
div.actions_button {
|
||||
float: right;
|
||||
background: #e87c1e url("../images/actions_left.png?v=v2.4.0") no-repeat scroll left top;
|
||||
background: #ea7d1e url("../images/actions_left.png?v=v2.4.0") no-repeat scroll left top;
|
||||
padding-left: 5px;
|
||||
margin-top: 0;
|
||||
margin-right: 10px;
|
||||
@@ -1507,7 +1953,7 @@ div.actions_button {
|
||||
vertical-align: middle;
|
||||
}
|
||||
div.actions_button a, .actions_button a:hover, .actions_button a:visited {
|
||||
background: #e87c1e url(../images/actions_bkg.png?v=v2.4.0) no-repeat scroll right top;
|
||||
background: #ea7d1e url(../images/actions_bkg.png?v=v2.4.0) no-repeat scroll right top;
|
||||
color: #fff;
|
||||
padding-right: 8px;
|
||||
cursor: pointer;
|
||||
@@ -1540,7 +1986,7 @@ select#org_id {
|
||||
position: relative;
|
||||
}
|
||||
.edit_mode .dashlet-selected {
|
||||
background: #e87c1e !important;
|
||||
background: #ea7d1e !important;
|
||||
padding: 5px;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -1703,10 +2149,10 @@ a.summary, a.summary:hover {
|
||||
position: absolute;
|
||||
display: none;
|
||||
border-top: 1px solid white;
|
||||
z-index: 999;
|
||||
z-index: 1500;
|
||||
}
|
||||
#DashboardMenu li ul li a:hover {
|
||||
background: #e87c1e;
|
||||
background: #ea7d1e;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
list-style: none;
|
||||
@@ -2063,7 +2509,7 @@ span.refresh-button {
|
||||
}
|
||||
#itop-breadcrumb .breadcrumb-item a:hover {
|
||||
text-decoration: none;
|
||||
color: #e87c1e;
|
||||
color: #ea7d1e;
|
||||
}
|
||||
#itop-breadcrumb .breadcrumb-item a::after {
|
||||
content: '';
|
||||
@@ -2127,7 +2573,7 @@ span.refresh-button {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
.object-ref-icon.fa {
|
||||
color: #e87c1e;
|
||||
color: #ea7d1e;
|
||||
font-size: smaller;
|
||||
vertical-align: 1px;
|
||||
margin-right: 1px;
|
||||
|
||||
@@ -646,7 +646,7 @@ div.actions_menu > ul > li {
|
||||
position: absolute;
|
||||
display: none;
|
||||
border-top: 1px solid white;
|
||||
z-index: 999;
|
||||
z-index: 1500;
|
||||
}
|
||||
|
||||
.itop_popup li ul li, #logOffBtn li ul li {
|
||||
@@ -756,65 +756,6 @@ input.dp-applied {
|
||||
}
|
||||
|
||||
/* For search forms */
|
||||
.SearchDrawer {
|
||||
//background: $complement-color url(../images/search-top-left-corner.png?v=#{$version}) top left no-repeat;
|
||||
border-top: 5px solid $complement-color;
|
||||
border-left: 5px solid $complement-color;
|
||||
border-right: 5px solid $complement-color;
|
||||
border-bottom: 0;
|
||||
background: $complement-light;
|
||||
color: #000;
|
||||
padding: 10px;
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
.SearchDrawer label {
|
||||
background: $complement-light;
|
||||
color: #000;
|
||||
text-align: right;
|
||||
}
|
||||
.SearchDrawer h1 {
|
||||
color: #000;
|
||||
}
|
||||
.SearchDrawer .SearchAttribute{
|
||||
> .field_input_zone{
|
||||
display: inline-block;
|
||||
|
||||
> .field_select_wrapper{
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
.DrawerClosed {
|
||||
display: none;
|
||||
}
|
||||
.DrawerHandle {
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
background: url(../images/drawer-handle.gif) bottom no-repeat transparent;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
/* center the block */
|
||||
width: 100px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
div.HRDrawer {
|
||||
height: 5px;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
background-color: $complement-color;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
display: block;
|
||||
}
|
||||
.mini_tabs a {
|
||||
text-decoration: none;
|
||||
font-weight:bold;
|
||||
@@ -839,6 +780,634 @@ div.HRDrawer {
|
||||
nopadding-right: 1em;
|
||||
margin-top: 0;
|
||||
}
|
||||
/* Search forms v2 */
|
||||
.search_box{
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
z-index: 1100; /* To be over the table block/unblock UI. Not very sure about this. */
|
||||
|
||||
/* Sizing reset */
|
||||
*{
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
.search_form_handler{
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
font-size: 12px;
|
||||
border: 1px solid #3F7294;
|
||||
|
||||
/* Sizing reset */
|
||||
*{
|
||||
box-sizing: border-box;
|
||||
}
|
||||
/* Hyperlink reset */
|
||||
a{
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
/* Input reset */
|
||||
input[type="text"],
|
||||
select{
|
||||
padding: 1px 2px;
|
||||
}
|
||||
|
||||
&.opened{
|
||||
.sf_title{
|
||||
.sft_toggler{
|
||||
transform: rotateX(180deg);
|
||||
}
|
||||
}
|
||||
.sf_criterion_area{
|
||||
/*display: inherit;*/
|
||||
}
|
||||
}
|
||||
|
||||
.sf_title{
|
||||
padding: 8px 10px;
|
||||
margin: 0;
|
||||
color: #ffffff;
|
||||
background-color: #3F7294;
|
||||
cursor: pointer;
|
||||
|
||||
/* Pictogram */
|
||||
.sft_picto{
|
||||
display: none; /* TODO: Remove this class and the correspondig DOM element if this option is kept. */
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.sft_refresh,
|
||||
.sft_toggler{
|
||||
transition: color 0.2s ease-in-out, transform 0.4s ease-in-out;
|
||||
|
||||
&:hover{
|
||||
color: $gray-extra-light;
|
||||
}
|
||||
}
|
||||
.sft_refresh{
|
||||
font-size: 10pt;
|
||||
line-height: 13pt;
|
||||
}
|
||||
.sft_toggler{
|
||||
margin-left: 0.7em;
|
||||
}
|
||||
}
|
||||
.sf_message{
|
||||
display: none;
|
||||
margin: 8px 8px 0px 8px;
|
||||
border-radius: 0px;
|
||||
}
|
||||
.sf_criterion_area{
|
||||
/*display: none;*/
|
||||
padding: 8px 8px 3px 8px; /* padding-bottom must equals to padding-top - .search_form_criteria:margin-bottom */
|
||||
background-color: $white;
|
||||
|
||||
/* Common style between criterion and more criterion */
|
||||
.search_form_criteria,
|
||||
.sf_more_criterion {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-bottom: 5px;
|
||||
vertical-align: top;
|
||||
|
||||
&.opened{
|
||||
margin-bottom: 0px; /* To compensate the .sfc/.sfm_header:padding-bottom: 13px */
|
||||
|
||||
.sfc_header,
|
||||
.sfm_header{
|
||||
border-bottom: none !important;
|
||||
box-shadow: none !important;
|
||||
padding-bottom: 13px; /* Must be equal to .search_form_criteria:margin-bottom + this:padding-bottom */
|
||||
}
|
||||
}
|
||||
|
||||
> *{
|
||||
padding: 7px 8px;
|
||||
vertical-align: top;
|
||||
border: $search-criteria-box-border;
|
||||
border-radius: $search-criteria-box-radius;
|
||||
box-shadow: $box-shadow-regular;
|
||||
}
|
||||
.sfc_form_group,
|
||||
.sfm_content{
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
min-width: 100%;
|
||||
left: 0px;
|
||||
margin-top: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Criteria tags */
|
||||
.search_form_criteria{
|
||||
margin-right: 30px;
|
||||
|
||||
/* Non editable criteria */
|
||||
&.locked{
|
||||
background-color: $gray-extra-light;
|
||||
|
||||
.sfc_title{
|
||||
user-select: none;
|
||||
cursor: initial;
|
||||
}
|
||||
}
|
||||
/* Draft criteria (modifications not applied) */
|
||||
&.draft{
|
||||
.sfc_header,
|
||||
.sfc_form_group{
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.sfc_title{
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
/* Opened criteria (form group displayed) */
|
||||
&.opened{
|
||||
z-index: 1; /* To be over other criterion */
|
||||
|
||||
.sfc_toggle{
|
||||
transform: rotateX(-180deg);
|
||||
}
|
||||
.sfc_form_group{
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
&.opened_left{
|
||||
.sfc_form_group{
|
||||
left: auto;
|
||||
right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:last-of-type)::after{
|
||||
/* TODO: Find an elegant way to do this, without hardcoding the content (could be a <style> in the markup) and margin. Note, only a few languages (hawaiian and stuff like this have more than 4 letters in for "and" word. */
|
||||
content: "and";
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: calc(100% + 0px);
|
||||
margin-right: 5px;
|
||||
margin-left: 5px;
|
||||
text-align: center;
|
||||
color: $gray-light;
|
||||
}
|
||||
|
||||
> *{
|
||||
background-color: $search-criteria-box-bg-color;
|
||||
color: $search-criteria-box-color;
|
||||
}
|
||||
|
||||
/* Top left corner icons */
|
||||
.sfc_toggle,
|
||||
.sfc_close{
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
color: $search-criteria-box-picto-color;
|
||||
}
|
||||
.sfc_locked{
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
color: $gray-light;
|
||||
}
|
||||
.sfc_toggle{
|
||||
display: inline-block;
|
||||
right: 23px;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
.sfc_close,
|
||||
.sfc_locked{
|
||||
right: 7px;
|
||||
}
|
||||
|
||||
.sfc_title{
|
||||
max-width: 240px;
|
||||
padding-right: 30px;
|
||||
white-space: nowrap;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
cursor: pointer;
|
||||
}
|
||||
.sfc_form_group{
|
||||
/* Form group (operators) is displayed only when the criteria is toggled to opened state */
|
||||
display: none;
|
||||
max-height: 520px;
|
||||
overflow: auto;
|
||||
|
||||
.sfc_fg_operators{
|
||||
font-size: 12px;
|
||||
|
||||
.sfc_fg_operator{
|
||||
&.force_hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
> label{
|
||||
line-height: 20px;
|
||||
white-space: nowrap;
|
||||
|
||||
> *{
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.sfc_op_radio{
|
||||
width: 12px;
|
||||
margin: 0px;
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
.sfc_op_name{
|
||||
width: 90px;
|
||||
}
|
||||
.sfc_op_content{
|
||||
input[type="text"]{
|
||||
width: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
.sfc_opc_multichoices{
|
||||
label > input{
|
||||
vertical-align: middle;
|
||||
margin-left: 0px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.sfc_opc_mc_toggler{
|
||||
|
||||
}
|
||||
.sfc_opc_mc_items{
|
||||
max-height: 445px; /* Must be less than .sfc_form_group:max-height - .sfc_opc_mc_toggler:height - .sfc_opc_mc_filter:height */
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
margin: 0px -8px; /* Compensate .sfc_opc_multichoices side padding so the hover style can take the full with */
|
||||
|
||||
.sfc_opc_mc_items_list{
|
||||
&.sfc_opc_mc_items_selected{
|
||||
position: relative;
|
||||
padding-top: 5px;
|
||||
margin-top: 5px;
|
||||
|
||||
&::before{
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-top: 1px solid $gray-lighter;
|
||||
width: calc(100% - 12px); /* minus margin-left x2 */
|
||||
margin-left: 6px;
|
||||
top: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.sfc_opc_mc_placeholder{
|
||||
padding: 15px 8px;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
}
|
||||
.sfc_opc_mc_item{
|
||||
padding: 4px 8px; /* Putting back the padding remove by .sfc_opc_mc_items */
|
||||
|
||||
&:hover{
|
||||
background-color: $search-criteria-box-hover-color;
|
||||
}
|
||||
|
||||
label{
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sfc_fg_apply,
|
||||
.sfc_fg_cancel{
|
||||
margin-top: 8px;
|
||||
padding: 3px 6px;
|
||||
font-size: 11px; /* Not bold, so it looks like 10px/bold of more/less buttons */
|
||||
}
|
||||
.sfc_fg_apply{
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.sfc_fg_more,
|
||||
.sfc_fg_less{
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
right: 0px;
|
||||
cursor: pointer;
|
||||
color: #3F7294;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
|
||||
> span{
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Show only first operator in simple mode */
|
||||
.sfc_fg_operator:not(:first-of-type),
|
||||
.sfc_fg_operator:first-of-type .sfc_op_radio{
|
||||
display: none;
|
||||
}
|
||||
.sfc_fg_less{
|
||||
display: none;
|
||||
}
|
||||
.sfc_fg_more{
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Show all operators in advanced mode */
|
||||
&.advanced{
|
||||
|
||||
.sfc_fg_operator{
|
||||
margin-bottom: 3px;
|
||||
|
||||
&:last-of-type{
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.sfc_fg_operator:not(:first-of-type),
|
||||
.sfc_fg_operator:first-of-type .sfc_op_radio{
|
||||
display: inherit;
|
||||
}
|
||||
.sfc_fg_less{
|
||||
display: inline-block;
|
||||
}
|
||||
.sfc_fg_more{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hide_on_advanced {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&:not(.advanced) {
|
||||
.hide_on_less {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Special criterion processing */
|
||||
&.search_form_criteria_raw{
|
||||
> *{
|
||||
border-color: transparent;
|
||||
}
|
||||
.sfc_title{
|
||||
cursor: initial;
|
||||
padding-right: 20px; /* Less than regular as there is no toggle icon */
|
||||
}
|
||||
.sfc_form_group{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&.search_form_criteria_enum{
|
||||
.sfc_form_group{
|
||||
.sfc_fg_operator_in{
|
||||
> label{
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
line-height: initial;
|
||||
white-space: nowrap;
|
||||
|
||||
.sfc_op_content{
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.search_form_criteria_numeric {
|
||||
.sfc_form_group.advanced {
|
||||
.sfc_fg_operator_between {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
.sfc_fg_operator_between {
|
||||
label {
|
||||
&.sfc_op_content_from_label, &.sfc_op_content_until_label {
|
||||
width: 90px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.search_form_criteria_date_time,
|
||||
&.search_form_criteria_date {
|
||||
.sfc_form_group.advanced {
|
||||
.sfc_fg_operator_between {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
.sfc_fg_operator_between_days {
|
||||
input {
|
||||
width: 135px;
|
||||
}
|
||||
}
|
||||
button.ui-datepicker-trigger {
|
||||
background: none;
|
||||
border: none;
|
||||
height: 100%;
|
||||
padding: 2px;
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* More criterion */
|
||||
.sf_more_criterion{
|
||||
margin-right: 10px;
|
||||
|
||||
&.opened{
|
||||
z-index: 2; /* To be over criterion */
|
||||
|
||||
.sfm_content{
|
||||
display: inherit;
|
||||
}
|
||||
}
|
||||
&.opened_left{
|
||||
.sfm_content{
|
||||
left: auto;
|
||||
right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
> *{
|
||||
background-color: $search-add-criteria-box-bg-color;
|
||||
color: $search-add-criteria-box-color;
|
||||
}
|
||||
.sfm_toggler{
|
||||
.sfm_tg_title{
|
||||
margin-right: 7px;
|
||||
}
|
||||
.sfm_tg_icon{
|
||||
color: $search-criteria-box-picto-color;
|
||||
}
|
||||
}
|
||||
.sfm_content{
|
||||
display: none;
|
||||
min-width: 200px; /* To avoid element going to thin on filter, not very slick */
|
||||
|
||||
.sfm_lists{
|
||||
margin: 0px -8px;
|
||||
padding: 0px 8px;
|
||||
max-height: 400px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
.sfl_items{
|
||||
> li{
|
||||
&:hover{
|
||||
background-color: $search-add-criteria-box-hover-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.sfm_buttons{
|
||||
display: none;
|
||||
|
||||
button{
|
||||
margin-top: 8px;
|
||||
margin-right: 5px;
|
||||
padding: 3px 6px;
|
||||
font-size: 11px;
|
||||
|
||||
&:last-of-type{
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* List helpers */
|
||||
.sf_list{
|
||||
&:not(:first-of-type){
|
||||
.sfl_title{
|
||||
border-top: 1px solid $gray-lighter;
|
||||
padding-top: 8px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.sfl_title{
|
||||
font-weight: bold;
|
||||
}
|
||||
.sfl_items{
|
||||
margin: 5px -8px 0px -8px;
|
||||
padding: 0px;
|
||||
|
||||
> li{
|
||||
padding: 4px 8px;
|
||||
list-style: none;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover{
|
||||
background-color: $white;
|
||||
}
|
||||
&.sfl_i_placeholder{
|
||||
font-style: italic;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
> label{
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
|
||||
> *{
|
||||
vertical-align: middle;
|
||||
}
|
||||
> input[type="checkbox"]{
|
||||
margin-left: 0px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.sf_filter{
|
||||
position: relative;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
input,
|
||||
button,
|
||||
.sff_picto{
|
||||
vertical-align: middle;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
input,
|
||||
button{
|
||||
border: 1px solid #ABABAB;
|
||||
}
|
||||
input{
|
||||
width: 100%;
|
||||
}
|
||||
button{
|
||||
width: 23px; /* Must be equals to .sf_filter > *:height */
|
||||
background-color: $white;
|
||||
color: $combodo-orange;
|
||||
font-size: 10px;
|
||||
|
||||
&:first-of-type{
|
||||
margin-left: 5px;
|
||||
}
|
||||
&:not(:first-of-type){
|
||||
border-left: transparent;
|
||||
}
|
||||
}
|
||||
.sff_input_wrapper{
|
||||
position: relative;
|
||||
|
||||
.sff_picto{
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 2px;
|
||||
opacity: 0.6;
|
||||
user-select: none;
|
||||
}
|
||||
.sff_reset{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.sf_with_buttons{
|
||||
input{
|
||||
width: calc(100% - 28px) !important; /* Minus button outter width (only one for now) */
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.sf_results_area{
|
||||
.sf_results_placeholder{
|
||||
margin-top: 100px;
|
||||
text-align: center;
|
||||
|
||||
button > span{
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hidden{
|
||||
display: none !important;
|
||||
}
|
||||
.visible{
|
||||
display: initial !important;
|
||||
}
|
||||
.mandatory {
|
||||
border: 1px solid #f00;
|
||||
}
|
||||
@@ -1881,7 +2450,7 @@ a.summary, a.summary:hover {
|
||||
position: absolute;
|
||||
display: none;
|
||||
border-top: 1px solid white;
|
||||
z-index: 999;
|
||||
z-index: 1500;
|
||||
}
|
||||
#DashboardMenu li ul li a:hover{
|
||||
background: $popup-menu-highlight-color;
|
||||
|
||||
@@ -311,6 +311,17 @@ Dict::Add('EN US', 'English', 'English', array(
|
||||
'Class:URP_AttributeGrant/Attribute:attcode+' => 'attribute code',
|
||||
));
|
||||
|
||||
//
|
||||
// Expression to Natural language
|
||||
//
|
||||
Dict::Add('EN US', 'English', 'English', array(
|
||||
'Expression:Unit:Short:DAY' => 'd',
|
||||
'Expression:Unit:Short:WEEK' => 'w',
|
||||
'Expression:Unit:Short:MONTH' => 'm',
|
||||
'Expression:Unit:Short:YEAR' => 'y',
|
||||
));
|
||||
|
||||
|
||||
//
|
||||
// String from the User Interface: menu, messages, buttons, etc...
|
||||
//
|
||||
@@ -367,6 +378,7 @@ Dict::Add('EN US', 'English', 'English', array(
|
||||
'UI:Button:Ok' => 'Ok',
|
||||
'UI:Button:Save' => 'Save',
|
||||
'UI:Button:Cancel' => 'Cancel',
|
||||
'UI:Button:Close' => 'Close',
|
||||
'UI:Button:Apply' => 'Apply',
|
||||
'UI:Button:Back' => ' << Back ',
|
||||
'UI:Button:Restart' => ' |<< Restart ',
|
||||
@@ -390,6 +402,8 @@ Dict::Add('EN US', 'English', 'English', array(
|
||||
'UI:Button:ChangePassword' => ' Change Password ',
|
||||
'UI:Button:ResetPassword' => ' Reset Password ',
|
||||
'UI:Button:Insert' => 'Insert',
|
||||
'UI:Button:More' => 'More',
|
||||
'UI:Button:Less' => 'Less',
|
||||
|
||||
'UI:SearchToggle' => 'Search',
|
||||
'UI:ClickToCreateNew' => 'Create a new %1$s',
|
||||
@@ -1368,4 +1382,116 @@ When associated with a trigger, each action is given an "order" number, specifyi
|
||||
'UI:Button:ResetImage' => 'Recover the previous image',
|
||||
'UI:Button:RemoveImage' => 'Remove the image',
|
||||
'UI:UploadNotSupportedInThisMode' => 'The modification of images or files is not supported in this mode.',
|
||||
|
||||
// TODO: Reorganize those entries with other search entries and make entries for other languages.
|
||||
// Search form
|
||||
'UI:Search:Toggle' => 'Minimize / Expand',
|
||||
'UI:Search:Criterion:MoreMenu:AddCriteria' => 'Add new criteria',
|
||||
// - Add new criteria button
|
||||
'UI:Search:AddCriteria:List:RecentlyUsed:Title' => 'Recently used',
|
||||
'UI:Search:AddCriteria:List:MostPopular:Title' => 'Most popular',
|
||||
'UI:Search:AddCriteria:List:Others:Title' => 'Others',
|
||||
'UI:Search:AddCriteria:List:RecentlyUsed:Placeholder' => 'None yet.',
|
||||
|
||||
// - Criteria titles
|
||||
// - Default widget
|
||||
'UI:Search:Criteria:Title:Default:Empty' => '%1$s is empty',
|
||||
'UI:Search:Criteria:Title:Default:NotEmpty' => '%1$s is not empty',
|
||||
'UI:Search:Criteria:Title:Default:Equals' => '%1$s equals %2$s',
|
||||
'UI:Search:Criteria:Title:Default:Contains' => '%1$s contains %2$s',
|
||||
'UI:Search:Criteria:Title:Default:StartsWith' => '%1$s starts with %2$s',
|
||||
'UI:Search:Criteria:Title:Default:EndsWith' => '%1$s ends with %2$s',
|
||||
'UI:Search:Criteria:Title:Default:RegExp' => '%1$s matches %2$s',
|
||||
'UI:Search:Criteria:Title:Default:GreaterThan' => '%1$s > %2$s',
|
||||
'UI:Search:Criteria:Title:Default:GreaterThanOrEquals' => '%1$s >= %2$s',
|
||||
'UI:Search:Criteria:Title:Default:LessThan' => '%1$s < %2$s',
|
||||
'UI:Search:Criteria:Title:Default:LessThanOrEquals' => '%1$s <= %2$s',
|
||||
'UI:Search:Criteria:Title:Default:Different' => '%1$s ≠ %2$s',
|
||||
'UI:Search:Criteria:Title:Default:Between' => '%1$s between [%2$s]',
|
||||
'UI:Search:Criteria:Title:Default:BetweenDates:All' => '%1$s: Any',
|
||||
'UI:Search:Criteria:Title:Default:BetweenDates:From' => '%1$s from %2$s',
|
||||
'UI:Search:Criteria:Title:Default:BetweenDates:Until' => '%1$s until %2$s',
|
||||
'UI:Search:Criteria:Title:Default:Between:All' => '%1$s: Any',
|
||||
'UI:Search:Criteria:Title:Default:Between:From' => '%1$s from %2$s',
|
||||
'UI:Search:Criteria:Title:Default:Between:Until' => '%1$s until %2$s',
|
||||
'UI:Search:Criteria:Title:Default:BetweenDays' => '%1$s [%2$s]',
|
||||
// - Numeric widget
|
||||
// None yet
|
||||
// - DateTime widget
|
||||
'UI:Search:Criteria:Title:DateTime:Between' => '%2$s <= 1$s <= %3$s',
|
||||
// - Enum widget
|
||||
'UI:Search:Criteria:Title:Enum:In' => '%1$s: %2$s',
|
||||
'UI:Search:Criteria:Title:Enum:In:Many' => '%1$s: %2$s and %3$s others',
|
||||
'UI:Search:Criteria:Title:Enum:In:All' => '%1$s: Any',
|
||||
// - External key widget
|
||||
'UI:Search:Criteria:Title:ExternalKey:Empty' => '%1$s is defined',
|
||||
'UI:Search:Criteria:Title:ExternalKey:NotEmpty' => '%1$s is not defined',
|
||||
'UI:Search:Criteria:Title:ExternalKey:Equals' => '%1$s %2$s',
|
||||
'UI:Search:Criteria:Title:ExternalKey:In' => '%1$s: %2$s',
|
||||
'UI:Search:Criteria:Title:ExternalKey:In:Many' => '%1$s: %2$s and %3$s others',
|
||||
'UI:Search:Criteria:Title:ExternalKey:In:All' => '%1$s: Any',
|
||||
|
||||
// - Criteria operators
|
||||
// - Default widget
|
||||
'UI:Search:Criteria:Operator:Default:Empty' => 'Is empty',
|
||||
'UI:Search:Criteria:Operator:Default:NotEmpty' => 'Is not empty',
|
||||
'UI:Search:Criteria:Operator:Default:Equals' => 'Equals',
|
||||
'UI:Search:Criteria:Operator:Default:Between' => 'Between',
|
||||
// - String widget
|
||||
'UI:Search:Criteria:Operator:String:Contains' => 'Contains',
|
||||
'UI:Search:Criteria:Operator:String:StartsWith' => 'Starts with',
|
||||
'UI:Search:Criteria:Operator:String:EndsWith' => 'Ends with',
|
||||
'UI:Search:Criteria:Operator:String:RegExp' => 'Regular exp.',
|
||||
// - Numeric widget
|
||||
'UI:Search:Criteria:Operator:Numeric:Equals' => 'Equals', // => '=',
|
||||
'UI:Search:Criteria:Operator:Numeric:GreaterThan' => 'Greater', // => '>',
|
||||
'UI:Search:Criteria:Operator:Numeric:GreaterThanOrEquals' => 'Greater / equals', // > '>=',
|
||||
'UI:Search:Criteria:Operator:Numeric:LessThan' => 'Less', // => '<',
|
||||
'UI:Search:Criteria:Operator:Numeric:LessThanOrEquals' => 'Less / equals', // > '<=',
|
||||
'UI:Search:Criteria:Operator:Numeric:Different' => 'Different', // => '≠',
|
||||
|
||||
// - Other translations
|
||||
'UI:Search:Value:Filter:Placeholder' => 'Filter...',
|
||||
'UI:Search:Value:Search:Placeholder' => 'Search...',
|
||||
'UI:Search:Value:Autocomplete:StartTyping' => 'Start typing for possible values.',
|
||||
'UI:Search:Value:Autocomplete:Wait' => 'Please wait...',
|
||||
'UI:Search:Value:Autocomplete:NoResult' => 'No result.',
|
||||
'UI:Search:Value:Toggler:CheckAllNone' => 'Check all / none',
|
||||
'UI:Search:Value:Toggler:CheckAllNoneFiltered' => 'Check all / none filtered',
|
||||
|
||||
// - Widget other translations
|
||||
'UI:Search:Criteria:Numeric:From' => 'From',
|
||||
'UI:Search:Criteria:Numeric:Until' => 'Until',
|
||||
'UI:Search:Criteria:Numeric:PlaceholderFrom' => 'Any',
|
||||
'UI:Search:Criteria:Numeric:PlaceholderUntil' => 'Any',
|
||||
'UI:Search:Criteria:DateTime:From' => 'From',
|
||||
'UI:Search:Criteria:DateTime:FromTime' => 'From',
|
||||
'UI:Search:Criteria:DateTime:Until' => 'until',
|
||||
'UI:Search:Criteria:DateTime:UntilTime' => 'until',
|
||||
'UI:Search:Criteria:DateTime:PlaceholderFrom' => 'Any date',
|
||||
'UI:Search:Criteria:DateTime:PlaceholderFromTime' => 'Any date',
|
||||
'UI:Search:Criteria:DateTime:PlaceholderUntil' => 'Any date',
|
||||
'UI:Search:Criteria:DateTime:PlaceholderUntilTime' => 'Any date',
|
||||
|
||||
'UI:Search:Criteria:Raw:Filtered' => 'Filtered',
|
||||
'UI:Search:Criteria:Raw:FilteredOn' => 'Filtered on %1$s',
|
||||
));
|
||||
|
||||
//
|
||||
// Expression to Natural language
|
||||
//
|
||||
Dict::Add('EN US', 'English', 'English', array(
|
||||
'Expression:Operator:AND' => ' AND ',
|
||||
'Expression:Operator:OR' => ' OR ',
|
||||
|
||||
'Expression:Unit:Short:DAY' => 'd',
|
||||
'Expression:Unit:Short:WEEK' => 'w',
|
||||
'Expression:Unit:Short:MONTH' => 'm',
|
||||
'Expression:Unit:Short:YEAR' => 'y',
|
||||
|
||||
'Expression:Unit:Long:DAY' => 'day(s)',
|
||||
'Expression:Unit:Long:HOUR' => 'hour(s)',
|
||||
'Expression:Unit:Long:MINUTE' => 'minute(s)',
|
||||
|
||||
'Expression:Verb:NOW' => 'now',
|
||||
));
|
||||
|
||||
@@ -1200,4 +1200,116 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
|
||||
'UI:Button:ResetImage' => 'Récupérer l\'image initiale',
|
||||
'UI:Button:RemoveImage' => 'Supprimer l\'image',
|
||||
'UI:UploadNotSupportedInThisMode' => 'La modification d\'images ou de fichiers n\'est pas supportée dans ce mode.',
|
||||
));
|
||||
|
||||
// TODO: Reorganize those entries with other search entries and make entries for other languages.
|
||||
// Search form
|
||||
'UI:Search:Toggle' => 'Réduire / Ouvrir',
|
||||
'UI:Search:Criterion:MoreMenu:AddCriteria' => 'Ajouter un critère',
|
||||
// - Add new criteria button
|
||||
'UI:Search:AddCriteria:List:RecentlyUsed:Title' => 'Récents',
|
||||
'UI:Search:AddCriteria:List:MostPopular:Title' => 'Populaires',
|
||||
'UI:Search:AddCriteria:List:Others:Title' => 'Autres',
|
||||
'UI:Search:AddCriteria:List:RecentlyUsed:Placeholder' => 'Aucun.',
|
||||
|
||||
// - Criteria titles
|
||||
// - Default widget
|
||||
'UI:Search:Criteria:Title:Default:Empty' => '%1$s vide',
|
||||
'UI:Search:Criteria:Title:Default:NotEmpty' => '%1$s non vide',
|
||||
'UI:Search:Criteria:Title:Default:Equals' => '%1$s égal %2$s',
|
||||
'UI:Search:Criteria:Title:Default:Contains' => '%1$s contient %2$s',
|
||||
'UI:Search:Criteria:Title:Default:StartsWith' => '%1$s commence par %2$s',
|
||||
'UI:Search:Criteria:Title:Default:EndsWith' => '%1$s fini par %2$s',
|
||||
'UI:Search:Criteria:Title:Default:RegExp' => '%1$s correspond à %2$s',
|
||||
'UI:Search:Criteria:Title:Default:GreaterThan' => '%1$s > %2$s',
|
||||
'UI:Search:Criteria:Title:Default:GreaterThanOrEquals' => '%1$s >= %2$s',
|
||||
'UI:Search:Criteria:Title:Default:LessThan' => '%1$s < %2$s',
|
||||
'UI:Search:Criteria:Title:Default:LessThanOrEquals' => '%1$s <= %2$s',
|
||||
'UI:Search:Criteria:Title:Default:Different' => '%1$s ≠ %2$s',
|
||||
'UI:Search:Criteria:Title:Default:Between' => '%1$s entre [%2$s]',
|
||||
'UI:Search:Criteria:Title:Default:BetweenDates:All' => '%1$s : Indifférent',
|
||||
'UI:Search:Criteria:Title:Default:BetweenDates:From' => '%1$s depuis %2$s',
|
||||
'UI:Search:Criteria:Title:Default:BetweenDates:Until' => '%1$s jusqu\'à %2$s',
|
||||
'UI:Search:Criteria:Title:Default:Between:All' => '%1$s : Indifférent',
|
||||
'UI:Search:Criteria:Title:Default:Between:From' => '%1$s à partir de %2$s',
|
||||
'UI:Search:Criteria:Title:Default:Between:Until' => '%1$s jusqu\'à %2$s',
|
||||
'UI:Search:Criteria:Title:Default:BetweenDays' => '%1$s [%2$s]',
|
||||
// - Numeric widget
|
||||
// None yet
|
||||
// - DateTime widget
|
||||
'UI:Search:Criteria:Title:DateTime:Between' => '%2$s <= 1$s <= %3$s',
|
||||
// - Enum widget
|
||||
'UI:Search:Criteria:Title:Enum:In' => '%1$s : %2$s',
|
||||
'UI:Search:Criteria:Title:Enum:In:Many' => '%1$s : %2$s et %3$s autres',
|
||||
'UI:Search:Criteria:Title:Enum:In:All' => '%1$s : Indifférent',
|
||||
// - External key widget
|
||||
'UI:Search:Criteria:Title:ExternalKey:Empty' => '%1$s est renseigné',
|
||||
'UI:Search:Criteria:Title:ExternalKey:NotEmpty' => '%1$s n\'est pas renseigné',
|
||||
'UI:Search:Criteria:Title:ExternalKey:Equals' => '%1$s %2$s',
|
||||
'UI:Search:Criteria:Title:ExternalKey:In' => '%1$s : %2$s',
|
||||
'UI:Search:Criteria:Title:ExternalKey:In:Many' => '%1$s : %2$s et %3$s autres',
|
||||
'UI:Search:Criteria:Title:ExternalKey:In:All' => '%1$s : Indifférent',
|
||||
|
||||
/// - Criteria operators
|
||||
// - Default widget
|
||||
'UI:Search:Criteria:Operator:Default:Empty' => 'Vide',
|
||||
'UI:Search:Criteria:Operator:Default:NotEmpty' => 'Non vide',
|
||||
'UI:Search:Criteria:Operator:Default:Equals' => 'Egal',
|
||||
// - String widget
|
||||
'UI:Search:Criteria:Operator:String:Contains' => 'Contient',
|
||||
'UI:Search:Criteria:Operator:String:StartsWith' => 'Commence par',
|
||||
'UI:Search:Criteria:Operator:String:EndsWith' => 'Fini par',
|
||||
'UI:Search:Criteria:Operator:String:RegExp' => 'Exp. rég.',
|
||||
// - Numeric widget
|
||||
'UI:Search:Criteria:Operator:Numeric:Equals' => 'Egal', // => '=',
|
||||
'UI:Search:Criteria:Operator:Numeric:GreaterThan' => 'Supérieur', // => '>',
|
||||
'UI:Search:Criteria:Operator:Numeric:GreaterThanOrEquals' => 'Sup. / égal', // > '>=',
|
||||
'UI:Search:Criteria:Operator:Numeric:LessThan' => 'Inférieur', // => '<',
|
||||
'UI:Search:Criteria:Operator:Numeric:LessThanOrEquals' => 'Inf. / égal', // > '<=',
|
||||
'UI:Search:Criteria:Operator:Numeric:Different' => 'Différent', // => '≠',
|
||||
|
||||
// - Other translations
|
||||
'UI:Search:Value:Filter:Placeholder' => 'Filtrez...',
|
||||
'UI:Search:Value:Search:Placeholder' => 'Recherchez...',
|
||||
'UI:Search:Value:Autocomplete:StartTyping' => 'Commencez à taper pour voir les valeurs possibles.',
|
||||
'UI:Search:Value:Autocomplete:Wait' => 'Patientez ...',
|
||||
'UI:Search:Value:Autocomplete:NoResult' => 'Aucun résultat.',
|
||||
'UI:Search:Value:Toggler:CheckAllNone' => 'Cocher tout / aucun',
|
||||
'UI:Search:Value:Toggler:CheckAllNoneFiltered' => 'Cocher tout / aucun filtrés',
|
||||
|
||||
// - Widget other translations
|
||||
'UI:Search:Criteria:Numeric:From' => 'Depuis',
|
||||
'UI:Search:Criteria:Numeric:Until' => 'Jusqu\'à',
|
||||
'UI:Search:Criteria:Numeric:PlaceholderFrom' => 'Indifférent',
|
||||
'UI:Search:Criteria:Numeric:PlaceholderUntil' => 'Indifférent',
|
||||
'UI:Search:Criteria:DateTime:From' => 'Depuis',
|
||||
'UI:Search:Criteria:DateTime:FromTime' => 'Depuis',
|
||||
'UI:Search:Criteria:DateTime:Until' => 'jusqu\'à',
|
||||
'UI:Search:Criteria:DateTime:UntilTime' => 'jusqu\'à',
|
||||
'UI:Search:Criteria:DateTime:PlaceholderFrom' => 'Indifférent',
|
||||
'UI:Search:Criteria:DateTime:PlaceholderFromTime' => 'Indifférent',
|
||||
'UI:Search:Criteria:DateTime:PlaceholderUntil' => 'Indifférent',
|
||||
'UI:Search:Criteria:DateTime:PlaceholderUntilTime' => 'Indifférent',
|
||||
|
||||
'UI:Search:Criteria:Raw:Filtered' => 'Filtré',
|
||||
'UI:Search:Criteria:Raw:FilteredOn' => 'Filtré sur %1$s',
|
||||
));
|
||||
|
||||
|
||||
//
|
||||
// Expression to Natural language
|
||||
//
|
||||
Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'Expression:Operator:AND' => ' ET ',
|
||||
'Expression:Operator:OR' => ' OU ',
|
||||
|
||||
'Expression:Unit:Short:DAY' => 'j',
|
||||
'Expression:Unit:Short:WEEK' => 's',
|
||||
'Expression:Unit:Short:MONTH' => 'm',
|
||||
'Expression:Unit:Short:YEAR' => 'a',
|
||||
|
||||
'Expression:Unit:Long:DAY' => 'jour(s)',
|
||||
'Expression:Unit:Long:HOUR' => 'heure(s)',
|
||||
'Expression:Unit:Long:MINUTE' => 'minute(s)',
|
||||
|
||||
'Expression:Verb:NOW' => 'maintenant',
|
||||
));
|
||||
|
||||
@@ -184,6 +184,15 @@ $(function()
|
||||
oParams.real_class = '';
|
||||
oParams.att_code = this.options.att_code;
|
||||
oParams.iInputId = this.id;
|
||||
|
||||
// Gather the already linked target objects
|
||||
oParams.aAlreadyLinked = new Array();
|
||||
$('#'+this.id+' .listResults td input:checkbox').each(function () {
|
||||
iKey = parseInt(this.value, 10); // Numbers are in base 10
|
||||
oParams.aAlreadyLinked.push(iKey);
|
||||
}
|
||||
);
|
||||
|
||||
if (this.options.oWizardHelper)
|
||||
{
|
||||
this.options.oWizardHelper.UpdateWizard();
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
// 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/>
|
||||
|
||||
// JavaScript Document
|
||||
function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizHelper, sExtKeyToRemote)
|
||||
{
|
||||
@@ -101,21 +118,43 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizH
|
||||
operation: 'addObjects',
|
||||
json: me.oWizardHelper.ToJSON()
|
||||
};
|
||||
$.post( GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', theMap,
|
||||
function(data)
|
||||
{
|
||||
$('#dlg_'+me.id).html(data);
|
||||
$('#dlg_'+me.id).dialog('open');
|
||||
me.UpdateSizes(null, null);
|
||||
me.SearchObjectsToAdd();
|
||||
$('#'+me.id+'_indicatorAdd').html('');
|
||||
},
|
||||
'html'
|
||||
);
|
||||
|
||||
// Gather the already linked target objects
|
||||
theMap.aAlreadyLinked = [];
|
||||
$('#linkedset_'+me.id+' .selection:input').each(function (i) {
|
||||
var iRemote = $(this).attr('data-remote-id');
|
||||
theMap.aAlreadyLinked.push(iRemote);
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
"url": GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
|
||||
"method": "POST",
|
||||
"data": theMap,
|
||||
"dataType": "html"
|
||||
})
|
||||
.done(function (data) {
|
||||
$('#dlg_'+me.id).html(data);
|
||||
$('#dlg_'+me.id).dialog('open');
|
||||
me.UpdateSizes(null, null);
|
||||
me.SearchObjectsToAdd();
|
||||
$('#'+me.id+'_indicatorAdd').html('');
|
||||
})
|
||||
;
|
||||
};
|
||||
|
||||
this.SearchObjectsToAdd = function()
|
||||
{
|
||||
$('#count_'+me.id).change(function () {
|
||||
var c = this.value;
|
||||
me.UpdateButtons(c);
|
||||
});
|
||||
FixSearchFormsDisposition();
|
||||
me.UpdateSizes(null, null);
|
||||
|
||||
$("#fs_SearchFormToAdd_"+me.id).trigger('itop.search.form.submit');
|
||||
|
||||
return false; // Don't submit the form, stay in the current page !
|
||||
|
||||
var theMap = { sAttCode: me.sAttCode,
|
||||
iInputId: me.iInputId,
|
||||
sSuffix: me.sSuffix,
|
||||
|
||||
729
js/search/search_form_criteria.js
Normal file
729
js/search/search_form_criteria.js
Normal file
@@ -0,0 +1,729 @@
|
||||
//iTop Search form criteria
|
||||
;
|
||||
$(function()
|
||||
{
|
||||
// the widget definition, where 'itop' is the namespace,
|
||||
// 'search_form_criteria' the widget name
|
||||
$.widget( 'itop.search_form_criteria',
|
||||
{
|
||||
// default options
|
||||
options:
|
||||
{
|
||||
// Default values for the criteria
|
||||
'ref': '',
|
||||
'operator': '=',
|
||||
'values': [],
|
||||
'oql': '',
|
||||
'is_removable': true, // Not used for now. If we come to show locked criterion they will need to have this flag set to false.
|
||||
|
||||
'field': {
|
||||
'label': '',
|
||||
'allowed_values': null,
|
||||
'is_null_allowed': false,
|
||||
},
|
||||
// Available operators. They can be extended or restricted by derivated widgets (see this._initOperators() for more informations)
|
||||
'available_operators': {
|
||||
'=': {
|
||||
'label': Dict.S('UI:Search:Criteria:Operator:Default:Equals'),
|
||||
'code': 'equals',
|
||||
'rank': 10,
|
||||
},
|
||||
'empty': {
|
||||
'label': Dict.S('UI:Search:Criteria:Operator:Default:Empty'),
|
||||
'code': 'empty',
|
||||
'rank': 90,
|
||||
},
|
||||
'not_empty': {
|
||||
'label': Dict.S('UI:Search:Criteria:Operator:Default:NotEmpty'),
|
||||
'code': 'not_empty',
|
||||
'rank': 100,
|
||||
},
|
||||
},
|
||||
|
||||
'init_opened': false,
|
||||
'is_modified': false, // TODO: change this on value change and remove oql property value
|
||||
},
|
||||
|
||||
// Operators
|
||||
operators: null,
|
||||
// Form handler
|
||||
handler: null,
|
||||
// Keys that should not trigger an event in filter/autocomplete inputs
|
||||
filtered_keys: [9, 16, 17, 18, 19, 27, 33, 34, 35, 36, 37, 38, 39, 40], // Tab, Shift, Ctrl, Alt, Pause, Esc, Page Up/Down, Home, End, Left/Up/Right/Down arrows
|
||||
|
||||
// the constructor
|
||||
_create: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
this.element.addClass('search_form_criteria');
|
||||
|
||||
// Init properties (complexe type properties would be static if not initialized with a simple type variable...)
|
||||
this.operators = {};
|
||||
|
||||
// Init operators
|
||||
this._initOperators();
|
||||
|
||||
// Link search form handler
|
||||
this.handler = this.element.closest('.search_form_handler');
|
||||
|
||||
// Bind events
|
||||
this._bindEvents();
|
||||
|
||||
this._prepareElement();
|
||||
},
|
||||
// called when created, and later when changing options
|
||||
_refresh: function()
|
||||
{
|
||||
|
||||
},
|
||||
// events bound via _bind are removed automatically
|
||||
// revert other modifications here
|
||||
_destroy: function()
|
||||
{
|
||||
this.element.removeClass('search_form_criteria');
|
||||
},
|
||||
// _setOptions is called with a hash of all options that are changing
|
||||
// always refresh when changing options
|
||||
_setOptions: function()
|
||||
{
|
||||
this._superApply(arguments);
|
||||
},
|
||||
// _setOption is called for each individual option that is changing
|
||||
_setOption: function( key, value )
|
||||
{
|
||||
this._super( key, value );
|
||||
},
|
||||
|
||||
|
||||
// Protected methods
|
||||
// - Init operators by cleaning up available operators and ordering them.
|
||||
// Note: A null operator or an operator with a rank "false" will be removed.
|
||||
_initOperators: function()
|
||||
{
|
||||
// Reset operators
|
||||
this.operators = {};
|
||||
|
||||
// Cancel empty/not_empty operators if field can't be null
|
||||
if(this.options.field.is_null_allowed === false)
|
||||
{
|
||||
this.options.available_operators.empty = null;
|
||||
this.options.available_operators.not_empty = null;
|
||||
}
|
||||
|
||||
// Temp array to sort operators
|
||||
var aSortable = [];
|
||||
for(var sOpIdx in this.options.available_operators)
|
||||
{
|
||||
var oOp = this.options.available_operators[sOpIdx];
|
||||
|
||||
// Some operator can be disabled by the derivated widget, so we check it.
|
||||
if(oOp !== null && oOp.rank !== false)
|
||||
{
|
||||
aSortable.push([sOpIdx, oOp.rank]);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the array
|
||||
aSortable.sort(function(a, b){
|
||||
return a[1] - b[1];
|
||||
})
|
||||
|
||||
// Populate this.operators
|
||||
for(var iIdx in aSortable)
|
||||
{
|
||||
var sOpIdx = aSortable[iIdx][0];
|
||||
this.operators[sOpIdx] = this.options.available_operators[sOpIdx];
|
||||
}
|
||||
|
||||
// Fallback operator in case the current operator is not available. Should not happen.
|
||||
if(this.operators[this.options.operator] === undefined)
|
||||
{
|
||||
this.options.operator = Object.keys(this.operators)[0];
|
||||
}
|
||||
},
|
||||
// - Bind external events
|
||||
_bindEvents: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
// Get criteria data
|
||||
this.element.on('itop.search.criteria.get_data', function(oEvent, oData){
|
||||
return me._onGetData(oData);
|
||||
});
|
||||
|
||||
// Get/SetCurrentValues callbacks handler
|
||||
this.element.on('itop.search.criteria.get_current_values itop.search.criteria.set_current_values', function(oEvent, oData){
|
||||
oEvent.stopPropagation();
|
||||
|
||||
var callback = me.options[oEvent.type+'_callback'];
|
||||
|
||||
if(typeof callback === 'string')
|
||||
{
|
||||
return me[callback](oEvent, oData);
|
||||
}
|
||||
else if(typeof callback === 'function')
|
||||
{
|
||||
return callback(me, oEvent, oData);
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log('search form criteria: callback type must be a function or a existing function name of the widget');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Close criteria
|
||||
this.element.on('itop.search.criteria.close', function(){
|
||||
return me._close();
|
||||
});
|
||||
},
|
||||
// - Cinematic
|
||||
// - Open / Close criteria
|
||||
_open: function()
|
||||
{
|
||||
// Inform handler that a criteria is opening
|
||||
this.handler.triggerHandler('itop.search.criteria.opening');
|
||||
|
||||
// Open criteria
|
||||
this._resetOperators();
|
||||
// - Open it first
|
||||
this.element.addClass('opened');
|
||||
// - Then only check if more menu is to close to the right side (otherwise we might not have the right element's position)
|
||||
var iFormWidth = this.element.closest('.search_form_handler').outerWidth();
|
||||
var iFormLeftPos = this.element.closest('.search_form_handler').offset().left;
|
||||
var iContentWidth = this.element.find('.sfc_form_group').outerWidth();
|
||||
var iContentLeftPos = this.element.find('.sfc_form_group').offset().left;
|
||||
if( (iContentWidth + iContentLeftPos) > (iFormWidth + iFormLeftPos - 10 /* Security margin */) )
|
||||
{
|
||||
this.element.addClass('opened_left');
|
||||
}
|
||||
|
||||
// Focus on right input
|
||||
var oOpElemRadioChecked = this.element.find('.sfc_fg_operator .sfc_op_radio:checked');
|
||||
var oOpElemInputFirst = oOpElemRadioChecked.closest('.sfc_fg_operator').find('.sfc_op_content input[type="text"]:first');
|
||||
|
||||
oOpElemInputFirst.filter(':not([data-no-auto-focus])').trigger('click').trigger('focus');
|
||||
|
||||
this.element.find('.sfc_form_group').removeClass('advanced');
|
||||
|
||||
if (!oOpElemInputFirst.is(':visible'))
|
||||
{
|
||||
this.element.find('.sfc_form_group').addClass('advanced');
|
||||
}
|
||||
|
||||
},
|
||||
_close: function()
|
||||
{
|
||||
this.element.removeClass('opened_left');
|
||||
this.element.removeClass('opened');
|
||||
this._unmarkAsDraft();
|
||||
},
|
||||
_closeAll: function()
|
||||
{
|
||||
this.element.closest('.search_form_handler').find('.search_form_criteria').each(function(){
|
||||
$(this).triggerHandler('itop.search.criteria.close');
|
||||
});
|
||||
},
|
||||
_remove: function()
|
||||
{
|
||||
this.element.remove();
|
||||
this.handler.triggerHandler('itop.search.criteria.removed');
|
||||
},
|
||||
// - Mark / Unmark criteria as draft (new value not applied)
|
||||
_markAsDraft: function()
|
||||
{
|
||||
this.element.addClass('draft');
|
||||
},
|
||||
_unmarkAsDraft: function()
|
||||
{
|
||||
this.element.removeClass('draft');
|
||||
},
|
||||
// - Apply / Cancel new value
|
||||
_apply: function()
|
||||
{
|
||||
// Find active operator
|
||||
var oActiveOpElem = this.element.find('.sfc_op_radio:checked').closest('.sfc_fg_operator');
|
||||
if(oActiveOpElem.length === 0)
|
||||
{
|
||||
this._trace('Could not apply new value as there seems to be no active operator.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get value from operator (polymorphic method)
|
||||
var sCallback = '_get' + this._toCamelCase(oActiveOpElem.attr('data-operator-code')) + 'OperatorValues';
|
||||
if(this[sCallback] === undefined)
|
||||
{
|
||||
this._trace('Callback ' + sCallback + ' is undefined, using _getOperatorValues instead.');
|
||||
sCallback = '_getOperatorValues';
|
||||
}
|
||||
var aValues = this[sCallback](oActiveOpElem);
|
||||
|
||||
// Update widget
|
||||
this.options.operator = oActiveOpElem.find('.sfc_op_radio').val();
|
||||
|
||||
// TODO: Better modification check. The previous if has been removed as it caused a regression.
|
||||
this.is_modified = true;
|
||||
this.options.oql = '';
|
||||
this.options.values = aValues;
|
||||
this._setTitle();
|
||||
this._unmarkAsDraft();
|
||||
|
||||
|
||||
// Trigger event to handler
|
||||
this.handler.triggerHandler('itop.search.criteria.value_changed');
|
||||
},
|
||||
|
||||
|
||||
// Event callbacks
|
||||
// - Internal events
|
||||
_onButtonApply: function()
|
||||
{
|
||||
this._apply();
|
||||
this._close();
|
||||
},
|
||||
_onButtonCancel: function()
|
||||
{
|
||||
this._close();
|
||||
},
|
||||
_onButtonMore: function()
|
||||
{
|
||||
this.element.find('.sfc_form_group').addClass('advanced');
|
||||
},
|
||||
_onButtonLess: function()
|
||||
{
|
||||
this.element.find('.sfc_form_group').removeClass('advanced');
|
||||
},
|
||||
// - External events
|
||||
_onGetData: function(oData)
|
||||
{
|
||||
var oCriteriaData = {
|
||||
'ref': this.options.ref,
|
||||
'operator': this.options.operator,
|
||||
'values': this.options.values,
|
||||
'is_removable': this.options.is_removable,
|
||||
'oql': this.options.oql,
|
||||
|
||||
// Field data
|
||||
'class': this.options.field.class,
|
||||
'class_alias': this.options.field.class_alias,
|
||||
'code': this.options.field.code,
|
||||
'widget': this.options.field.widget,
|
||||
};
|
||||
return oCriteriaData;
|
||||
},
|
||||
|
||||
|
||||
// DOM element helpers
|
||||
// - Prepare element DOM structure
|
||||
_prepareElement: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
// Prepare base DOM structure
|
||||
this.element
|
||||
.append('<div class="sfc_header"><div class="sfc_title"></div><span class="sfc_toggle"><a class="fa fa-caret-down" href="#"></a></span></div>')
|
||||
.append('<div class="sfc_form_group"><div class="sfc_fg_operators"></div><div class="sfc_fg_buttons"></div></div>');
|
||||
|
||||
// Bind events
|
||||
// Note: No event to handle criteria closing when clicking outside of it as it is already handle by the form handler.
|
||||
// - Toggler
|
||||
this.element.find('.sfc_toggle, .sfc_title').on('click', function(oEvent){
|
||||
// Prevent anchor
|
||||
oEvent.preventDefault();
|
||||
|
||||
// First memorize if current criteria is close
|
||||
var bOpen = !me.element.hasClass('opened');
|
||||
// Then close every criterion
|
||||
me._closeAll();
|
||||
// Finally open current criteria if necessary
|
||||
if(bOpen === true)
|
||||
{
|
||||
me._open();
|
||||
}
|
||||
});
|
||||
this.element.on('keydown', function(oEvent){
|
||||
// Apply if "enter" key
|
||||
if(oEvent.key === 'Enter')
|
||||
{
|
||||
me._apply();
|
||||
|
||||
// Keep criteria open only on Ctrl + Enter.
|
||||
if(oEvent.ctrlKey === false)
|
||||
{
|
||||
me._close();
|
||||
}
|
||||
}
|
||||
// Close if "escape" key
|
||||
else if(oEvent.key === 'Escape')
|
||||
{
|
||||
me._close();
|
||||
}
|
||||
});
|
||||
|
||||
// Removable / locked decoration
|
||||
if(this.options.is_removable === true)
|
||||
{
|
||||
this.element.find('.sfc_header').append('<span class="sfc_close"><a class="fa fa-times" href="#"></a></span>');
|
||||
this.element.find('.sfc_close').on('click', function(oEvent){
|
||||
// Prevent anchor
|
||||
oEvent.preventDefault();
|
||||
|
||||
me._remove();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
this.element.addClass('locked');
|
||||
this.element.find('.sfc_header').append('<span class="sfc_locked"><span class="fa fa-lock"></span></span>');
|
||||
}
|
||||
|
||||
// Form group
|
||||
this._prepareOperators();
|
||||
this._prepareButtons();
|
||||
|
||||
// Fill criteria
|
||||
// - Title
|
||||
this._setTitle();
|
||||
|
||||
// Init opened to improve UX (toggle & focus in main operator's input)
|
||||
if(this.options.init_opened === true)
|
||||
{
|
||||
this._closeAll();
|
||||
this._open();
|
||||
}
|
||||
},
|
||||
// - Prepare the available operators for the criteria
|
||||
// Meant for overloading.
|
||||
_prepareOperators: function()
|
||||
{
|
||||
for(var sOpIdx in this.operators)
|
||||
{
|
||||
var oOp = this.operators[sOpIdx];
|
||||
var sMethod = '_prepare' + this._toCamelCase(oOp.code) + 'Operator';
|
||||
|
||||
// Create DOM element from template
|
||||
var oOpElem = $(this._getOperatorTemplate())
|
||||
.uniqueId()
|
||||
.appendTo(this.element.find('.sfc_fg_operators'));
|
||||
|
||||
// Prepare operator's base elements
|
||||
this._prepareOperator(oOpElem, sOpIdx, oOp);
|
||||
|
||||
// Prepare operator's specific elements
|
||||
if(this[sMethod] !== undefined)
|
||||
{
|
||||
this[sMethod](oOpElem, sOpIdx, oOp);
|
||||
}
|
||||
else
|
||||
{
|
||||
this._prepareDefaultOperator(oOpElem, sOpIdx, oOp);
|
||||
}
|
||||
}
|
||||
},
|
||||
// - Prepare the buttons (DOM and events) for a criteria
|
||||
_prepareButtons: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
// DOM elements
|
||||
this.element.find('.sfc_fg_buttons')
|
||||
.append('<button type="button" name="apply" class="sfc_fg_button sfc_fg_apply">' + Dict.S('UI:Button:Apply') + '</button>')
|
||||
.append('<button type="button" name="cancel" class="sfc_fg_button sfc_fg_cancel">' + Dict.S('UI:Button:Close') + '</button>')
|
||||
.append('<button type="button" name="more" class="sfc_fg_button sfc_fg_more">' + Dict.S('UI:Button:More') + '<span class="fa fa-angle-double-down"></span></button>')
|
||||
.append('<button type="button" name="less" class="sfc_fg_button sfc_fg_less">' + Dict.S('UI:Button:Less') + '<span class="fa fa-angle-double-up"></span></button>');
|
||||
|
||||
// Events
|
||||
this.element.find('.sfc_fg_button').on('click', function(oEvent){
|
||||
oEvent.preventDefault();
|
||||
oEvent.stopPropagation();
|
||||
|
||||
var sCallback = '_onButton' + me._toCamelCase($(this).attr('name'));
|
||||
me[sCallback]();
|
||||
});
|
||||
},
|
||||
// - Reset all operators but active one
|
||||
_resetOperators: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
// Reset all operators
|
||||
this.element.find('.sfc_fg_operator').each(function(){
|
||||
var sCallback = '_reset' + me._toCamelCase($(this).attr('data-operator-code')) + 'Operator';
|
||||
if(me[sCallback] === undefined)
|
||||
{
|
||||
sCallback = '_resetOperator';
|
||||
}
|
||||
me[sCallback]($(this));
|
||||
});
|
||||
|
||||
// Set value on current operator
|
||||
var sCurrentOpCode = this.operators[this.options.operator].code;
|
||||
this.element.find('.sfc_fg_operator[data-operator-code="' + sCurrentOpCode + '"]').each(function(){
|
||||
// Check radio (we don't use .trigger('click'), otherwise the criteria will be seen as draft.
|
||||
$(this).find('.sfc_op_radio').prop('checked', true);
|
||||
|
||||
// Reset values
|
||||
var sCallback = '_set' + me._toCamelCase(sCurrentOpCode) + 'OperatorValues';
|
||||
if(me[sCallback] === undefined)
|
||||
{
|
||||
sCallback = '_setOperatorValues';
|
||||
}
|
||||
me[sCallback]($(this), me.options.values);
|
||||
});
|
||||
},
|
||||
// - Set the title element
|
||||
_setTitle: function(sTitle)
|
||||
{
|
||||
if(sTitle === undefined)
|
||||
{
|
||||
var sOperator = this.operators[this.options.operator].code;
|
||||
var sDictEntry = 'UI:Search:Criteria:Title:' + this._toCamelCase(this.options.field.widget) + ':' + this._toCamelCase(sOperator);
|
||||
|
||||
// Fallback to default widget dict entry if none exists for the current widget
|
||||
if(Dict.S(sDictEntry) === sDictEntry)
|
||||
{
|
||||
sDictEntry = 'UI:Search:Criteria:Title:Default:' + this._toCamelCase(sOperator);
|
||||
}
|
||||
|
||||
sTitle = Dict.Format(sDictEntry, this.options.field.label, this._getValuesAsText());
|
||||
|
||||
// Last chande fallback
|
||||
if(sTitle === sDictEntry)
|
||||
{
|
||||
sTitle = this.options.label;
|
||||
}
|
||||
}
|
||||
this.element.find('.sfc_title')
|
||||
.html(sTitle)
|
||||
.attr('title', sTitle);
|
||||
},
|
||||
|
||||
// Operators helpers
|
||||
// - Return a HTML template for operators
|
||||
_getOperatorTemplate: function()
|
||||
{
|
||||
return '<div class="sfc_fg_operator"><label><input type="radio" class="sfc_op_radio" name="operator" /><span class="sfc_op_name"></span><span class="sfc_op_content"></span></label></div>';
|
||||
},
|
||||
// Prepare operator's DOM element
|
||||
// - Base preparation, always called
|
||||
_prepareOperator: function(oOpElem, sOpIdx, oOp)
|
||||
{
|
||||
var me = this;
|
||||
var sInputId = oOp.code + '_' + oOpElem.attr('id');
|
||||
|
||||
// Set radio
|
||||
oOpElem.find('.sfc_op_radio').val(sOpIdx);
|
||||
oOpElem.find('.sfc_op_radio').attr('id', sInputId);
|
||||
|
||||
// Set label
|
||||
oOpElem.find('.sfc_op_name').text(oOp.label);
|
||||
oOpElem.find('> label').attr('for', sInputId);
|
||||
|
||||
// Set helper classes
|
||||
oOpElem.addClass('sfc_fg_operator_' + oOp.code)
|
||||
.attr('data-operator-code', oOp.code);
|
||||
|
||||
// Bind events
|
||||
// - Check radio button on click and mark criteria as draft
|
||||
oOpElem.on('click focusin', function(){
|
||||
var bIsChecked = oOpElem.find('.sfc_op_radio').prop('checked');
|
||||
|
||||
if(bIsChecked === false)
|
||||
{
|
||||
oOpElem.find('.sfc_op_radio').prop('checked', true);
|
||||
me._markAsDraft();
|
||||
}
|
||||
});
|
||||
},
|
||||
// - Fallback for operator that has no dedicated callback
|
||||
_prepareDefaultOperator: function(oOpElem, sOpIdx, oOp)
|
||||
{
|
||||
var me = this;
|
||||
|
||||
// DOM element
|
||||
var oOpContentElem = $('<input type="text" />');
|
||||
oOpContentElem.val(this._getValuesAsText());
|
||||
|
||||
// Events
|
||||
// - Focus input on click (radio, label, ...)
|
||||
oOpElem.on('click', ':not(input[type="text"], select)', function(oEvent) {
|
||||
// Stopping propagation like this instead of oEvent.stopPropagation() as the event could be used by something.
|
||||
if ($(oEvent.target).is('input[type="text"], select')) {
|
||||
return;
|
||||
}
|
||||
oOpContentElem.focus();
|
||||
});
|
||||
// - Mark as draft on key typing
|
||||
oOpContentElem.on('keydown', function(oEvent){
|
||||
me._markAsDraft();
|
||||
});
|
||||
|
||||
oOpElem.find('.sfc_op_content').append(oOpContentElem);
|
||||
},
|
||||
_prepareEmptyOperator: function(oOpElem, sOpIdx, oOp)
|
||||
{
|
||||
// Do nothing as only the label is necessary
|
||||
},
|
||||
_prepareNotEmptyOperator: function(oOpElem, sOpIdx, oOp)
|
||||
{
|
||||
// Do nothing as only the label is necessary
|
||||
},
|
||||
// Reset operator's state
|
||||
// - Fallback for operator that has no dedicated callback
|
||||
_resetOperator: function(oOpElem)
|
||||
{
|
||||
oOpElem.find('.sfc_op_content input').val('');
|
||||
},
|
||||
// Get operator's values
|
||||
// - Fallback for operators without a specific callback
|
||||
_getOperatorValues: function(oOpElem)
|
||||
{
|
||||
var aValues = [];
|
||||
|
||||
oOpElem.find('.sfc_op_content input').each(function(){
|
||||
var sValue = $(this).val();
|
||||
aValues.push({value: sValue, label: sValue});
|
||||
});
|
||||
|
||||
return aValues;
|
||||
},
|
||||
// Set operator's values
|
||||
// - Fallback for operators without a specific callback
|
||||
_setOperatorValues: function(oOpElem, aValues)
|
||||
{
|
||||
if(aValues.length === 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
oOpElem.find('.sfc_op_content input').each(function(){
|
||||
$(this).val(aValues[0].value);
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
|
||||
// Values helpers
|
||||
// - Check if criteria has allowed values either preloaded or through autocomplete
|
||||
_hasAllowedValues: function()
|
||||
{
|
||||
return ( (this.options.field.allowed_values !== undefined) && (this.options.field.allowed_values !== null) );
|
||||
},
|
||||
// - Check if criteria has preloaded allowed values (as opposed to autocomplete)
|
||||
_hasPreloadedAllowedValues: function()
|
||||
{
|
||||
if(this._hasAllowedValues() && (this.options.field.allowed_values.values !== undefined) && (this.options.field.allowed_values.values !== null))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
// - Return the preloaded allowed values (not coming from autocomplete)
|
||||
_getPreloadedAllowedValues: function()
|
||||
{
|
||||
return (this._hasPreloadedAllowedValues()) ? this.options.field.allowed_values.values : {};
|
||||
},
|
||||
// - Check if criteria has allowed values that should be loaded through autocomplete
|
||||
_hasAutocompleteAllowedValues: function()
|
||||
{
|
||||
if(this._hasAllowedValues() && (this.options.field.allowed_values.autocomplete === true) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
// - Return the allowed values from the autocomplete
|
||||
_getAutocompleteAllowedValues: function()
|
||||
{
|
||||
// Meant for overloading.
|
||||
},
|
||||
// - Return current values
|
||||
_getValues: function()
|
||||
{
|
||||
return this.options.values;
|
||||
},
|
||||
// - Convert values to a standard string
|
||||
_getValuesAsText: function(aRawValues)
|
||||
{
|
||||
if (aRawValues == undefined)
|
||||
{
|
||||
aRawValues = this._getValues();
|
||||
}
|
||||
|
||||
|
||||
var aValues = [];
|
||||
for(var iValueIdx in aRawValues)
|
||||
{
|
||||
aValues.push(aRawValues[iValueIdx].label);
|
||||
}
|
||||
|
||||
return aValues.join(', ');
|
||||
},
|
||||
// - Make an OQL expression from the criteria values and operator
|
||||
_makeOQLExpression: function()
|
||||
{
|
||||
var aValues = [];
|
||||
var sOQL = '';
|
||||
|
||||
for(var iValueIdx in this.options.values)
|
||||
{
|
||||
aValues.push( '\'' + this.options.values[iValueIdx].value + '\'' );
|
||||
}
|
||||
sOQL += '(`' + this.options.ref + '`) ' + this.options.operator + ' ' + aValues.join(', ') + ')';
|
||||
|
||||
return sOQL;
|
||||
},
|
||||
|
||||
|
||||
// Global helpers
|
||||
// - Converts a snake_case string to CamelCase
|
||||
_toCamelCase: function(sString)
|
||||
{
|
||||
if( (sString === undefined) || (sString === null) )
|
||||
{
|
||||
return sString;
|
||||
}
|
||||
|
||||
var aParts = sString.split('_');
|
||||
|
||||
for(var i in aParts)
|
||||
{
|
||||
aParts[i] = aParts[i].charAt(0).toUpperCase() + aParts[i].substr(1);
|
||||
}
|
||||
|
||||
return aParts.join('');
|
||||
},
|
||||
// - Return if the given keycode is among filtered
|
||||
_isFilteredKey: function(iKeyCode)
|
||||
{
|
||||
return (this.filtered_keys.indexOf(iKeyCode) >= 0);
|
||||
},
|
||||
|
||||
|
||||
// Debug helpers
|
||||
// - Show a trace in the javascript console
|
||||
_trace: function(sMessage, oData)
|
||||
{
|
||||
if(window.console)
|
||||
{
|
||||
if(oData !== undefined)
|
||||
{
|
||||
console.log('Search form criteria: ' + sMessage, oData);
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log('Search form criteria: ' + sMessage);
|
||||
}
|
||||
}
|
||||
},
|
||||
// - Show current options
|
||||
showOptions: function()
|
||||
{
|
||||
this._trace('Options', this.options);
|
||||
}
|
||||
});
|
||||
});
|
||||
77
js/search/search_form_criteria_date.js
Normal file
77
js/search/search_form_criteria_date.js
Normal file
@@ -0,0 +1,77 @@
|
||||
//iTop Search form criteria date
|
||||
;
|
||||
$(function()
|
||||
{
|
||||
// the widget definition, where 'itop' is the namespace,
|
||||
// 'search_form_criteria_date' the widget name
|
||||
$.widget( 'itop.search_form_criteria_date', $.itop.search_form_criteria_date_abstract,
|
||||
{
|
||||
// default options
|
||||
options:
|
||||
{
|
||||
// // Overload default operator
|
||||
// 'operator': 'between_dates',
|
||||
// // Available operators
|
||||
// 'available_operators': {
|
||||
//
|
||||
// },
|
||||
aInputsParam: [
|
||||
{
|
||||
"code": "from",
|
||||
"code_uc_first":"From",
|
||||
"x_picker" : 'datepicker',
|
||||
"value_index": 0,
|
||||
"onclose_show" : "until",
|
||||
},
|
||||
{
|
||||
"code": "until",
|
||||
"code_uc_first":"Until",
|
||||
"x_picker" : 'datepicker',
|
||||
"value_index": 1,
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
// the constructor
|
||||
_create: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
this._super();
|
||||
this.element.addClass('search_form_criteria_date');
|
||||
},
|
||||
// called when created, and later when changing options
|
||||
_refresh: function()
|
||||
{
|
||||
|
||||
},
|
||||
// events bound via _bind are removed automatically
|
||||
// revert other modifications here
|
||||
_destroy: function()
|
||||
{
|
||||
this.element.removeClass('search_form_criteria_date');
|
||||
this._super();
|
||||
},
|
||||
// _setOptions is called with a hash of all options that are changing
|
||||
// always refresh when changing options
|
||||
_setOptions: function()
|
||||
{
|
||||
this._superApply(arguments);
|
||||
},
|
||||
// _setOption is called for each individual option that is changing
|
||||
_setOption: function( key, value )
|
||||
{
|
||||
this._super( key, value );
|
||||
},
|
||||
|
||||
|
||||
//------------------
|
||||
// Inherited methods
|
||||
//------------------
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
436
js/search/search_form_criteria_date_abstract.js
Normal file
436
js/search/search_form_criteria_date_abstract.js
Normal file
@@ -0,0 +1,436 @@
|
||||
//iTop Search form criteria date_abstract
|
||||
;
|
||||
$(function()
|
||||
{
|
||||
// the widget definition, where 'itop' is the namespace,
|
||||
// 'search_form_criteria_date_abstract' the widget name
|
||||
$.widget( 'itop.search_form_criteria_date_abstract', $.itop.search_form_criteria,
|
||||
{
|
||||
// default options
|
||||
options:
|
||||
{
|
||||
// Overload default operator
|
||||
'operator': 'between_dates',
|
||||
// Available operators
|
||||
'available_operators': {
|
||||
'between_dates': {
|
||||
'label': Dict.S('UI:Search:Criteria:Operator:Default:Between'),
|
||||
'code': 'between_days',
|
||||
'rank': 1,
|
||||
},
|
||||
'empty': {
|
||||
'rank': 700,//pre-existing, reordered
|
||||
},
|
||||
'not_empty': {
|
||||
'rank': 800,//pre-existing, reordered
|
||||
},
|
||||
'=': null
|
||||
},
|
||||
aInputsParam: [
|
||||
{
|
||||
// Common settings :
|
||||
// "code": "from", => the code used in the HTML
|
||||
// "code_uc_first":"From", => the code used in the translations
|
||||
// "onclose_show" : "until", => on x_picker close, should we open another one (on "from" close shall we show "until")
|
||||
// "value_index": 0, => the widget communicate with an array of values, the index 0 is "from" the index 1 is "until"
|
||||
|
||||
// Date_time widget specifi settings :
|
||||
// "x_picker" : 'datetimepicker', => the plugin used either datepicker or datetimepicker
|
||||
// "default_time_add": false, => either false to disable it or number of second to add (used by the datetimepicker to choose the right time on synched datepicker change, its value change from 0 for "from" to +1d-1s for "until"
|
||||
// "show_on_advanced": true, => is the input displaye on "more" or "less" mode advanced is an lais for "more" in the css
|
||||
// "synced_with": "from_time", => from and until has both two input (datepicker and datetimepicker). each time one input change, the other one has to change
|
||||
// "getter_code":"from_time" => iTop expect datetime to always provide time, so we must always use the datetimepicker in order to compute the value to be sent to iTop event if whe are in the display mode "less" with datepicker visible
|
||||
// "getter_suffix": " 00:00:00", => iTop expect datetime to always provide time, so either we use the "getter_code" or we add a suffix to force the time
|
||||
// "title_getter_code":"from", => if present, the title will be computed base on the given input code. Because the datetime widget title area is not large enought, so we remove the time info, in order to do so we use this.
|
||||
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Prepare operator's DOM element
|
||||
_prepareBetweenDaysOperator: function(oOpElem, sOpIdx, oOp)
|
||||
{
|
||||
var me = this;
|
||||
var aInputsParam = me.options.aInputsParam;
|
||||
var aInputsParamLength = aInputsParam.length;
|
||||
var aValues = me._getValues();//TODO : tenir compte du refactoring de la structure
|
||||
var oInputsParamIndexByCode = {};
|
||||
for (var i = 0; i < aInputsParamLength; i++) {
|
||||
oInputsParamIndexByCode[aInputsParam[i].code] = i;
|
||||
}
|
||||
|
||||
oContentElem = $(); //will be populated on each loop
|
||||
|
||||
for (var i = 0; i < aInputsParamLength; i++) {
|
||||
var oInputParam = aInputsParam[i];
|
||||
var oOpContentElem = $('<span class="sfc_op_content_'+oInputParam.code+'_outer '+(oInputParam.show_on_advanced ? 'hide_on_less' : 'hide_on_advanced')+'"><label class="sfc_op_content_'+oInputParam.code+'_label" for=""> '+Dict.S('UI:Search:Criteria:DateTime:'+oInputParam.code_uc_first)+' </label><input type="text" name="'+oInputParam.code+'" placeholder="'+Dict.S('UI:Search:Criteria:DateTime:Placeholder'+oInputParam.code_uc_first)+'"/></span>');
|
||||
var oInputElem = oOpContentElem
|
||||
.find('input')
|
||||
.uniqueId()
|
||||
//.attr('data-no-auto-focus', true)
|
||||
;
|
||||
oOpContentElem
|
||||
.find('label')
|
||||
.attr('for', oInputElem.attr('id'))
|
||||
;
|
||||
|
||||
if (oInputParam.value_index in aValues && typeof aValues[oInputParam.value_index].value != 'undefined')
|
||||
{
|
||||
oInputElem.val(aValues[oInputParam.value_index].value);
|
||||
}
|
||||
|
||||
oContentElem = oContentElem.add(oOpContentElem);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// - Apply on "enter" key hit
|
||||
//todo: this could be refactored
|
||||
oOpContentElem.on('keyup', function(oEvent){
|
||||
// Check operator's radio if not already (typically when focusing in input via "tab" key)
|
||||
if(oOpElem.find('.sfc_op_radio').prop('checked') === false)
|
||||
{
|
||||
oOpElem.find('.sfc_op_radio').prop('checked', true)
|
||||
}
|
||||
|
||||
me._markAsDraft();
|
||||
|
||||
// Apply if enter key
|
||||
if(oEvent.key === 'Enter')
|
||||
{
|
||||
me.val(me.val().trim());
|
||||
me._apply();
|
||||
}
|
||||
});
|
||||
|
||||
oOpElem
|
||||
.find('.sfc_op_name')
|
||||
.remove()
|
||||
.end()
|
||||
.find('.sfc_op_content')
|
||||
.append(oContentElem)
|
||||
;
|
||||
|
||||
|
||||
// once the inputs are appended into the DOM we can safely use jQuery UI
|
||||
var fHandleSynchCallback = function(select, bSetDate) {
|
||||
var selectElem = $(select);
|
||||
var sSyncedWith = selectElem.data('synced_with');
|
||||
var oInputParam = selectElem.data('oInputParam');
|
||||
|
||||
if (bSetDate)
|
||||
{
|
||||
var sDate = selectElem.val().trim();
|
||||
if ('' == sDate)
|
||||
{
|
||||
selectElem[oInputParam.x_picker]('setDate', null);
|
||||
} else
|
||||
{
|
||||
selectElem[oInputParam.x_picker]('setDate', sDate);
|
||||
}
|
||||
}
|
||||
|
||||
if (sSyncedWith != undefined)
|
||||
{
|
||||
var sCode = selectElem.data('code');
|
||||
var oInputParam = aInputsParam[oInputsParamIndexByCode[sCode]];
|
||||
var oSyncedInputParam = aInputsParam[oInputsParamIndexByCode[sSyncedWith]];
|
||||
var oSyncedInputElem = oOpElem.find('input[name="'+sSyncedWith+'"]');
|
||||
|
||||
var dSyncedDate = selectElem[oInputParam.x_picker]('getDate');
|
||||
|
||||
if (null == dSyncedDate)
|
||||
{
|
||||
// oSyncedInputElem.val('');
|
||||
oSyncedInputElem[oSyncedInputParam.x_picker]('setDate', null);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (typeof oSyncedInputParam.default_time_add != 'undefined' && oSyncedInputParam.default_time_add)
|
||||
{
|
||||
dSyncedDate.setSeconds(dSyncedDate.getSeconds() + oSyncedInputParam.default_time_add);
|
||||
}
|
||||
oSyncedInputElem[oSyncedInputParam.x_picker]('setDate', dSyncedDate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
var odatetimepickerOptionsDefault = {
|
||||
dateFormat: 'yy-mm-dd',
|
||||
timeFormat: 'HH:mm:ss',
|
||||
buttonImage: GetAbsoluteUrlAppRoot()+"/images/calendar.png",
|
||||
// buttonImageOnly: true,
|
||||
buttonText: "",
|
||||
showOn:'button',
|
||||
changeMonth:true,
|
||||
changeYear:true
|
||||
};
|
||||
for (var i = 0; i < aInputsParamLength; i++) {
|
||||
var oInputParam = aInputsParam[i];
|
||||
|
||||
|
||||
|
||||
var odatetimepickerOptions = $.extend({}, odatetimepickerOptionsDefault, me.options.datepicker, {
|
||||
onSelect: function() {
|
||||
fHandleSynchCallback(this, false);
|
||||
$(this).focus();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
var oInputElem = oOpElem.find('input[name="'+oInputParam.code+'"]');
|
||||
oInputElem.data('code', oInputParam.code);
|
||||
oInputElem.data('onclose_show', oInputParam.onclose_show);
|
||||
oInputElem.data('oInputParam', oInputParam);
|
||||
oInputElem.on('keydown', function(oEvent) {
|
||||
// Synch if "enter" key
|
||||
if(oEvent.key === 'Enter')
|
||||
{
|
||||
fHandleSynchCallback(this, true);
|
||||
}
|
||||
});
|
||||
oInputElem[oInputParam.x_picker](odatetimepickerOptions);
|
||||
|
||||
if (typeof aInputsParam[oInputsParamIndexByCode[oInputParam.synced_with]] != 'undefined')
|
||||
{
|
||||
var oSyncedInputParam = aInputsParam[oInputsParamIndexByCode[oInputParam.synced_with]];
|
||||
oInputElem.data('synced_with', oSyncedInputParam.code);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
_getBetweenDaysOperatorValues: function(oOpElem)
|
||||
{
|
||||
var me = this;
|
||||
var aValues = [];
|
||||
var aInputsParam = me.options.aInputsParam;
|
||||
var aInputsParamLength = aInputsParam.length;
|
||||
var bAdvancedMode = (me.element.find('.sfc_form_group.advanced').length > 0);
|
||||
|
||||
var sValue = '';
|
||||
var sLabel = '';
|
||||
|
||||
for (var i = 0; i < aInputsParamLength; i++) {
|
||||
var oInputParam = aInputsParam[i];
|
||||
|
||||
sLabel = oOpElem.find('input[name="'+oInputParam.code+'"]').val();
|
||||
|
||||
if (typeof oInputParam.show_on_advanced == 'undefined' || bAdvancedMode == oInputParam.show_on_advanced)
|
||||
{
|
||||
if (typeof oInputParam.getter_code != 'undefined')
|
||||
{
|
||||
sValue = oOpElem.find('input[name="'+oInputParam.getter_code+'"]').val();
|
||||
}
|
||||
else if (sLabel != "" && typeof oInputParam.getter_suffix != 'undefined')
|
||||
{
|
||||
sValue = sLabel + oInputParam.getter_suffix;
|
||||
}
|
||||
else
|
||||
{
|
||||
sValue = sLabel;
|
||||
}
|
||||
|
||||
aValues[oInputParam.value_index] = {value: sValue, label: sLabel};
|
||||
}
|
||||
}
|
||||
|
||||
return aValues;
|
||||
},
|
||||
|
||||
_setBetweenDaysOperatorValues: function(oOpElem, aValues)
|
||||
{
|
||||
|
||||
var me = this;
|
||||
var aInputsParam = me.options.aInputsParam;
|
||||
var aInputsParamLength = aInputsParam.length;
|
||||
for (var i = 0; i < aInputsParamLength; i++) {
|
||||
var oInputParam = aInputsParam[i];
|
||||
var oInputElem = oOpElem.find('input[name="'+oInputParam.code+'"]');
|
||||
// oInputElem.val(aValues[0].value);
|
||||
|
||||
if (typeof aValues[oInputParam.value_index] != 'undefined' && typeof aValues[oInputParam.value_index].value != 'undefined')
|
||||
{
|
||||
var sDate = aValues[oInputParam.value_index].value;
|
||||
if (sDate.trim() != '')
|
||||
{
|
||||
var oDate = new Date(sDate);
|
||||
oInputElem[oInputParam.x_picker]('setDate', oDate);
|
||||
}
|
||||
else
|
||||
{
|
||||
oInputElem[oInputParam.x_picker]('setDate', sDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_resetBetweenDaysOperator: function(oOpElem)
|
||||
{
|
||||
this._resetOperator(oOpElem);
|
||||
},
|
||||
|
||||
//------------------
|
||||
// Inherited methods
|
||||
//------------------
|
||||
|
||||
_setTitle: function(sTitle)
|
||||
{
|
||||
var me = this;
|
||||
if (sTitle === undefined && me.options.operator == 'between_dates')
|
||||
{
|
||||
var aValues = me._getValues();
|
||||
switch (true)
|
||||
{
|
||||
case (typeof aValues[0] == 'undefined' && typeof aValues[1] == 'undefined'):
|
||||
case (typeof aValues[0].label == 'undefined' && typeof aValues[1].label == 'undefined'):
|
||||
case (aValues[0].label.trim() == '' && aValues[1].label.trim() == ''):
|
||||
var sDictEntrySuffix = ':All';
|
||||
break;
|
||||
case (typeof aValues[0] == 'undefined' ):
|
||||
case (typeof aValues[0].label == 'undefined' ):
|
||||
case (aValues[0].label.trim() == '' ):
|
||||
var sDictEntrySuffix = ':Until';
|
||||
break;
|
||||
case (typeof aValues[1] == 'undefined'):
|
||||
case (typeof aValues[1].label == 'undefined' ):
|
||||
case (aValues[1].label.trim() == ''):
|
||||
var sDictEntrySuffix = ':From';
|
||||
break;
|
||||
default:
|
||||
var sDictEntrySuffix = undefined;
|
||||
break;
|
||||
}
|
||||
|
||||
if (sDictEntrySuffix != undefined)
|
||||
{
|
||||
var sDictEntry = 'UI:Search:Criteria:Title:' + this._toCamelCase(this.options.field.widget) + ':' + this._toCamelCase(me.options.operator) + sDictEntrySuffix ;
|
||||
// Fallback to default widget dict entry if none exists for the current widget
|
||||
if(Dict.S(sDictEntry) === sDictEntry)
|
||||
{
|
||||
sDictEntry = 'UI:Search:Criteria:Title:Default:' + this._toCamelCase(me.options.operator) + sDictEntrySuffix;
|
||||
}
|
||||
|
||||
sTitle = Dict.Format(sDictEntry, this.options.field.label, this._getValuesAsText());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return me._super(sTitle);
|
||||
},
|
||||
|
||||
|
||||
// - Convert values to a standard string
|
||||
_getValuesAsText: function(aRawValues)
|
||||
{
|
||||
var me = this;
|
||||
|
||||
if (aRawValues == undefined)
|
||||
{
|
||||
aRawValues = me._getValues();
|
||||
}
|
||||
if (me.options.operator == 'between_dates')
|
||||
{
|
||||
aRawValues = aRawValues.slice();//clone
|
||||
|
||||
if (typeof aRawValues[1] == 'undefined' || typeof aRawValues[1].label == 'undefined' || aRawValues[1].label == '')
|
||||
{
|
||||
aRawValues.splice(1, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
aRawValues[1].label = aRawValues[1].label.replace(/(\s\d{2}:\d{2}:\d{2})/, '');
|
||||
}
|
||||
if (typeof aRawValues[0] == 'undefined' || typeof aRawValues[0].label == 'undefined' || aRawValues[0].label == '')
|
||||
{
|
||||
aRawValues.splice(0, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
aRawValues[0].label = aRawValues[0].label.replace(/(\s\d{2}:\d{2}:\d{2})/, '');
|
||||
}
|
||||
}
|
||||
return me._super(aRawValues);
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
// Prepare operator's DOM element
|
||||
// - Base preparation, always called
|
||||
_prepareOperator: function(oOpElem, sOpIdx, oOp)
|
||||
{
|
||||
var me = this;
|
||||
if (typeof oOp.dropdown_group == 'undefined')
|
||||
{
|
||||
return this._super(oOpElem, sOpIdx, oOp);
|
||||
}
|
||||
|
||||
this._super(oOpElem, sOpIdx, oOp);
|
||||
oOpElem.addClass('force_hide')
|
||||
|
||||
//TODO: move this into the abstract widget
|
||||
|
||||
// DOM element
|
||||
oDropdownElem = this.element.find('select.dropdown_group_'+oOp.dropdown_group);
|
||||
if (oDropdownElem.length == 0)
|
||||
{
|
||||
oDropdownElem = $('<select class="dropdown_group_'+oOp.dropdown_group+'" data-dropdown-group="'+oOp.dropdown_group+'"></select>');
|
||||
|
||||
oDropdownElem.on('change', function(){
|
||||
// $option = $(this);
|
||||
me.element.find('.sfc_fg_operator_dropdown_group .sfc_op_radio').val(oDropdownElem.val());
|
||||
|
||||
oOptionOp = oDropdownElem.data('oOp');
|
||||
oDropdownElem.attr('data-operator-code', oOptionOp.code);
|
||||
});
|
||||
|
||||
|
||||
// Create DOM element from template
|
||||
var oOpElemDropdown = $(this._getOperatorTemplate()).uniqueId();
|
||||
|
||||
//todo : if this code is keeped, the radio mustr have an id and the label need to point to it
|
||||
oOpElemDropdown
|
||||
.addClass('sfc_fg_operator_dropdown_group')
|
||||
.attr('data-operator-code', 'dropdown_group')
|
||||
.find('.sfc_op_name')
|
||||
.append(oDropdownElem)
|
||||
.end()
|
||||
.find('.sfc_op_radio')
|
||||
.val(sOpIdx)
|
||||
.end()
|
||||
.on('click', function(oEvent){
|
||||
var bIsChecked = oOpElemDropdown.find('.sfc_op_radio').prop('checked');
|
||||
|
||||
if(bIsChecked === false)
|
||||
{
|
||||
oOpElemDropdown.find('.sfc_op_radio').prop('checked', true);
|
||||
me._markAsDraft();
|
||||
}
|
||||
oOpElemDropdown.find('input[type="text"]:first').focus();
|
||||
})
|
||||
.appendTo(this.element.find('.sfc_fg_operators'))
|
||||
;
|
||||
|
||||
this._prepareDefaultOperator(oOpElemDropdown, sOpIdx, oOp);
|
||||
}
|
||||
|
||||
oDropdownElem
|
||||
.append('<option value="'+sOpIdx+'" >'+oOp.label+'</option>')
|
||||
.data('oOp', oOp)
|
||||
;
|
||||
},
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
125
js/search/search_form_criteria_date_time.js
Normal file
125
js/search/search_form_criteria_date_time.js
Normal file
@@ -0,0 +1,125 @@
|
||||
//iTop Search form criteria date_time
|
||||
;
|
||||
$(function()
|
||||
{
|
||||
// the widget definition, where 'itop' is the namespace,
|
||||
// 'search_form_criteria_date_time' the widget name
|
||||
$.widget( 'itop.search_form_criteria_date_time', $.itop.search_form_criteria_date_abstract,
|
||||
{
|
||||
// default options
|
||||
options:
|
||||
{
|
||||
// // Overload default operator
|
||||
// 'operator': 'between_dates',
|
||||
// // Available operators
|
||||
// 'available_operators': {
|
||||
//
|
||||
// },
|
||||
aInputsParam: [
|
||||
{
|
||||
"code": "from",
|
||||
"code_uc_first":"From",
|
||||
"x_picker" : 'datepicker',
|
||||
"default_time_add": false,
|
||||
"show_on_advanced": false,
|
||||
"value_index": 0,
|
||||
"onclose_show" : "until",
|
||||
"synced_with": "from_time",
|
||||
//"getter_code":"from_time",
|
||||
"getter_suffix":" 00:00:00",
|
||||
},
|
||||
{
|
||||
"code": "from_time",
|
||||
"code_uc_first":"FromTime",
|
||||
"x_picker" : 'datetimepicker',
|
||||
"default_time_add": 0,
|
||||
"show_on_advanced": true,
|
||||
"value_index": 0,
|
||||
"onclose_show" : "until_time",
|
||||
"synced_with": "from",
|
||||
"title_getter_code":"from",
|
||||
},
|
||||
{
|
||||
"code": "until",
|
||||
"code_uc_first":"Until",
|
||||
"x_picker" : 'datepicker',
|
||||
"default_time_add": false,
|
||||
"show_on_advanced": false,
|
||||
"value_index": 1,
|
||||
"synced_with": "until_time",
|
||||
//"getter_code":"until_time",
|
||||
"getter_suffix":" 23:59:59",
|
||||
},
|
||||
{
|
||||
"code": "until_time",
|
||||
"code_uc_first":"UntilTime",
|
||||
"x_picker" : 'datetimepicker',
|
||||
"default_time_add": 86399, // 24 * 60 * 60 - 1
|
||||
"show_on_advanced": true,
|
||||
"value_index": 1,
|
||||
"synced_with": "until",
|
||||
"title_getter_code":"until",
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
//------------------
|
||||
// Inherited methods
|
||||
//------------------
|
||||
|
||||
// the constructor
|
||||
_create: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
me._super();
|
||||
me.element.addClass('search_form_criteria_date_time');
|
||||
},
|
||||
|
||||
// events bound via _bind are removed automatically
|
||||
// revert other modifications here
|
||||
_destroy: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
me.element.removeClass('search_form_criteria_date_time');
|
||||
me._super();
|
||||
},
|
||||
|
||||
_prepareBetweenDaysOperator: function(oOpElem, sOpIdx, oOp)
|
||||
{
|
||||
var me = this;
|
||||
var showAvancedOnInit = false;
|
||||
|
||||
me._super(oOpElem, sOpIdx, oOp);
|
||||
for (i = 0; i <= 1; i++) {
|
||||
if (typeof me.options.values[i] != 'undefined' && typeof me.options.values[i].value != 'undefined')
|
||||
{
|
||||
if (me.options.values[i].value.length > 10)
|
||||
{
|
||||
if (i == 0 && me.options.values[i].value.indexOf(' 00:00:00') == 10)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (i == 1 && me.options.values[i].value.indexOf(' 23:59:59') == 10)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
showAvancedOnInit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showAvancedOnInit)
|
||||
{
|
||||
this.element.find('.sfc_form_group').addClass('advanced');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
707
js/search/search_form_criteria_enum.js
Normal file
707
js/search/search_form_criteria_enum.js
Normal file
@@ -0,0 +1,707 @@
|
||||
//iTop Search form criteria enum
|
||||
;
|
||||
$(function()
|
||||
{
|
||||
// the widget definition, where 'itop' is the namespace,
|
||||
// 'search_form_criteria_enum' the widget name
|
||||
$.widget( 'itop.search_form_criteria_enum', $.itop.search_form_criteria,
|
||||
{
|
||||
// default options
|
||||
options:
|
||||
{
|
||||
// Overload default operator
|
||||
'operator': 'IN',
|
||||
// Available operators
|
||||
'available_operators': {
|
||||
'IN': {
|
||||
'label': Dict.S('UI:Search:Criteria:Operator:Enum:In'),
|
||||
'code': 'in',
|
||||
'rank': 10,
|
||||
},
|
||||
'=': null, // Remove this one from enum widget.
|
||||
'empty': null, // Remove as it will be handle by the "null" value in the "IN" operator
|
||||
'not_empty': null, // Remove as it will be handle by the "null" value in the "IN" operator
|
||||
},
|
||||
|
||||
// Null value
|
||||
'null_value': {
|
||||
'code': null,
|
||||
'label': Dict.S('Enum:Undefined'),
|
||||
},
|
||||
|
||||
// Autocomplete
|
||||
'autocomplete': {
|
||||
'xhr_throttle': 200,
|
||||
'min_autocomplete_chars': 3, // TODO: Pass this through widget instanciation.
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
// the constructor
|
||||
_create: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
this._super();
|
||||
this.element.addClass('search_form_criteria_enum');
|
||||
},
|
||||
// called when created, and later when changing options
|
||||
_refresh: function()
|
||||
{
|
||||
|
||||
},
|
||||
// events bound via _bind are removed automatically
|
||||
// revert other modifications here
|
||||
_destroy: function()
|
||||
{
|
||||
this.element.removeClass('search_form_criteria_enum');
|
||||
this._super();
|
||||
},
|
||||
// _setOptions is called with a hash of all options that are changing
|
||||
// always refresh when changing options
|
||||
_setOptions: function()
|
||||
{
|
||||
this._superApply(arguments);
|
||||
},
|
||||
// _setOption is called for each individual option that is changing
|
||||
_setOption: function( key, value )
|
||||
{
|
||||
this._super( key, value );
|
||||
},
|
||||
|
||||
//------------------
|
||||
// Inherited methods
|
||||
//------------------
|
||||
// - Bind external events
|
||||
_bindEvents: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
this._super();
|
||||
|
||||
// Add selected data
|
||||
this.element.on('itop.search.criteria_enum.add_selected_values', function(oEvent, oData){
|
||||
return me._onAddSelectedValues(oData);
|
||||
});
|
||||
},
|
||||
|
||||
// Events callbacks
|
||||
_onAddSelectedValues: function(oData)
|
||||
{
|
||||
this._addSelectedValues(oData);
|
||||
//this._apply();
|
||||
},
|
||||
|
||||
// DOM element helpers
|
||||
// - Prepare element DOM structure
|
||||
_prepareElement: function()
|
||||
{
|
||||
this._super();
|
||||
|
||||
// Remove more/less buttons
|
||||
this.element.find('.sfc_fg_buttons .sfc_fg_more, .sfc_fg_buttons .sfc_fg_less').remove();
|
||||
},
|
||||
_prepareInOperator: function(oOpElem, sOpIdx, oOp)
|
||||
{
|
||||
var me = this;
|
||||
|
||||
// Hide radio & name for now, until there is more than one operator
|
||||
oOpElem.find('.sfc_op_radio, .sfc_op_name').hide();
|
||||
|
||||
// DOM elements
|
||||
var sOpId = oOpElem.attr('id');
|
||||
var oOpContentElem = $('<div></div>')
|
||||
.addClass('sfc_opc_multichoices')
|
||||
.appendTo(oOpElem.find('.sfc_op_content'));
|
||||
|
||||
// - Check / Uncheck all togglers
|
||||
var sTogglerId = 'toggle_' + sOpId;
|
||||
var oTogglerElem = $('<div></div>')
|
||||
.addClass('sfc_opc_mc_toggler')
|
||||
.append('<label for="' + sTogglerId + '"><input type="checkbox" id="' + sTogglerId + '" />' + Dict.S('UI:Search:Value:Toggler:CheckAllNone') + '</label>')
|
||||
.appendTo(oOpContentElem);
|
||||
|
||||
// - Filter
|
||||
var sFilterId = 'filter_' + sOpId;
|
||||
var sFilterPlaceholder = (this._hasAutocompleteAllowedValues()) ? Dict.S('UI:Search:Value:Search:Placeholder') : Dict.S('UI:Search:Value:Filter:Placeholder');
|
||||
var oFilterElem = $('<div></div>')
|
||||
.addClass('sf_filter')
|
||||
.append('<span class="sff_input_wrapper"><input type="text" id="' + sFilterId + '" placeholder="' + sFilterPlaceholder + '" autocomplete="off" /><span class="sff_picto sff_reset fa fa-times"></span></span>')
|
||||
.appendTo(oOpContentElem);
|
||||
|
||||
// - Allowed values
|
||||
var oAllowedValuesElem = $('<div></div>')
|
||||
.addClass('sfc_opc_mc_items')
|
||||
.appendTo(oOpContentElem);
|
||||
// - Static values: Always there no matter the field constraints
|
||||
var oStaticListElem = $('<div></div>')
|
||||
.addClass('sfc_opc_mc_items_list')
|
||||
.addClass('sfc_opc_mc_items_static')
|
||||
.appendTo(oAllowedValuesElem);
|
||||
// - Dynamic values: Depends on the field constraints
|
||||
var oDynamicListElem = $('<div></div>')
|
||||
.addClass('sfc_opc_mc_items_list')
|
||||
.addClass('sfc_opc_mc_items_dynamic')
|
||||
.appendTo(oAllowedValuesElem);
|
||||
|
||||
// - Null value if allowed
|
||||
// Note: null values is NOT put among the allowed values for two reasons:
|
||||
// - It must be the first value of the list
|
||||
// - It is not give by neither the autocomplete or the pre-filled values, so we would need to manually add it in both cases, all operations.
|
||||
if(this.options.field.is_null_allowed === true)
|
||||
{
|
||||
var sValCode = this.options.null_value.code;
|
||||
var sValLabel = this.options.null_value.label;
|
||||
var oValueElem = this._makeListItemElement(sValLabel, sValCode);
|
||||
oValueElem.appendTo(oStaticListElem);
|
||||
}
|
||||
|
||||
// Events
|
||||
// - Filter
|
||||
oFilterElem.find('.sff_reset').on('click', function(){
|
||||
oFilterElem.find('input')
|
||||
.val('')
|
||||
// Focus the input for improved UX
|
||||
.trigger('focus')
|
||||
// Submit autocomplete with new value
|
||||
.trigger('itop.search.criteria_enum.autocomplete.submit');
|
||||
});
|
||||
|
||||
// - Check / Uncheck all toggler
|
||||
oTogglerElem.on('click', function(oEvent){
|
||||
// Check / uncheck all allowed values
|
||||
var bChecked = $(this).closest('.sfc_opc_mc_toggler').find('input:checkbox').prop('checked');
|
||||
oOpContentElem.find('.sfc_opc_mc_item input:checkbox').prop('checked', bChecked);
|
||||
|
||||
// Apply criteria
|
||||
//me._apply();
|
||||
});
|
||||
|
||||
if(this._hasAutocompleteAllowedValues())
|
||||
{
|
||||
this._prepareInOperatorWithAutocomplete(oOpElem, sOpIdx, oOp);
|
||||
}
|
||||
else
|
||||
{
|
||||
this._prepareInOperatorWithoutAutocomplete(oOpElem, sOpIdx, oOp);
|
||||
}
|
||||
|
||||
},
|
||||
_prepareInOperatorWithoutAutocomplete: function(oOpElem, sOpIdx, oOp)
|
||||
{
|
||||
var me = this;
|
||||
|
||||
var oOpContentElem = oOpElem.find('.sfc_opc_multichoices');
|
||||
var oTogglerElem = oOpContentElem.find('.sfc_opc_mc_toggler');
|
||||
var oFilterElem = oOpContentElem.find('.sf_filter');
|
||||
var oAllowedValuesElem = oOpContentElem.find('.sfc_opc_mc_items');
|
||||
var oDynamicListElem = oOpContentElem.find('.sfc_opc_mc_items_dynamic');
|
||||
|
||||
// DOM elements
|
||||
// - Filter
|
||||
oFilterElem.find('.sff_input_wrapper')
|
||||
.append('<span class="sff_picto sff_filter fa fa-filter"></span>');
|
||||
|
||||
// - Allowed values
|
||||
var aSortedValues = this._sortValuesByLabel(this._getPreloadedAllowedValues());
|
||||
for (var i in aSortedValues)
|
||||
{
|
||||
var sValCode = aSortedValues[i][0];
|
||||
var sValLabel = aSortedValues[i][1];
|
||||
var oValueElem = this._makeListItemElement(sValLabel, sValCode);
|
||||
oValueElem.appendTo(oDynamicListElem);
|
||||
|
||||
if (this._isSelectedValues(sValCode))
|
||||
{
|
||||
oValueElem.find(':checkbox').prop('checked', true);
|
||||
}
|
||||
}
|
||||
|
||||
// Events
|
||||
// - Filter
|
||||
// Note: "keyup" event is use instead of "keydown", otherwise, the input value would not be set yet.
|
||||
oFilterElem.find('input').on('keyup focus', function(oEvent){
|
||||
// TODO: Move on values with up and down arrow keys; select with space or enter.
|
||||
var sQuery = $(this).val();
|
||||
|
||||
if(sQuery === '')
|
||||
{
|
||||
oOpContentElem.find('.sfc_opc_mc_item').show();
|
||||
oFilterElem.find('.sff_filter').show();
|
||||
oFilterElem.find('.sff_reset').hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
oOpContentElem.find('.sfc_opc_mc_item').each(function(){
|
||||
var oRegExp = new RegExp(sQuery, 'ig');
|
||||
var sValue = $(this).find('input').val();
|
||||
var sLabel = $(this).text();
|
||||
|
||||
if( (sValue.match(oRegExp) !== null) || (sLabel.match(oRegExp) !== null) )
|
||||
{
|
||||
$(this).show();
|
||||
}
|
||||
else
|
||||
{
|
||||
$(this).hide();
|
||||
}
|
||||
});
|
||||
oFilterElem.find('.sff_filter').hide();
|
||||
oFilterElem.find('.sff_reset').show();
|
||||
}
|
||||
});
|
||||
oFilterElem.find('.sff_filter').on('click', function(){
|
||||
oFilterElem.find('input').trigger('focus');
|
||||
});
|
||||
|
||||
// - Apply on check
|
||||
oAllowedValuesElem.on('click', '.sfc_opc_mc_item input', function(oEvent){
|
||||
// Prevent propagation, otherwise there will be multiple "_apply()"
|
||||
oEvent.stopPropagation();
|
||||
|
||||
// Uncheck toggler
|
||||
oTogglerElem.find('input:checkbox').prop('checked', false);
|
||||
|
||||
// Apply criteria
|
||||
//me._apply();
|
||||
});
|
||||
},
|
||||
_prepareInOperatorWithAutocomplete: function(oOpElem, sOpIdx, oOp)
|
||||
{
|
||||
var me = this;
|
||||
|
||||
var oOpContentElem = oOpElem.find('.sfc_opc_multichoices');
|
||||
var oTogglerElem = oOpContentElem.find('.sfc_opc_mc_toggler');
|
||||
var oFilterElem = oOpContentElem.find('.sf_filter');
|
||||
var oAllowedValuesElem = oOpContentElem.find('.sfc_opc_mc_items');
|
||||
|
||||
// DOM
|
||||
// - Hide toggler for now
|
||||
oTogglerElem.hide();
|
||||
|
||||
// - Set typing hint
|
||||
this._setACTypingHint();
|
||||
|
||||
// - Add search dialog button
|
||||
oFilterElem
|
||||
.append('<button type="button" class="sff_search_dialog"><span class=" fa fa-search"></span></button>')
|
||||
.addClass('sf_with_buttons');
|
||||
|
||||
// - Prepare "selected" values area
|
||||
var oSelectedValuesElem = $('<div></div>')
|
||||
.addClass('sfc_opc_mc_items')
|
||||
.append('<div class="sfc_opc_mc_items_list sfc_opc_mc_items_selected"></div>')
|
||||
.appendTo(oOpContentElem);
|
||||
this._refreshSelectedValues();
|
||||
|
||||
// - External classes
|
||||
var oFilterIconElem = oFilterElem.find('.sff_search_dialog').uniqueId();
|
||||
oFilterIconElem.attr('id', oFilterIconElem.attr('id').replace(/-/g, '_'));
|
||||
var oForeignKeysWidgetCurrent = new SearchFormForeignKeys(
|
||||
oFilterIconElem.attr('id'), // id
|
||||
me.options.field.target_class, // sTargetClass
|
||||
me.options.field.code, // sAttCode
|
||||
me.element, // oSearchWidgetElmt
|
||||
'', // sFilter //TODO
|
||||
me.options.field.label // sTitle
|
||||
);
|
||||
window['oForeignKeysWidget'+oFilterIconElem.attr('id')] = oForeignKeysWidgetCurrent;
|
||||
oForeignKeysWidgetCurrent.Init();
|
||||
|
||||
// Events
|
||||
// - Autocomplete
|
||||
var oACXHR = null;
|
||||
var oACTimeout = null;
|
||||
oFilterElem.find('input').on('keyup itop.search.criteria_enum.autocomplete.submit', function(oEvent){
|
||||
// TODO: Move on values with up and down arrow keys; select with space or enter.
|
||||
if(me._isFilteredKey(oEvent.keyCode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var sQuery = $(this).val();
|
||||
if( (sQuery === '') ) // TODO: Put this back after tests || (sQuery.length < me.options.autocomplete.min_autocomplete_chars) )
|
||||
{
|
||||
me._setACTypingHint();
|
||||
oFilterElem.find('.sff_reset').hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Show loader
|
||||
me._setACWaitHint();
|
||||
|
||||
clearTimeout(oACTimeout);
|
||||
oACTimeout = setTimeout(function(){
|
||||
|
||||
if(oACXHR !== null)
|
||||
{
|
||||
oACXHR.abort();
|
||||
}
|
||||
oACXHR = $.post(
|
||||
AddAppContext(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'),
|
||||
{
|
||||
sTargetClass: me.options.field.target_class,
|
||||
sFilter: 'SELECT ' + me.options.field.target_class,
|
||||
q: sQuery,
|
||||
bSearchMode: 'true',
|
||||
sOutputFormat: 'json',
|
||||
operation: 'ac_extkey',
|
||||
}
|
||||
)
|
||||
.done(function(oResponse, sStatus, oXHR){ me._onACSearchSuccess(oResponse); })
|
||||
.fail(function(oResponse, sStatus, oXHR){ me._onACSearchFail(oResponse, sStatus); })
|
||||
.always(function(oResponse, sStatus, oXHR){ me._onACSearchAlways(); });
|
||||
|
||||
oFilterElem.find('.sff_reset').show();
|
||||
}, me.options.autocomplete.xhr_throttle);
|
||||
}
|
||||
});
|
||||
|
||||
// - Apply on check
|
||||
oAllowedValuesElem.on('click', '.sfc_opc_mc_item input', function(oEvent){
|
||||
// Prevent propagation, otherwise there will be multiple "_apply()"
|
||||
oEvent.stopPropagation();
|
||||
|
||||
var oItemElem = $(this).closest('.sfc_opc_mc_item');
|
||||
|
||||
// Hide item
|
||||
oItemElem.hide();
|
||||
|
||||
// Copy item to selected items list
|
||||
var oValues = {};
|
||||
oValues[oItemElem.find('input:checkbox').val()] = oItemElem.text();
|
||||
me._addSelectedValues(oValues);
|
||||
|
||||
// Apply criteria
|
||||
//me._apply();
|
||||
});
|
||||
|
||||
// - Apply on uncheck
|
||||
oSelectedValuesElem.on('click', '.sfc_opc_mc_item', function(oEvent){
|
||||
// Prevent propagation, otherwise there will be multiple "_apply()"
|
||||
oEvent.stopPropagation();
|
||||
|
||||
// Show item among allowed values (if still there, could have been removed bu another search needle)
|
||||
var oAllowedValueElem = oAllowedValuesElem.find('.sfc_opc_mc_item[data-value-code="' + $(this).attr('data-value-code') + '"]');
|
||||
if(oAllowedValueElem.length > 0)
|
||||
{
|
||||
oAllowedValuesElem.find('.sfc_opc_mc_item[data-value-code="' + $(this).attr('data-value-code') + '"]')
|
||||
.show()
|
||||
.find('input:checkbox')
|
||||
.prop('checked', false);
|
||||
}
|
||||
|
||||
// Remove item from selected values
|
||||
$(this).remove();
|
||||
me._refreshSelectedValues();
|
||||
|
||||
// Apply criteria
|
||||
//me._apply();
|
||||
});
|
||||
|
||||
// - Open search dialog
|
||||
oFilterElem.find('.sff_search_dialog').on('click', function(){
|
||||
oForeignKeysWidgetCurrent.ShowModalSearchForeignKeys();
|
||||
});
|
||||
},
|
||||
_setTitle: function(sTitle)
|
||||
{
|
||||
var iValLimit = 3;
|
||||
var iValCount = Object.keys(this.options.values).length;
|
||||
var iAllowedValuesCount = Object.keys(this._getPreloadedAllowedValues()).length;
|
||||
|
||||
// Manually increase allowed values count if null is allowed
|
||||
if( (this.options.field.is_null_allowed === true) && (this._hasAutocompleteAllowedValues() === false) )
|
||||
{
|
||||
iAllowedValuesCount++;
|
||||
}
|
||||
|
||||
// Making right tite regarding the number of selected values
|
||||
if( (iValCount === 0) || (iValCount === iAllowedValuesCount) )
|
||||
{
|
||||
sTitle = Dict.Format('UI:Search:Criteria:Title:Enum:In:All', this.options.field.label);
|
||||
}
|
||||
else if(iValCount > iValLimit)
|
||||
{
|
||||
var aFirstValues = [];
|
||||
for(var i=0; i<iValLimit-1; i++)
|
||||
{
|
||||
aFirstValues.push(this.options.values[i].label);
|
||||
}
|
||||
|
||||
sTitle = Dict.Format('UI:Search:Criteria:Title:Enum:In:Many', this.options.field.label, aFirstValues.join(', '), (iValCount - iValLimit+1));
|
||||
}
|
||||
|
||||
this._super(sTitle);
|
||||
},
|
||||
|
||||
// Operators helpers
|
||||
// Reset operator's state
|
||||
_resetInOperator: function(oOpElem)
|
||||
{
|
||||
// Uncheck toggler
|
||||
oOpElem.find('sfc_opc_mc_toggler input').prop('checked', false);
|
||||
|
||||
// Clear filter
|
||||
oOpElem.find('sfc_opc_mc_filter input').val('');
|
||||
},
|
||||
// Get operator's values
|
||||
_getInOperatorValues: function(oOpElem)
|
||||
{
|
||||
var aValues = [];
|
||||
|
||||
var sValuesSelector = this._getSelectedValuesWrapperSelector();
|
||||
oOpElem.find(sValuesSelector).find('.sfc_opc_mc_item input:checked').each(function(iIdx, oElem){
|
||||
var sValue = $(oElem).val();
|
||||
var sLabel = $(oElem).parent().text();
|
||||
aValues.push({value: sValue, label: sLabel});
|
||||
});
|
||||
|
||||
return aValues;
|
||||
},
|
||||
// Set operator's values
|
||||
_setInOperatorValues: function(oOpElem, aValues)
|
||||
{
|
||||
if(aValues.length === 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Uncheck all allowed values
|
||||
oOpElem.find('.sfc_opc_mc_item input').prop('checked', false);
|
||||
|
||||
// Re-check allowed values from param
|
||||
for(var iIdx in aValues)
|
||||
{
|
||||
if(oOpElem.find('.sfc_opc_mc_item[data-value-code="' + aValues[iIdx].value + '"]').length > 0)
|
||||
{
|
||||
oOpElem.find('.sfc_opc_mc_item[data-value-code="' + aValues[iIdx].value + '"] input')
|
||||
.prop('checked', true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var oItemElem = this._makeListItemElement(aValues[iIdx].label, aValues[iIdx].value, true);
|
||||
oItemElem.appendTo(this._getSelectedValuesWrapperSelector());
|
||||
}
|
||||
}
|
||||
|
||||
this._refreshSelectedValues();
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
|
||||
// Autocomplete helpers
|
||||
_setACHint: function(sHint)
|
||||
{;
|
||||
this.element.find('.sfc_opc_mc_items_dynamic').html('<div class="sfc_opc_mc_placeholder">' + sHint + '</div>');
|
||||
},
|
||||
_setACTypingHint: function()
|
||||
{
|
||||
this._setACHint(Dict.S('UI:Search:Value:Autocomplete:StartTyping'));
|
||||
},
|
||||
_setACWaitHint: function()
|
||||
{
|
||||
this._setACHint(Dict.S('UI:Search:Value:Autocomplete:Wait'));
|
||||
},
|
||||
_setACNoResultHint: function()
|
||||
{
|
||||
this._setACHint(Dict.S('UI:Search:Value:Autocomplete:NoResult'));
|
||||
},
|
||||
// Autocomplete callbacks
|
||||
_onACSearchSuccess: function(oResponse)
|
||||
{
|
||||
if(typeof oResponse !== 'object')
|
||||
{
|
||||
this._onACSearchFail(oResponse, 'unexcepted');
|
||||
return false;
|
||||
}
|
||||
|
||||
var oDynamicListElem = this.element.find('.sfc_opc_mc_items_dynamic')
|
||||
.html('');
|
||||
if(Object.keys(oResponse).length > 0)
|
||||
{
|
||||
// Note: Response is indexed by labels from server so the JSON is always ordered on decoding.
|
||||
for(var sLabel in oResponse)
|
||||
{
|
||||
var sValue = oResponse[sLabel];
|
||||
var bInitChecked = this._isSelectedValues(sValue);
|
||||
var bInitHidden = this._isSelectedValues(sValue);
|
||||
var oValueElem = this._makeListItemElement(sLabel, sValue, bInitChecked, bInitHidden);
|
||||
oValueElem.appendTo(oDynamicListElem);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this._setACNoResultHint();
|
||||
}
|
||||
},
|
||||
_onACSearchFail: function(oResponse, sStatus)
|
||||
{
|
||||
if(sStatus !== 'abort')
|
||||
{
|
||||
var sErrorMessage = Dict.Format('Error:XHR:Fail', '');
|
||||
|
||||
this._setACHint('=/');
|
||||
this.handler.triggerHandler('itop.search.criteria.error_occured', sErrorMessage);
|
||||
}
|
||||
},
|
||||
_onACSearchAlways: function()
|
||||
{
|
||||
},
|
||||
|
||||
|
||||
// Value helpers
|
||||
// - Return the selector fo the element containing the selected values
|
||||
_getSelectedValuesWrapperSelector: function()
|
||||
{
|
||||
return (this._hasAutocompleteAllowedValues()) ? '.sfc_opc_mc_items_selected' : '.sfc_opc_mc_items_static, .sfc_opc_mc_items_dynamic';
|
||||
},
|
||||
// - Return true if sValue is among the selected values "codes"
|
||||
_isSelectedValues: function(sValue)
|
||||
{
|
||||
var bFound = false;
|
||||
|
||||
for(var iValIdx in this.options.values)
|
||||
{
|
||||
// Note: Mind the double "=" instead of triple as the .value can be either string or integer whereas the sValue is always string
|
||||
if(this.options.values[iValIdx].value == sValue)
|
||||
{
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return bFound;
|
||||
},
|
||||
// - Return true if sLabel is among the selected values "labels"
|
||||
_isSelectedLabels: function(sLabel)
|
||||
{
|
||||
var bFound = false;
|
||||
|
||||
for(var iValIdx in this.options.values)
|
||||
{
|
||||
// Note: Mind the double "=" instead of triple as the .label can be either string or integer whereas the sLabel is always string
|
||||
if(this.options.values[iValIdx].label == sLabel)
|
||||
{
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return bFound;
|
||||
},
|
||||
// - Add oValues to the list of selected values
|
||||
_addSelectedValues: function(oValues)
|
||||
{
|
||||
var oSelectedValuesElem = this.element.find(this._getSelectedValuesWrapperSelector());
|
||||
for(var sValue in oValues)
|
||||
{
|
||||
// Add only if not already selected
|
||||
if(this._isSelectedValues(sValue))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(oSelectedValuesElem.find('.sfc_opc_mc_item[data-value-code="' + sValue + '"]').length > 0)
|
||||
{
|
||||
oSelectedValuesElem.find('.sfc_opc_mc_item[data-value-code="' + sValue + '"] input')
|
||||
.prop('checked', true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var oItemElem = this._makeListItemElement(oValues[sValue], sValue, true);
|
||||
oItemElem
|
||||
.appendTo(oSelectedValuesElem)
|
||||
.effect('highlight', {}, 500);
|
||||
}
|
||||
|
||||
this.options.values.push({
|
||||
value: sValue,
|
||||
label: oValues[sValue]
|
||||
});
|
||||
}
|
||||
this._refreshSelectedValues();
|
||||
},
|
||||
_refreshSelectedValues: function()
|
||||
{
|
||||
// No sense to make this in preloaded mode
|
||||
if(this._hasAutocompleteAllowedValues() === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Show / hide
|
||||
var oSelectedValuesElem = this.element.find(this._getSelectedValuesWrapperSelector());
|
||||
if(oSelectedValuesElem.find('.sfc_opc_mc_item').length > 0)
|
||||
{
|
||||
oSelectedValuesElem.show();
|
||||
}
|
||||
else
|
||||
{
|
||||
oSelectedValuesElem.hide();
|
||||
}
|
||||
|
||||
// TODO: Reorder
|
||||
// oSelectedValuesElem.html('');
|
||||
// var aSortedValues = this._sortValuesByLabel(this.options.values);
|
||||
// for(var iIdx in aSortedValues)
|
||||
// {
|
||||
// var oItemElem = this._makeListItemElement(aSortedValues[iIdx][1], aSortedValues[iIdx][0], true);
|
||||
// oItemElem.appendTo(oSelectedValuesElem);
|
||||
// }
|
||||
},
|
||||
// - Return an array of allowed values sorted by labels
|
||||
_sortValuesByLabel: function(oSource)
|
||||
{
|
||||
var aSortable = [];
|
||||
var bSourceAsObject = $.isPlainObject(oSource);
|
||||
for (var sKey in oSource) {
|
||||
// eg. [{value: "3", label: "Demo"}, {value: "2", label: "IT Department"}] in autocomplete mode
|
||||
if(bSourceAsObject === false)
|
||||
{
|
||||
aSortable.push([oSource[sKey].value, oSource[sKey].label]);
|
||||
}
|
||||
// eg. {2: "IT Department", 3: "Demo"} in regular mode
|
||||
else
|
||||
{
|
||||
aSortable.push([sKey, oSource[sKey]]);
|
||||
}
|
||||
}
|
||||
|
||||
aSortable.sort(function(a, b) {
|
||||
if(a[1] < b[1])
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if(a[1] > b[1])
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
return aSortable;
|
||||
},
|
||||
// - Make a jQuery element for a list item
|
||||
_makeListItemElement: function(sLabel, sValue, bInitChecked, bInitHidden)
|
||||
{
|
||||
var oItemElem = $('<div></div>')
|
||||
.addClass('sfc_opc_mc_item')
|
||||
.attr('data-value-code', sValue)
|
||||
.append('<label><input type="checkbox" value="'+sValue+'"/>'+sLabel+'</label>');
|
||||
|
||||
if(bInitChecked === true)
|
||||
{
|
||||
oItemElem.find('input:checkbox').prop('checked', true);
|
||||
}
|
||||
if(bInitHidden === true)
|
||||
{
|
||||
oItemElem.hide();
|
||||
}
|
||||
|
||||
return oItemElem;
|
||||
},
|
||||
});
|
||||
});
|
||||
25
js/search/search_form_criteria_external_field.js
Normal file
25
js/search/search_form_criteria_external_field.js
Normal file
@@ -0,0 +1,25 @@
|
||||
//iTop Search form criteria external_field
|
||||
;
|
||||
$(function () {
|
||||
// the widget definition, where 'itop' is the namespace,
|
||||
// 'search_form_criteria_external_field' the widget name
|
||||
$.widget('itop.search_form_criteria_external_field', $.itop.search_form_criteria_string,
|
||||
{
|
||||
// the constructor
|
||||
_create: function () {
|
||||
this._super();
|
||||
this.element.addClass('search_form_criteria_external_field');
|
||||
},
|
||||
|
||||
// events bound via _bind are removed automatically
|
||||
// revert other modifications here
|
||||
_destroy: function () {
|
||||
this.element.removeClass('search_form_criteria_external_field');
|
||||
this._super();
|
||||
},
|
||||
|
||||
//------------------
|
||||
// Inherited methods
|
||||
//------------------
|
||||
});
|
||||
});
|
||||
56
js/search/search_form_criteria_external_key.js
Normal file
56
js/search/search_form_criteria_external_key.js
Normal file
@@ -0,0 +1,56 @@
|
||||
//iTop Search form criteria external key
|
||||
;
|
||||
$(function()
|
||||
{
|
||||
// the widget definition, where 'itop' is the namespace,
|
||||
// 'search_form_criteria_external_key' the widget name
|
||||
$.widget( 'itop.search_form_criteria_external_key', $.itop.search_form_criteria_enum,
|
||||
{
|
||||
// default options
|
||||
options:
|
||||
{
|
||||
// Null value
|
||||
'null_value': {
|
||||
'code': 0,
|
||||
'label': Dict.S('UI:UndefinedObject'),
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
// the constructor
|
||||
_create: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
this._super();
|
||||
this.element.addClass('search_form_criteria_external_key');
|
||||
},
|
||||
// called when created, and later when changing options
|
||||
_refresh: function()
|
||||
{
|
||||
|
||||
},
|
||||
// events bound via _bind are removed automatically
|
||||
// revert other modifications here
|
||||
_destroy: function()
|
||||
{
|
||||
this.element.removeClass('search_form_criteria_external_key');
|
||||
this._super();
|
||||
},
|
||||
// _setOptions is called with a hash of all options that are changing
|
||||
// always refresh when changing options
|
||||
_setOptions: function()
|
||||
{
|
||||
this._superApply(arguments);
|
||||
},
|
||||
// _setOption is called for each individual option that is changing
|
||||
_setOption: function( key, value )
|
||||
{
|
||||
this._super( key, value );
|
||||
},
|
||||
|
||||
//------------------
|
||||
// Inherited methods
|
||||
//------------------
|
||||
});
|
||||
});
|
||||
73
js/search/search_form_criteria_hierarchical_key.js
Normal file
73
js/search/search_form_criteria_hierarchical_key.js
Normal file
@@ -0,0 +1,73 @@
|
||||
//iTop Search form criteria hierarchical key
|
||||
;
|
||||
$(function()
|
||||
{
|
||||
// the widget definition, where 'itop' is the namespace,
|
||||
// 'search_form_criteria_hierarchical_key' the widget name
|
||||
$.widget( 'itop.search_form_criteria_hierarchical_key', $.itop.search_form_criteria_enum,
|
||||
{
|
||||
// default options
|
||||
options:
|
||||
{
|
||||
},
|
||||
|
||||
|
||||
// the constructor
|
||||
_create: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
this._super();
|
||||
this.element.addClass('search_form_criteria_hierarchical_key');
|
||||
},
|
||||
// called when created, and later when changing options
|
||||
_refresh: function()
|
||||
{
|
||||
|
||||
},
|
||||
// events bound via _bind are removed automatically
|
||||
// revert other modifications here
|
||||
_destroy: function()
|
||||
{
|
||||
this.element.removeClass('search_form_criteria_hierarchical_key');
|
||||
this._super();
|
||||
},
|
||||
// _setOptions is called with a hash of all options that are changing
|
||||
// always refresh when changing options
|
||||
_setOptions: function()
|
||||
{
|
||||
this._superApply(arguments);
|
||||
},
|
||||
// _setOption is called for each individual option that is changing
|
||||
_setOption: function( key, value )
|
||||
{
|
||||
this._super( key, value );
|
||||
},
|
||||
|
||||
//------------------
|
||||
// Inherited methods
|
||||
//------------------
|
||||
|
||||
// DOM element helpers
|
||||
prepareInOperator: function(oOpElem, sOpIdx, oOp)
|
||||
{
|
||||
var me = this;
|
||||
|
||||
this._super();
|
||||
|
||||
// Note: Hierarchical key is on stand by for now.
|
||||
// // DOM elements
|
||||
// // - Add search dialog button
|
||||
// this.element.find('.sf_filter')
|
||||
// .append('<button type="button" class="sff_hierarchy_dialog"><span class=" fa fa-sitemap"></span></button>')
|
||||
// .addClass('sf_with_buttons');
|
||||
//
|
||||
// // Events
|
||||
// // - Open hierarchy dialog
|
||||
// this.element.find('.sff_hierarchy_dialog').on('click', function(){
|
||||
// // TODO: Open hierarchy dialog with right params
|
||||
// alert('Not implemented yet');
|
||||
// });
|
||||
},
|
||||
});
|
||||
});
|
||||
336
js/search/search_form_criteria_numeric.js
Normal file
336
js/search/search_form_criteria_numeric.js
Normal file
@@ -0,0 +1,336 @@
|
||||
//iTop Search form criteria numeric
|
||||
;
|
||||
$(function()
|
||||
{
|
||||
// the widget definition, where 'itop' is the namespace,
|
||||
// 'search_form_criteria_numeric' the widget name
|
||||
$.widget( 'itop.search_form_criteria_numeric', $.itop.search_form_criteria,
|
||||
{
|
||||
// default options
|
||||
options:
|
||||
{
|
||||
// Overload default operator
|
||||
'operator': '=',
|
||||
// Available operators
|
||||
'available_operators': {
|
||||
'=': {
|
||||
'label': Dict.S('UI:Search:Criteria:Operator:Numeric:Equals'),//pre-existing, label changed
|
||||
// 'dropdown_group':1,
|
||||
},
|
||||
'>': {
|
||||
'label': Dict.S('UI:Search:Criteria:Operator:Numeric:GreaterThan'),
|
||||
'code': 'greater_than',
|
||||
'rank': 100,
|
||||
// 'dropdown_group':1,
|
||||
},
|
||||
'>=': {
|
||||
'label': Dict.S('UI:Search:Criteria:Operator:Numeric:GreaterThanOrEquals'),
|
||||
'code': 'greater_than_or_equals',
|
||||
'rank': 200,
|
||||
// 'dropdown_group':1,
|
||||
},
|
||||
'<': {
|
||||
'label': Dict.S('UI:Search:Criteria:Operator:Numeric:LessThan'),
|
||||
'code': 'less_than',
|
||||
'rank': 300,
|
||||
// 'dropdown_group':1,
|
||||
},
|
||||
'<=': {
|
||||
'label': Dict.S('UI:Search:Criteria:Operator:Numeric:LessThanOrEquals'),
|
||||
'code': 'less_than_or_equals',
|
||||
'rank': 400,
|
||||
// 'dropdown_group':1,
|
||||
},
|
||||
'!=': {
|
||||
'label': Dict.S('UI:Search:Criteria:Operator:Numeric:Different'),
|
||||
'code': 'different',
|
||||
'rank': 500,
|
||||
// 'dropdown_group':1,
|
||||
},
|
||||
|
||||
'between': {
|
||||
'label': Dict.S('UI:Search:Criteria:Operator:Numeric:Between'),
|
||||
'code': 'between',
|
||||
'rank': 600,
|
||||
},
|
||||
|
||||
'empty': {
|
||||
'rank': 700,//pre-existing, reordered
|
||||
},
|
||||
'not_empty': {
|
||||
'rank': 800,//pre-existing, reordered
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
// the constructor
|
||||
_create: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
this._super();
|
||||
this.element.addClass('search_form_criteria_numeric');
|
||||
},
|
||||
// called when created, and later when changing options
|
||||
_refresh: function()
|
||||
{
|
||||
|
||||
},
|
||||
// events bound via _bind are removed automatically
|
||||
// revert other modifications here
|
||||
_destroy: function()
|
||||
{
|
||||
this.element.removeClass('search_form_criteria_numeric');
|
||||
this._super();
|
||||
},
|
||||
// _setOptions is called with a hash of all options that are changing
|
||||
// always refresh when changing options
|
||||
_setOptions: function()
|
||||
{
|
||||
this._superApply(arguments);
|
||||
},
|
||||
// _setOption is called for each individual option that is changing
|
||||
_setOption: function( key, value )
|
||||
{
|
||||
this._super( key, value );
|
||||
},
|
||||
|
||||
|
||||
// Prepare operator's DOM element
|
||||
_prepareBetweenOperator: function(oOpElem, sOpIdx, oOp)
|
||||
{
|
||||
var me = this;
|
||||
|
||||
aValues = me._getValues();//TODO : tenir compte du refactoring de la structure
|
||||
|
||||
// DOM elements
|
||||
var oOpContentOuterElemFrom = $('<div class="sfc_op_content_from_outer"><label class="sfc_op_content_from_label" for=""> '+Dict.S('UI:Search:Criteria:Numeric:From')+' </label><input type="text" name="from" placeholder="'+Dict.S('UI:Search:Criteria:Numeric:PlaceholderFrom')+'"/></div>');
|
||||
var oOpContentElemFrom = oOpContentOuterElemFrom.find('input').uniqueId();
|
||||
oOpContentOuterElemFrom.find('label').attr('for', oOpContentElemFrom.attr('id'));
|
||||
if (typeof aValues[0] != 'undefined' && typeof aValues[0].value != 'undefined')
|
||||
{
|
||||
oOpContentElemFrom.val(aValues[0].value);
|
||||
}
|
||||
var oOpContentOuterElemUntil = $('<div class="sfc_op_content_until_outer"><label class="sfc_op_content_until_label" for=""> '+Dict.S('UI:Search:Criteria:Numeric:Until')+' </label><input type="text" name="until" placeholder="'+Dict.S('UI:Search:Criteria:Numeric:PlaceholderUntil')+'"/></div>');
|
||||
var oOpContentElemUntil = oOpContentOuterElemUntil.find('input').uniqueId();
|
||||
oOpContentOuterElemUntil.find('label').attr('for', oOpContentElemUntil.attr('id'));
|
||||
if (typeof aValues[1] != 'undefined' && typeof aValues[1].value != 'undefined')
|
||||
{
|
||||
oOpContentElemUntil.val(aValues[1].value);
|
||||
}
|
||||
|
||||
|
||||
oOpContentElem = $().add(oOpContentOuterElemFrom).add(oOpContentOuterElemUntil);
|
||||
|
||||
// Events
|
||||
// - Focus input on click (radio, label, ...)
|
||||
oOpElem.on('click', function(oEvent){
|
||||
if ($(oEvent.target).is('input[type="text"], select')) {
|
||||
return;
|
||||
}
|
||||
oOpContentElemFrom.focus();
|
||||
});
|
||||
// - Apply on "enter" key hit
|
||||
// TODO: this could be refactored
|
||||
oOpContentElem.on('keydown', function(oEvent){
|
||||
me._markAsDraft();
|
||||
});
|
||||
|
||||
oOpElem
|
||||
.find('.sfc_op_name')
|
||||
.remove()
|
||||
.end()
|
||||
.find('.sfc_op_content')
|
||||
.append(oOpContentElem)
|
||||
;
|
||||
|
||||
},
|
||||
|
||||
_getBetweenOperatorValues: function(oOpElem)
|
||||
{
|
||||
var aValues = [];
|
||||
|
||||
var sValueFrom = oOpElem.find('.sfc_op_content input[name="from"]').val();
|
||||
var sValueUntil = oOpElem.find('.sfc_op_content input[name="until"]').val();
|
||||
|
||||
aValues.push({value: sValueFrom, label: sValueFrom});
|
||||
aValues.push({value: sValueUntil, label: sValueUntil});
|
||||
|
||||
|
||||
return aValues;
|
||||
},
|
||||
|
||||
_setBetweenOperatorValues: function(oOpElem, aValues)
|
||||
{
|
||||
switch (aValues.length)
|
||||
{
|
||||
case 2:
|
||||
oOpElem.find('.sfc_op_content input[name="until"]').val(aValues[1].value);
|
||||
//NO BREAK!!!
|
||||
case 1:
|
||||
oOpElem.find('.sfc_op_content input[name="from"]').val(aValues[0].value);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_resetBetweenOperator: function(oOpElem)
|
||||
{
|
||||
this._resetOperator(oOpElem);
|
||||
},
|
||||
|
||||
//------------------
|
||||
// Inherited methods
|
||||
//------------------
|
||||
|
||||
|
||||
_setTitle: function(sTitle)
|
||||
{
|
||||
var me = this;
|
||||
if (sTitle === undefined && me.options.operator == 'between')
|
||||
{
|
||||
var aValues = me._getValues();
|
||||
switch (true)
|
||||
{
|
||||
case (typeof aValues[0] == 'undefined' && typeof aValues[1] == 'undefined'):
|
||||
var sDictEntrySuffix = ':All';
|
||||
break;
|
||||
case (typeof aValues[0] == 'undefined' ):
|
||||
var sDictEntrySuffix = ':Until';
|
||||
break;
|
||||
case (typeof aValues[1] == 'undefined'):
|
||||
var sDictEntrySuffix = ':From';
|
||||
break;
|
||||
case (typeof aValues[0].label == 'undefined' && typeof aValues[1].label == 'undefined'):
|
||||
var sDictEntrySuffix = ':All';
|
||||
break;
|
||||
case (typeof aValues[0].label == 'undefined' ):
|
||||
var sDictEntrySuffix = ':Until';
|
||||
break;
|
||||
case (typeof aValues[1].label == 'undefined' ):
|
||||
var sDictEntrySuffix = ':From';
|
||||
break;
|
||||
case ((typeof aValues[0].label == 'string' && aValues[0].label.trim() == '') && (typeof aValues[1].label == 'string' && aValues[1].label.trim() == '')):
|
||||
var sDictEntrySuffix = ':All';
|
||||
break;
|
||||
case (typeof aValues[0].label == 'string' && aValues[0].label.trim() == ''):
|
||||
var sDictEntrySuffix = ':Until';
|
||||
break;
|
||||
case (typeof aValues[1].label == 'string' && aValues[1].label.trim() == ''):
|
||||
var sDictEntrySuffix = ':From';
|
||||
break;
|
||||
default:
|
||||
var sDictEntrySuffix = undefined;
|
||||
break;
|
||||
}
|
||||
|
||||
if (sDictEntrySuffix != undefined)
|
||||
{
|
||||
var sDictEntry = 'UI:Search:Criteria:Title:' + this._toCamelCase(this.options.field.widget) + ':' + this._toCamelCase(me.options.operator) + sDictEntrySuffix ;
|
||||
// Fallback to default widget dict entry if none exists for the current widget
|
||||
if(Dict.S(sDictEntry) === sDictEntry)
|
||||
{
|
||||
sDictEntry = 'UI:Search:Criteria:Title:Default:' + this._toCamelCase(me.options.operator) + sDictEntrySuffix;
|
||||
}
|
||||
|
||||
sTitle = Dict.Format(sDictEntry, this.options.field.label, this._getValuesAsText());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return me._super(sTitle);
|
||||
},
|
||||
|
||||
|
||||
// - Convert values to a standard string
|
||||
_getValuesAsText: function(aRawValues)
|
||||
{
|
||||
var me = this;
|
||||
|
||||
if (aRawValues == undefined)
|
||||
{
|
||||
aRawValues = me._getValues();
|
||||
}
|
||||
if (me.options.operator == 'between')
|
||||
{
|
||||
aRawValues = aRawValues.slice(); //clone
|
||||
if (typeof aRawValues[1] == 'undefined' || typeof aRawValues[1].label == 'undefined' || (typeof aRawValues[1].label == 'string' && aRawValues[1].label.trim() == ''))
|
||||
{
|
||||
aRawValues.splice(1, 1);
|
||||
}
|
||||
if (typeof aRawValues[0] == 'undefined' || typeof aRawValues[0].label == 'undefined' || (typeof aRawValues[0].label == 'string' && aRawValues[0].label.trim() == ''))
|
||||
{
|
||||
aRawValues.splice(0, 1);
|
||||
}
|
||||
}
|
||||
return me._super(aRawValues);
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
// Prepare operator's DOM element
|
||||
// - Base preparation, always called
|
||||
_prepareOperator: function(oOpElem, sOpIdx, oOp)
|
||||
{
|
||||
var me = this;
|
||||
if (typeof oOp.dropdown_group == 'undefined')
|
||||
{
|
||||
return this._super(oOpElem, sOpIdx, oOp);
|
||||
}
|
||||
|
||||
this._super(oOpElem, sOpIdx, oOp);
|
||||
oOpElem.addClass('force_hide')
|
||||
|
||||
//TODO: Move this into the abstract widget
|
||||
|
||||
// DOM element
|
||||
oDropdownElem = this.element.find('select.dropdown_group_'+oOp.dropdown_group);
|
||||
if (oDropdownElem.length == 0)
|
||||
{
|
||||
oDropdownElem = $('<select class="dropdown_group_'+oOp.dropdown_group+'" data-dropdown-group="'+oOp.dropdown_group+'"></select>');
|
||||
|
||||
oDropdownElem.on('change', function(){
|
||||
// $option = $(this);
|
||||
me.element.find('.sfc_fg_operator_dropdown_group .sfc_op_radio').val(oDropdownElem.val());
|
||||
|
||||
oOptionOp = oDropdownElem.data('oOp');
|
||||
oDropdownElem.attr('data-operator-code', oOptionOp.code);
|
||||
});
|
||||
|
||||
|
||||
// Create DOM element from template
|
||||
var oOpElemDropdown = $(this._getOperatorTemplate()).uniqueId();
|
||||
|
||||
// TODO: If this code is keeped, the radio must have an id and the label need to point to it
|
||||
oOpElemDropdown
|
||||
.addClass('sfc_fg_operator_dropdown_group')
|
||||
.attr('data-operator-code', 'dropdown_group')
|
||||
.find('.sfc_op_name')
|
||||
.append(oDropdownElem)
|
||||
.end()
|
||||
.find('.sfc_op_radio')
|
||||
.val(sOpIdx)
|
||||
.end()
|
||||
.on('click', function(oEvent){
|
||||
//oOpElemDropdown.find('input[type="text"]:first').focus();
|
||||
})
|
||||
.appendTo(this.element.find('.sfc_fg_operators'))
|
||||
;
|
||||
|
||||
this._prepareDefaultOperator(oOpElemDropdown, sOpIdx, oOp);
|
||||
}
|
||||
|
||||
oDropdownElem
|
||||
.append('<option value="'+sOpIdx+'" >'+oOp.label+'</option>')
|
||||
.data('oOp', oOp)
|
||||
;
|
||||
},
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
80
js/search/search_form_criteria_raw.js
Normal file
80
js/search/search_form_criteria_raw.js
Normal file
@@ -0,0 +1,80 @@
|
||||
//iTop Search form criteria raw
|
||||
;
|
||||
$(function()
|
||||
{
|
||||
// the widget definition, where 'itop' is the namespace,
|
||||
// 'search_form_criteria_raw' the widget name
|
||||
$.widget( 'itop.search_form_criteria_raw', $.itop.search_form_criteria,
|
||||
{
|
||||
// default options
|
||||
options:
|
||||
{
|
||||
'label': '', // Computed by server
|
||||
},
|
||||
|
||||
// the constructor
|
||||
_create: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
this._super();
|
||||
this.element.addClass('search_form_criteria_raw');
|
||||
},
|
||||
// called when created, and later when changing options
|
||||
_refresh: function()
|
||||
{
|
||||
|
||||
},
|
||||
// events bound via _bind are removed automatically
|
||||
// revert other modifications here
|
||||
_destroy: function()
|
||||
{
|
||||
this.element.removeClass('search_form_criteria_raw');
|
||||
this._super();
|
||||
},
|
||||
// _setOptions is called with a hash of all options that are changing
|
||||
// always refresh when changing options
|
||||
_setOptions: function()
|
||||
{
|
||||
this._superApply(arguments);
|
||||
},
|
||||
// _setOption is called for each individual option that is changing
|
||||
_setOption: function( key, value )
|
||||
{
|
||||
this._super( key, value );
|
||||
},
|
||||
|
||||
//------------------
|
||||
// Inherited methods
|
||||
//------------------
|
||||
|
||||
// DOM element helpers
|
||||
_prepareElement: function()
|
||||
{
|
||||
this._super();
|
||||
|
||||
// Remove toggler as it's a non sense here
|
||||
this.element.find('.sfc_toggle').remove();
|
||||
this.element.find('.sfc_toggle, .sfc_title').off('click');
|
||||
|
||||
// Force close as it has no sense either
|
||||
this._close();
|
||||
},
|
||||
_prepareOperators: function()
|
||||
{
|
||||
// Overloading function and doing nothing for this special kind of criteria.
|
||||
},
|
||||
_prepareButtons: function()
|
||||
{
|
||||
// Overloading function and doing nothing for this special kind of criteria.
|
||||
},
|
||||
_setTitle: function(sTitle)
|
||||
{
|
||||
if(sTitle === undefined)
|
||||
{
|
||||
sTitle = this.options.label;
|
||||
}
|
||||
this._super(sTitle);
|
||||
},
|
||||
});
|
||||
});
|
||||
77
js/search/search_form_criteria_string.js
Normal file
77
js/search/search_form_criteria_string.js
Normal file
@@ -0,0 +1,77 @@
|
||||
//iTop Search form criteria string
|
||||
;
|
||||
$(function()
|
||||
{
|
||||
// the widget definition, where 'itop' is the namespace,
|
||||
// 'search_form_criteria_string' the widget name
|
||||
$.widget( 'itop.search_form_criteria_string', $.itop.search_form_criteria,
|
||||
{
|
||||
// default options
|
||||
options:
|
||||
{
|
||||
// Overload default operator
|
||||
'operator': 'contains',
|
||||
// Available operators
|
||||
'available_operators': {
|
||||
'contains': {
|
||||
'label': Dict.S('UI:Search:Criteria:Operator:String:Contains'),
|
||||
'code': 'contains',
|
||||
'rank': 10,
|
||||
},
|
||||
'starts_with': {
|
||||
'label': Dict.S('UI:Search:Criteria:Operator:String:StartsWith'),
|
||||
'code': 'starts_with',
|
||||
'rank': 20,
|
||||
},
|
||||
'ends_with': {
|
||||
'label': Dict.S('UI:Search:Criteria:Operator:String:EndsWith'),
|
||||
'code': 'ends_with',
|
||||
'rank': 30,
|
||||
},
|
||||
'REGEXP': {
|
||||
'label': Dict.S('UI:Search:Criteria:Operator:String:RegExp'),
|
||||
'code': 'reg_exp',
|
||||
'rank': 40,
|
||||
},
|
||||
'=': null, // Remove this one from string widget.
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
// the constructor
|
||||
_create: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
this._super();
|
||||
this.element.addClass('search_form_criteria_string');
|
||||
},
|
||||
// called when created, and later when changing options
|
||||
_refresh: function()
|
||||
{
|
||||
|
||||
},
|
||||
// events bound via _bind are removed automatically
|
||||
// revert other modifications here
|
||||
_destroy: function()
|
||||
{
|
||||
this.element.removeClass('search_form_criteria_string');
|
||||
this._super();
|
||||
},
|
||||
// _setOptions is called with a hash of all options that are changing
|
||||
// always refresh when changing options
|
||||
_setOptions: function()
|
||||
{
|
||||
this._superApply(arguments);
|
||||
},
|
||||
// _setOption is called for each individual option that is changing
|
||||
_setOption: function( key, value )
|
||||
{
|
||||
this._super( key, value );
|
||||
},
|
||||
|
||||
//------------------
|
||||
// Inherited methods
|
||||
//------------------
|
||||
});
|
||||
});
|
||||
963
js/search/search_form_handler.js
Normal file
963
js/search/search_form_handler.js
Normal file
@@ -0,0 +1,963 @@
|
||||
//iTop Search form handler
|
||||
;
|
||||
$(function()
|
||||
{
|
||||
// the widget definition, where 'itop' is the namespace,
|
||||
// 'search_form_handler' the widget name
|
||||
$.widget( 'itop.search_form_handler',
|
||||
{
|
||||
// default options
|
||||
options:
|
||||
{
|
||||
'criterion_outer_selector': null,
|
||||
'result_list_outer_selector': null,
|
||||
'data_config_list_selector': null,
|
||||
'submit_button_selector': null,
|
||||
'hide_initial_criterion': false, // TODO: What is that?
|
||||
'endpoint': null,
|
||||
'init_opened': false,
|
||||
"datepicker":
|
||||
{
|
||||
"dayNamesMin": ["Su","Mo","Tu","We","Th","Fr","Sa"],
|
||||
"monthNamesShort": ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],
|
||||
"firstDay": 0
|
||||
},
|
||||
'search': {
|
||||
'base_oql': '',
|
||||
'class_name': null,
|
||||
'criterion': [
|
||||
// Structure
|
||||
// {
|
||||
// 'or': [
|
||||
// {
|
||||
// 'and': [
|
||||
// {
|
||||
// 'ref': 'alias.code',
|
||||
// 'operator': 'contains',
|
||||
// 'values': [
|
||||
// {
|
||||
// 'value': 'foo',
|
||||
// 'label': 'bar',
|
||||
// }
|
||||
// ],
|
||||
// 'is_removable': true,
|
||||
// 'oql': '',
|
||||
// },
|
||||
// ]
|
||||
// },
|
||||
// ]
|
||||
// },
|
||||
],
|
||||
'fields': [
|
||||
// Structure
|
||||
// 'zlist': {
|
||||
// 'alias.code': {
|
||||
// 'class_alias': '',
|
||||
// 'class': '',
|
||||
// 'code': '',
|
||||
// 'label': '',
|
||||
// 'type': '',
|
||||
// 'allowed_values': {...},
|
||||
// },
|
||||
// },
|
||||
// 'others': {
|
||||
// 'alias.code': {
|
||||
// 'class_alias': '',
|
||||
// 'class': '',
|
||||
// 'code': '',
|
||||
// 'label': '',
|
||||
// 'type': '',
|
||||
// 'allowed_values': {...},
|
||||
// },
|
||||
// },
|
||||
],
|
||||
},
|
||||
'default_criteria_type': 'raw',
|
||||
},
|
||||
|
||||
// jQuery elements
|
||||
elements: null,
|
||||
|
||||
// Submit properties (XHR, throttle, ...)
|
||||
submit: null,
|
||||
|
||||
// the constructor
|
||||
_create: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
this.element.addClass('search_form_handler');
|
||||
|
||||
// Init properties (complexe type properties would be static if not initialized with a simple type variable...)
|
||||
this.elements = {
|
||||
message_area: null,
|
||||
criterion_area: null,
|
||||
more_criterion: null,
|
||||
results_area: null,
|
||||
};
|
||||
this.submit = {
|
||||
xhr: null,
|
||||
};
|
||||
|
||||
//init others widgets :
|
||||
this.element.search_form_handler_history({"itop_root_class":me.options.search.class_name});
|
||||
|
||||
// Prepare DOM elements
|
||||
this._prepareFormArea();
|
||||
this._prepareCriterionArea();
|
||||
this._prepareResultsArea();
|
||||
|
||||
// Binding buttons
|
||||
if(this.options.submit_button_selector !== null)
|
||||
{
|
||||
$(this.options.submit_button_selector).off('click').on('click', function(oEvent){ me._onSubmitClick(oEvent); });
|
||||
}
|
||||
|
||||
// Binding events (eg. from search_form_criteria widgets)
|
||||
this._bindEvents();
|
||||
},
|
||||
|
||||
// called when created, and later when changing options
|
||||
_refresh: function()
|
||||
{
|
||||
|
||||
},
|
||||
// events bound via _bind are removed automatically
|
||||
// revert other modifications here
|
||||
_destroy: function()
|
||||
{
|
||||
this.element
|
||||
.removeClass('search_form_handler');
|
||||
},
|
||||
// _setOptions is called with a hash of all options that are changing
|
||||
// always refresh when changing options
|
||||
_setOptions: function()
|
||||
{
|
||||
this._superApply(arguments);
|
||||
},
|
||||
// _setOption is called for each individual option that is changing
|
||||
_setOption: function( key, value )
|
||||
{
|
||||
this._super( key, value );
|
||||
},
|
||||
|
||||
|
||||
//
|
||||
_bindEvents: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
// Form events
|
||||
// - Prevent regular form submission (eg. hitting "Enter" in inputs)
|
||||
this.element.on('submit', function(oEvent){
|
||||
oEvent.preventDefault();
|
||||
});
|
||||
// - Submit the search form
|
||||
this.element.on('itop.search.form.submit', function(oEvent, oData){
|
||||
me._onSubmit();
|
||||
});
|
||||
|
||||
// Criteria events
|
||||
this.element.on('itop.search.criteria.value_changed', function(oEvent, oData){
|
||||
me._onCriteriaValueChanged(oData);
|
||||
});
|
||||
this.element.on('itop.search.criteria.removed', function(oEvent, oData){
|
||||
me._onCriteriaRemoved(oData);
|
||||
});
|
||||
this.element.on('itop.search.criteria.error_occured', function(oEvent, oData){
|
||||
me._onCriteriaErrorOccured(oData);
|
||||
});
|
||||
},
|
||||
// - Update search option of the widget
|
||||
_updateSearch: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
// Criterion
|
||||
// - Note: As of today, only a "or" level with a "and" is supported, so the following part
|
||||
// will need some refactoring when introducing new stuff.
|
||||
var oCriterion = {
|
||||
'or': [{
|
||||
'and': []
|
||||
}]
|
||||
};
|
||||
// - Retrieve criterion
|
||||
this.elements.criterion_area.find('.search_form_criteria').each(function(){
|
||||
var oCriteriaData = $(this).triggerHandler('itop.search.criteria.get_data');
|
||||
oCriterion['or'][0]['and'].push(oCriteriaData);
|
||||
});
|
||||
// - Update search
|
||||
this.options.search.criterion = oCriterion;
|
||||
|
||||
// No need to update base OQL and fields
|
||||
},
|
||||
// - Open / Close more criterion menu
|
||||
_openMoreCriterion: function()
|
||||
{
|
||||
// Open more criterion menu
|
||||
// - Open it first
|
||||
this.elements.more_criterion.addClass('opened');
|
||||
// - Focus filter
|
||||
this.elements.more_criterion.find('.sf_filter:first input[type="text"]')
|
||||
.val('')
|
||||
.focus();
|
||||
// - Then only check if more menu is to close to the right side (otherwise we might not have the right element's position)
|
||||
var iFormWidth = this.element.outerWidth();
|
||||
var iFormLeftPos = this.element.offset().left;
|
||||
var iMenuWidth = this.elements.more_criterion.find('.sfm_content').outerWidth();
|
||||
var iMenuLeftPos = this.elements.more_criterion.find('.sfm_content').offset().left;
|
||||
if( (iMenuWidth + iMenuLeftPos) > (iFormWidth + iFormLeftPos - 10 /* Security margin */) )
|
||||
{
|
||||
this.elements.more_criterion.addClass('opened_left');
|
||||
}
|
||||
},
|
||||
_closeMoreCriterion: function()
|
||||
{
|
||||
this.elements.more_criterion.removeClass('opened_left');
|
||||
this.elements.more_criterion.removeClass('opened');
|
||||
},
|
||||
_toggleMoreCriterion: function()
|
||||
{
|
||||
// Calling methods instead of toggling the class so additional processing are done.
|
||||
if(this.elements.more_criterion.hasClass('opened'))
|
||||
{
|
||||
this._closeMoreCriterion();
|
||||
}
|
||||
else
|
||||
{
|
||||
this._openMoreCriterion();
|
||||
}
|
||||
},
|
||||
// - Close all criterion
|
||||
_closeAllCriterion: function()
|
||||
{
|
||||
this.elements.criterion_area.find('.search_form_criteria').each(function(){
|
||||
$(this).triggerHandler('itop.search.criteria.close');
|
||||
});
|
||||
},
|
||||
|
||||
// DOM helpers
|
||||
// - Prepare form area
|
||||
_prepareFormArea: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
// Build DOM elements
|
||||
// - Message area
|
||||
this.elements.message_area = this.element.find('.sf_message');
|
||||
this._cleanMessageArea();
|
||||
|
||||
// Events
|
||||
// - Refresh icon
|
||||
this.element.find('.sft_refresh').on('click', function(oEvent){
|
||||
// Prevent anchor
|
||||
oEvent.preventDefault();
|
||||
// Prevent form toggling
|
||||
oEvent.stopPropagation();
|
||||
|
||||
me._submit();
|
||||
});
|
||||
// - Toggle icon
|
||||
// TODO: UX Improvment
|
||||
// Note: Would be better to toggle by clicking on the whole title, but we have an issue with <select> on abstract classes.
|
||||
this.element.find('.sf_title').on('click', function(oEvent){
|
||||
// Prevent anchors
|
||||
oEvent.preventDefault();
|
||||
|
||||
// Prevent toggle on <select>
|
||||
if(oEvent.target.nodeName.toLowerCase() !== 'select')
|
||||
{
|
||||
me.element.find('.sf_criterion_area').slideToggle('fast');
|
||||
me.element.toggleClass('opened');
|
||||
}
|
||||
});
|
||||
},
|
||||
// - Prepare criterion area
|
||||
_prepareCriterionArea: function()
|
||||
{
|
||||
var oCriterionAreaElem;
|
||||
|
||||
// Build area element
|
||||
if(this.options.criterion_outer_selector !== null && $(this.options.criterion_outer_selector).length > 0)
|
||||
{
|
||||
oCriterionAreaElem = $(this.options.criterion_outer_selector);
|
||||
}
|
||||
else
|
||||
{
|
||||
oCriterionAreaElem = $('<div></div>').appendTo(this.element);
|
||||
}
|
||||
oCriterionAreaElem.addClass('sf_criterion_area');
|
||||
this.elements.criterion_area = oCriterionAreaElem;
|
||||
|
||||
// Clean area
|
||||
oCriterionAreaElem
|
||||
.html('')
|
||||
.append('<div class="sf_more_criterion"></div>');
|
||||
this.elements.more_criterion = oCriterionAreaElem.find('.sf_more_criterion');
|
||||
|
||||
// Prepare content
|
||||
this._prepareExistingCriterion();
|
||||
this._prepareMoreCriterionMenu();
|
||||
},
|
||||
// - Prepare existing criterion
|
||||
_prepareExistingCriterion: function()
|
||||
{
|
||||
// - OR conditions
|
||||
var aORs = (this.options.search.criterion['or'] !== undefined) ? this.options.search.criterion['or'] : [];
|
||||
for(var iORIdx in aORs)
|
||||
{
|
||||
// Note: We might want to create a OR container here when handling several OR conditions.
|
||||
|
||||
var aANDs = (aORs[iORIdx]['and'] !== undefined) ? aORs[iORIdx]['and'] : [];
|
||||
for(var iANDIdx in aANDs)
|
||||
{
|
||||
var oCriteriaData = aANDs[iANDIdx];
|
||||
this._addCriteria(oCriteriaData);
|
||||
}
|
||||
}
|
||||
},
|
||||
// - Prepare "more" button
|
||||
_prepareMoreCriterionMenu: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
// Header part
|
||||
var oHeaderElem = $('<div class="sfm_header"></div>')
|
||||
.append('<a class="sfm_toggler" href="#"><span class="sfm_tg_title">' + Dict.S('UI:Search:Criterion:MoreMenu:AddCriteria') + '</span><span class="sfm_tg_icon fa fa-plus"></span></a>')
|
||||
.appendTo(this.elements.more_criterion);
|
||||
|
||||
// Content part
|
||||
var oContentElem = $('<div class="sfm_content"></div>')
|
||||
.appendTo(this.elements.more_criterion);
|
||||
|
||||
// - Filter
|
||||
var oFilterElem = $('<div></div>')
|
||||
.addClass('sf_filter')
|
||||
.addClass('sfm_filter')
|
||||
.append('<span class="sff_input_wrapper"><input type="text" placeholder="' + Dict.S('UI:Search:Value:Filter:Placeholder') + '" /><span class="sff_picto sff_filter fa fa-filter"></span><span class="sff_picto sff_reset fa fa-times"></span></span>')
|
||||
.appendTo(oContentElem);
|
||||
|
||||
// - Lists container
|
||||
var oListsElem = $('<div></div>')
|
||||
.addClass('sfm_lists')
|
||||
.appendTo(oContentElem);
|
||||
|
||||
// - Recently used list
|
||||
var oRecentsElem = $('<div></div>')
|
||||
.addClass('sf_list')
|
||||
.addClass('sf_list_recents')
|
||||
.appendTo(oListsElem);
|
||||
|
||||
$('<div class="sfl_title"></div>')
|
||||
.text(Dict.S('UI:Search:AddCriteria:List:RecentlyUsed:Title'))
|
||||
.appendTo(oRecentsElem);
|
||||
|
||||
var oRecentsItemsElem = $('<ul class="sfl_items"></ul>')
|
||||
.append('<li class="sfl_i_placeholder">' + Dict.S('UI:Search:AddCriteria:List:RecentlyUsed:Placeholder') + '</li>')
|
||||
.appendTo(oRecentsElem);
|
||||
|
||||
me._refreshRecentlyUsed();
|
||||
|
||||
// - Search zlist list
|
||||
var oZlistElem = $('<div></div>')
|
||||
.addClass('sf_list')
|
||||
.addClass('sf_list_zlist')
|
||||
.appendTo(oListsElem);
|
||||
|
||||
$('<div class="sfl_title"></div>')
|
||||
.text(Dict.S('UI:Search:AddCriteria:List:MostPopular:Title'))
|
||||
.appendTo(oZlistElem);
|
||||
|
||||
var oZListItemsElem = $('<ul class="sfl_items"></ul>')
|
||||
.appendTo(oZlistElem);
|
||||
|
||||
for(var sFieldRef in this.options.search.fields.zlist)
|
||||
{
|
||||
var oFieldElem = me._getHtmlLiFromFieldRef(me, sFieldRef, ['zlist']);
|
||||
oFieldElem.appendTo(oZListItemsElem);
|
||||
}
|
||||
|
||||
// - Remaining fields list
|
||||
if(this.options.search.fields.others !== undefined)
|
||||
{
|
||||
var oOthersElem = $('<div></div>')
|
||||
.addClass('sf_list')
|
||||
.addClass('sf_list_others')
|
||||
.appendTo(oListsElem);
|
||||
|
||||
$('<div class="sfl_title"></div>')
|
||||
.text(Dict.S('UI:Search:AddCriteria:List:Others:Title'))
|
||||
.appendTo(oOthersElem);
|
||||
|
||||
var oOthersItemsElem = $('<ul class="sfl_items"></ul>')
|
||||
.appendTo(oOthersElem);
|
||||
|
||||
for(var sFieldRef in this.options.search.fields.others)
|
||||
{
|
||||
var oFieldElem = me._getHtmlLiFromFieldRef(me, sFieldRef, ['others']);
|
||||
oFieldElem.appendTo(oOthersItemsElem);
|
||||
}
|
||||
}
|
||||
|
||||
// - Buttons
|
||||
var oButtonsElem = $('<div></div>')
|
||||
.addClass('sfm_buttons')
|
||||
.append('<button type="button" name="apply">' + Dict.S('UI:Button:Apply') + '</button>')
|
||||
.append('<button type="button" name="cancel">' + Dict.S('UI:Button:Cancel') + '</button>')
|
||||
.appendTo(oContentElem);
|
||||
|
||||
// Bind events
|
||||
// - Close menu on click anywhere else
|
||||
// - Intercept click to avoid propagation (mostly used for closing it when clicking outside of it)
|
||||
$('body').on('click', function(oEvent){
|
||||
// Prevent propagation to parents and therefore multiple attempts to close it
|
||||
oEvent.stopPropagation();
|
||||
|
||||
oEventTargetElem = $(oEvent.target);
|
||||
|
||||
// If not more menu, close all criterion
|
||||
if(oEventTargetElem.closest('.sf_more_criterion').length > 0)
|
||||
{
|
||||
me._closeAllCriterion();
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Try to put this back in the date widget as it introduced a non necessary coupling.
|
||||
// If using the datetimepicker, do not close anything
|
||||
if (oEventTargetElem.closest('#ui-datepicker-div, .ui-datepicker-prev, .ui-datepicker-next').length > 0 )
|
||||
{
|
||||
// No closing in this case
|
||||
}
|
||||
// //if the context is not document, then we have encountered a bug : the css ::after elements do have a context on click and thus, we cannot check if they are inside a #ui-datepicker-div
|
||||
// else if (typeof oEventTargetElem.context != 'undefined' && $(oEventTargetElem.context).is('.ui-icon'))
|
||||
// {
|
||||
// //no closing in this case (bugfix)
|
||||
// }
|
||||
// If criteria, close more menu & all criterion but me
|
||||
else if(oEventTargetElem.closest('.search_form_criteria').length > 0)
|
||||
{
|
||||
me._closeMoreCriterion();
|
||||
// All criterion but me is already handle by the criterion, no callback needed.
|
||||
}
|
||||
// If not criteria, close more menu & all criterion
|
||||
else
|
||||
{
|
||||
me._closeMoreCriterion();
|
||||
me._closeAllCriterion();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// - More criteria toggling
|
||||
this.elements.more_criterion.find('.sfm_header').on('click', function(oEvent){
|
||||
// Prevent anchor
|
||||
oEvent.preventDefault();
|
||||
|
||||
me._toggleMoreCriterion();
|
||||
});
|
||||
|
||||
// - Filter
|
||||
// Note: "keyup" event is use instead of "keydown", otherwise, the inpu value would not be set yet.
|
||||
oFilterElem.find('input').on('keyup focus', function(oEvent){
|
||||
// TODO: Move on values with up and down arrow keys; select with space or enter.
|
||||
// TODO: Hide list if no result on filter.
|
||||
|
||||
var sFilter = $(this).val();
|
||||
|
||||
// Show / hide items
|
||||
if(sFilter === '')
|
||||
{
|
||||
oListsElem.find('.sfl_items > li').show();
|
||||
oFilterElem.find('.sff_filter').show();
|
||||
oFilterElem.find('.sff_reset').hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
oListsElem.find('.sfl_items > li:not(.sfl_i_placeholder)').each(function(){
|
||||
var oRegExp = new RegExp(sFilter, 'ig');
|
||||
var sValue = $(this).find('input').val();
|
||||
var sLabel = $(this).text();
|
||||
|
||||
// We don't check the sValue as it contains the class alias.
|
||||
if(sLabel.match(oRegExp) !== null)
|
||||
{
|
||||
$(this).show();
|
||||
}
|
||||
else
|
||||
{
|
||||
$(this).hide();
|
||||
}
|
||||
});
|
||||
oFilterElem.find('.sff_filter').hide();
|
||||
oFilterElem.find('.sff_reset').show();
|
||||
}
|
||||
|
||||
// Show / hide lists with no visible items
|
||||
oListsElem.find('.sf_list').each(function(){
|
||||
$(this).show();
|
||||
if($(this).find('.sfl_items > li:visible').length === 0)
|
||||
{
|
||||
$(this).hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
oFilterElem.find('.sff_filter').on('click', function(){
|
||||
oFilterElem.find('input').trigger('focus');
|
||||
});
|
||||
oFilterElem.find('.sff_reset').on('click', function(){
|
||||
oFilterElem.find('input')
|
||||
.val('')
|
||||
.trigger('focus');
|
||||
});
|
||||
|
||||
// - Add one criteria
|
||||
this.elements.more_criterion.on('click', '.sfm_field', function(oEvent){
|
||||
// Prevent anchor
|
||||
oEvent.preventDefault();
|
||||
// Prevent propagation to not close the opening criteria
|
||||
oEvent.stopPropagation();
|
||||
|
||||
// If no checkbox checked, add criteria right away, otherwise we "queue" it we other checkboxed.
|
||||
if(me.elements.more_criterion.find('.sfm_field input[type="checkbox"]:checked').length === 0)
|
||||
{
|
||||
var sFieldRef = $(this).attr('data-field-ref');
|
||||
|
||||
// Prepare new criterion data (as already opened to increase UX)
|
||||
var oData = {
|
||||
'ref': sFieldRef,
|
||||
'init_opened': (oEvent.ctrlKey) ? false : true,
|
||||
};
|
||||
|
||||
// Add criteria but don't submit form as the user has not specified the value yet.
|
||||
me.element.search_form_handler_history('setLatest', sFieldRef);
|
||||
me._refreshRecentlyUsed();
|
||||
me._addCriteria(oData);
|
||||
}
|
||||
else
|
||||
{
|
||||
$(this).find('input[type="checkbox"]').prop('checked', !$(this).find('input[type="checkbox"]').prop('checked'));
|
||||
}
|
||||
});
|
||||
|
||||
// - Add several criterion
|
||||
this.elements.more_criterion.on('click', '.sfm_field input[type="checkbox"]', function(oEvent){
|
||||
// Prevent propagation to field and instant add of the criteria
|
||||
oEvent.stopPropagation();
|
||||
|
||||
if(me.elements.more_criterion.find('.sfm_field input[type="checkbox"]:checked').length === 0)
|
||||
{
|
||||
oButtonsElem.hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
oButtonsElem.show();
|
||||
}
|
||||
|
||||
// Put focus back to filter to improve UX.
|
||||
oFilterElem.find('input').trigger('focus');
|
||||
});
|
||||
oButtonsElem.find('button').on('click', function(){
|
||||
// Add criterion on apply
|
||||
if($(this).attr('name') === 'apply')
|
||||
{
|
||||
me.elements.more_criterion.find('.sfm_field input[type="checkbox"]:checked').each(function(iIdx, oElem){
|
||||
var sFieldRef = $(oElem).closest('.sfm_field').attr('data-field-ref');
|
||||
var oData = {
|
||||
'ref': sFieldRef,
|
||||
'init_opened': false,
|
||||
};
|
||||
|
||||
me.element.search_form_handler_history('setLatest', sFieldRef);
|
||||
me._addCriteria(oData);
|
||||
});
|
||||
|
||||
me._refreshRecentlyUsed();
|
||||
me._closeMoreCriterion();
|
||||
}
|
||||
|
||||
// Clear all
|
||||
// - Checkboxes
|
||||
me.elements.more_criterion.find('.sfm_field input[type="checkbox"]:checked').prop('checked', false);
|
||||
// - Filter
|
||||
oFilterElem.find('input')
|
||||
.val('')
|
||||
.trigger('focus');
|
||||
|
||||
// Hide buttons
|
||||
oButtonsElem.hide();
|
||||
});
|
||||
},
|
||||
// - Prepare results area
|
||||
_prepareResultsArea: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
var oResultAreaElem;
|
||||
|
||||
// Build area element
|
||||
if(this.options.result_list_outer_selector !== null && $(this.options.result_list_outer_selector).length > 0)
|
||||
{
|
||||
oResultAreaElem = $(this.options.result_list_outer_selector);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reusing previously created DOM element
|
||||
if(this.element.closest('.display_block').next('.sf_results_area').length > 0)
|
||||
{
|
||||
oResultAreaElem = this.element.closest('.display_block').next('.sf_results_area');
|
||||
}
|
||||
else
|
||||
{
|
||||
oResultAreaElem = $('<div class="display_block"></div>').insertAfter(this.element.closest('.display_block'));
|
||||
}
|
||||
}
|
||||
oResultAreaElem.addClass('sf_results_area');
|
||||
|
||||
// Make placeholder if nothing yet
|
||||
if(oResultAreaElem.html() === '')
|
||||
{
|
||||
// TODO: Make a good UI for this POC.
|
||||
// TODO: Translate sentence.
|
||||
oResultAreaElem.html('<div class="sf_results_placeholder"><p>Add some criterion on the search box or click the search button to view the objects.</p><p><button type="button">Search<span class="fa fa-search"></span></button></p></div>');
|
||||
oResultAreaElem.find('button').on('click', function(){
|
||||
// TODO: Bug: Open "Search for CI", change child classe in the dropdown, click the search button. It submit the search for the original child classe, not the current one; whereas a click on the upper right pictogram does. This might be due to the form reloading.
|
||||
me._onSubmitClick();
|
||||
});
|
||||
}
|
||||
|
||||
this.elements.results_area = oResultAreaElem;
|
||||
},
|
||||
|
||||
/**
|
||||
* "add new criteria" <li /> markup
|
||||
* - with checkbox, label, data-* ...
|
||||
* - without event binding
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param sFieldRef
|
||||
* @param aFieldCollectionsEligible
|
||||
*
|
||||
* @return jQuery detached <li />
|
||||
*/
|
||||
_getHtmlLiFromFieldRef: function(me, sFieldRef, aFieldCollectionsEligible) {
|
||||
var oFieldElem = undefined;
|
||||
|
||||
aFieldCollectionsEligible.forEach(function (sFieldCollection) {
|
||||
if (typeof me.options.search.fields[sFieldCollection][sFieldRef] == 'undefined')
|
||||
{
|
||||
return true;//if this field is not present in the Collection, let's try the next
|
||||
}
|
||||
|
||||
var oField = me.options.search.fields[sFieldCollection][sFieldRef];
|
||||
var sFieldTitleAttr = (oField.description !== undefined) ? 'title="' + oField.description + '"' : '';
|
||||
oFieldElem = $('<li></li>')
|
||||
.addClass('sfm_field')
|
||||
.attr('data-field-ref', sFieldRef)
|
||||
.append('<label ' + sFieldTitleAttr + '><input type="checkbox" value="' + sFieldRef + '" />' + oField.label + '</label>')
|
||||
});
|
||||
|
||||
if (undefined == oFieldElem)
|
||||
{
|
||||
me._trace('no sFieldRef in given collection', {"sFieldRef":sFieldRef, "aFieldCollectionsEligible":aFieldCollectionsEligible});
|
||||
return $('<!-- no sFieldRef in given collection -->');
|
||||
}
|
||||
|
||||
return oFieldElem;
|
||||
},
|
||||
|
||||
|
||||
// Criteria helpers
|
||||
// - Add a criteria to the form
|
||||
_addCriteria: function(oData)
|
||||
{
|
||||
var sRef = oData.ref;
|
||||
var sType = sType = (oData.widget !== undefined) ? oData.widget : this._getCriteriaTypeFromFieldRef(sRef);
|
||||
|
||||
// Force to raw for non removable criteria
|
||||
if( (oData.is_removable !== undefined) && (oData.is_removable === false) )
|
||||
{
|
||||
sType = 'raw';
|
||||
}
|
||||
|
||||
// Protection against bad initialization data
|
||||
if(sType === null)
|
||||
{
|
||||
this._trace('Could not add criteria as we could not retrieve type for ref "'+sRef+'".');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Retrieve widget class
|
||||
var sWidgetName = this._getCriteriaWidgetNameFromType(sType);
|
||||
|
||||
// Add some informations from the field
|
||||
if(this._hasFieldDefinition(sRef))
|
||||
{
|
||||
var oFieldDef = this._getFieldDefinition(sRef);
|
||||
oData.field = {
|
||||
label: oFieldDef.label,
|
||||
class: oFieldDef.class,
|
||||
class_alias: oFieldDef.class_alias,
|
||||
code: oFieldDef.code,
|
||||
target_class: oFieldDef.target_class,
|
||||
widget: oFieldDef.widget,
|
||||
allowed_values: oFieldDef.allowed_values,
|
||||
is_null_allowed: oFieldDef.is_null_allowed,
|
||||
};
|
||||
}
|
||||
|
||||
if ('date' == sType || 'date_time' == sType)
|
||||
{
|
||||
oData.datepicker = this.options.datepicker;
|
||||
}
|
||||
|
||||
// Create DOM element
|
||||
var oCriteriaElem = $('<div></div>')
|
||||
.addClass('sf_criteria')
|
||||
//.insertBefore(this.elements.more_criterion);
|
||||
.appendTo(this.elements.criterion_area);
|
||||
|
||||
// Instanciate widget
|
||||
$.itop[sWidgetName](oData, oCriteriaElem);
|
||||
|
||||
return true;
|
||||
},
|
||||
// - Find a criteria's type from a field's ref (usually <CLASS_ALIAS>.<ATT_CODE>)
|
||||
_getCriteriaTypeFromFieldRef: function(sRef)
|
||||
{
|
||||
// Fallback for unknown widget types or unknown field refs
|
||||
var sType = this.options.default_criteria_type;
|
||||
|
||||
for(var sListIdx in this.options.search.fields)
|
||||
{
|
||||
if(this.options.search.fields[sListIdx][sRef] !== undefined)
|
||||
{
|
||||
sType = this.options.search.fields[sListIdx][sRef].widget.toLowerCase();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return sType;
|
||||
},
|
||||
// - Find a criteria's widget name from a criteria's type
|
||||
_getCriteriaWidgetNameFromType: function(sType)
|
||||
{
|
||||
return 'search_form_criteria' + '_' + (($.itop['search_form_criteria_'+sType] !== undefined) ? sType : 'raw');
|
||||
},
|
||||
// Criteria handlers
|
||||
_onCriteriaValueChanged: function(oData)
|
||||
{
|
||||
this._updateSearch();
|
||||
this._submit();
|
||||
},
|
||||
_onCriteriaRemoved: function(oData)
|
||||
{
|
||||
this._updateSearch();
|
||||
this._submit();
|
||||
},
|
||||
_onCriteriaErrorOccured: function(oData)
|
||||
{
|
||||
this._setErrorMessage(oData);
|
||||
},
|
||||
|
||||
// Field helpers
|
||||
_hasFieldDefinition: function(sRef)
|
||||
{
|
||||
var bFound = false;
|
||||
|
||||
for(var sListIdx in this.options.search.fields)
|
||||
{
|
||||
if(this.options.search.fields[sListIdx][sRef] !== undefined)
|
||||
{
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return bFound;
|
||||
},
|
||||
_getFieldDefinition: function(sRef)
|
||||
{
|
||||
var oFieldDef = null;
|
||||
|
||||
for(var sListIdx in this.options.search.fields)
|
||||
{
|
||||
if(this.options.search.fields[sListIdx][sRef] !== undefined)
|
||||
{
|
||||
oFieldDef = this.options.search.fields[sListIdx][sRef];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return oFieldDef;
|
||||
},
|
||||
|
||||
// Message helper
|
||||
_cleanMessageArea: function()
|
||||
{
|
||||
this.elements.message_area
|
||||
.hide()
|
||||
.html('')
|
||||
.removeClass('message_error');
|
||||
},
|
||||
_setErrorMessage: function(sMessage)
|
||||
{
|
||||
this.elements.message_area
|
||||
.addClass('message_error')
|
||||
.html(sMessage)
|
||||
.show();
|
||||
},
|
||||
|
||||
// Button handlers
|
||||
_onSubmitClick: function(oEvent)
|
||||
{
|
||||
// Assertion: the search is already up to date
|
||||
this._submit();
|
||||
},
|
||||
|
||||
|
||||
// Submit handlers
|
||||
// - External event callback
|
||||
_onSubmit: function()
|
||||
{
|
||||
this._submit();
|
||||
},
|
||||
// - Do the submit
|
||||
_submit: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
// Data
|
||||
// - Regular params
|
||||
var oData = {
|
||||
'params': JSON.stringify({
|
||||
'base_oql': this.options.search.base_oql,
|
||||
'criterion': this.options.search.criterion,
|
||||
}),
|
||||
};
|
||||
// - List params (pass through for the server), merge data_config with list_params if present.
|
||||
var oListParams = {};
|
||||
if(this.options.data_config_list_selector !== null)
|
||||
{
|
||||
var sExtraParams = $(this.options.data_config_list_selector).data('sExtraParams');
|
||||
if(sExtraParams !== undefined)
|
||||
{
|
||||
oListParams = JSON.parse(sExtraParams);
|
||||
}
|
||||
}
|
||||
$.extend(oListParams, this.options.list_params);
|
||||
oData.list_params = JSON.stringify(oListParams);
|
||||
|
||||
// Abort pending request
|
||||
if(this.submit.xhr !== null)
|
||||
{
|
||||
this.submit.xhr.abort();
|
||||
}
|
||||
|
||||
// Show loader
|
||||
this._showLoader();
|
||||
this._cleanMessageArea();
|
||||
|
||||
// Do submit
|
||||
this.submit.xhr = $.post(
|
||||
this.options.endpoint,
|
||||
oData
|
||||
)
|
||||
.done(function(oResponse, sStatus, oXHR){ me._onSubmitSuccess(oResponse); })
|
||||
.fail(function(oResponse, sStatus, oXHR){ me._onSubmitFailure(oResponse, sStatus); })
|
||||
.always(function(oResponse, sStatus, oXHR){ me._onSubmitAlways(oResponse); });
|
||||
},
|
||||
// - Called on form submit successes
|
||||
_onSubmitSuccess: function(oData)
|
||||
{
|
||||
this.elements.results_area.html(oData);
|
||||
},
|
||||
// - Called on form submit failures
|
||||
_onSubmitFailure: function(oData, sStatus)
|
||||
{
|
||||
if(sStatus === 'abort')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fallback message in case the server send back only HTML markup.
|
||||
var oErrorElem = $(oData.responseText);
|
||||
var sErrorMessage = (oErrorElem.text() !== '') ? oErrorElem.text() : Dict.Format('Error:XHR:Fail', '');
|
||||
|
||||
this._setErrorMessage(sErrorMessage);
|
||||
},
|
||||
// - Called after form submits
|
||||
_onSubmitAlways: function(oData)
|
||||
{
|
||||
this._hideLoader();
|
||||
},
|
||||
|
||||
|
||||
// Global helpers
|
||||
_refreshRecentlyUsed: function()
|
||||
{
|
||||
me = this;
|
||||
|
||||
var aHistory = me.element.search_form_handler_history("getHistory");
|
||||
var oRecentsItemsElem = me.element.find('.sf_list_recents .sfl_items');
|
||||
|
||||
if (aHistory.length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
oRecentsItemsElem.empty();
|
||||
|
||||
|
||||
aHistory.forEach(function(sFieldRef) {
|
||||
var oFieldElem = me._getHtmlLiFromFieldRef(me, sFieldRef, ['zlist', 'others']);
|
||||
oRecentsItemsElem.append(oFieldElem);
|
||||
});
|
||||
|
||||
},
|
||||
// - Show loader
|
||||
_showLoader: function()
|
||||
{
|
||||
this.elements.results_area.block();
|
||||
},
|
||||
// - Hide loader
|
||||
_hideLoader: function()
|
||||
{
|
||||
this.elements.results_area.unblock();
|
||||
},
|
||||
// - Converts a snake_case string to CamelCase
|
||||
_toCamelCase: function(sString)
|
||||
{
|
||||
var aParts = sString.split('_');
|
||||
|
||||
for(var i in aParts)
|
||||
{
|
||||
aParts[i] = aParts[i].charAt(0).toUpperCase() + aParts[i].substr(1);
|
||||
}
|
||||
|
||||
return aParts.join('');
|
||||
},
|
||||
|
||||
|
||||
// Debug helpers
|
||||
// - Show a trace in the javascript console
|
||||
_trace: function(sMessage, oData)
|
||||
{
|
||||
if(window.console)
|
||||
{
|
||||
if(oData !== undefined)
|
||||
{
|
||||
console.log('Search form handler: ' + sMessage, oData);
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log('Search form handler: ' + sMessage);
|
||||
}
|
||||
}
|
||||
},
|
||||
// - Show current options
|
||||
showOptions: function()
|
||||
{
|
||||
this._trace('Options', this.options);
|
||||
}
|
||||
});
|
||||
});
|
||||
132
js/search/search_form_handler_history.js
Normal file
132
js/search/search_form_handler_history.js
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
*
|
||||
* iTop Search form history handler
|
||||
*
|
||||
* the widget exposes
|
||||
*
|
||||
* $(cssSelector).search_form_handler_history({"itop_root_class":"FooBarClass"}) : constructor
|
||||
* $(cssSelector).search_form_handler_history("getHistory") : history array getter
|
||||
* $(cssSelector).search_form_handler_history("setLatest", "field.ref") : prepend the field ref to the beginning of the history array
|
||||
*
|
||||
*
|
||||
* please take a look at the options for custom constructor values
|
||||
*
|
||||
*
|
||||
* The persistence layer rely on these two core iTop's JS functions :
|
||||
* - GetUserPreference(sPreferenceCode, sDefaultValue)
|
||||
* - SetUserPreference(sPreferenceCode, sPrefValue, bPersistent)
|
||||
*
|
||||
*/
|
||||
;
|
||||
$(function()
|
||||
{
|
||||
// the widget definition, where 'itop' is the namespace,
|
||||
// 'search_form_handler_history' the widget name
|
||||
$.widget( 'itop.search_form_handler_history',
|
||||
{
|
||||
// default options
|
||||
options:
|
||||
{
|
||||
"itop_root_class": null,
|
||||
"preference_code": undefined, // if undefined, it is computed on _create
|
||||
"history_max_length": 10, // if the history is longer, the older entries are removed
|
||||
"history_backend_store_timeout": 5000, // wait for this time before storing the new history in the backend
|
||||
},
|
||||
|
||||
aHistory : [],
|
||||
|
||||
iStoreHistoryTimeoutHandler : undefined,
|
||||
|
||||
/**
|
||||
* the constructor, called by the widget factory
|
||||
* @private
|
||||
*/
|
||||
_create: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
if (me.options.preference_code == undefined)
|
||||
{
|
||||
me.options.preference_code = me.options.itop_root_class + '__search_history';
|
||||
}
|
||||
|
||||
|
||||
me.aHistory = JSON.parse(GetUserPreference(me._getPreferenceCode(), "[]"));
|
||||
},
|
||||
|
||||
getHistory: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
return me.aHistory;
|
||||
},
|
||||
|
||||
setLatest: function(sFieldRef)
|
||||
{
|
||||
var me = this;
|
||||
|
||||
//if present, delete
|
||||
var iIndexInHistory = me.aHistory.indexOf(sFieldRef);
|
||||
if (iIndexInHistory > -1)
|
||||
{
|
||||
me.aHistory.splice(iIndexInHistory, 1);
|
||||
}
|
||||
|
||||
//add to the begining
|
||||
me.aHistory.unshift(sFieldRef);
|
||||
|
||||
//restrain the length to me.options.history_max_length
|
||||
var iDeleteCount = me.aHistory.length - me.options.history_max_length;
|
||||
if (iDeleteCount > 0)
|
||||
{
|
||||
me.aHistory.splice(-iDeleteCount, iDeleteCount);
|
||||
}
|
||||
|
||||
//store it in the backend (with a delay in the hope to wait long enough to make bulk modifications
|
||||
me._storeHistory();
|
||||
|
||||
//setter should never return a value!
|
||||
return;
|
||||
},
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @returns String
|
||||
* @private
|
||||
*/
|
||||
_getPreferenceCode: function()
|
||||
{
|
||||
return this.options.preference_code;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* should only be called by setLatest in order to store the updated history
|
||||
* @private
|
||||
*/
|
||||
_storeHistory: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
if (undefined != me.iStoreHistoryTimeoutHandler)
|
||||
{
|
||||
clearTimeout(me.iStoreHistoryTimeoutHandler);
|
||||
}
|
||||
|
||||
me.iStoreHistoryTimeoutHandler = setTimeout(me._storeHistoryTimeoutFunction(me), me.options.history_backend_store_timeout);
|
||||
},
|
||||
|
||||
/**
|
||||
* should only be called by _storeHistory using a timeout
|
||||
* @private
|
||||
*/
|
||||
_storeHistoryTimeoutFunction: function(me)
|
||||
{
|
||||
SetUserPreference(me._getPreferenceCode(), JSON.stringify(me.getHistory()), true);
|
||||
|
||||
me.iStoreHistoryTimeoutHandler = undefined;
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
343
js/searchformforeignkeys.js
Normal file
343
js/searchformforeignkeys.js
Normal file
@@ -0,0 +1,343 @@
|
||||
// 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/>
|
||||
|
||||
/**
|
||||
*
|
||||
* @param id String the dom identifier of the source input
|
||||
* @param sTargetClass
|
||||
* @param sAttCode
|
||||
* @param oSearchWidgetElmt
|
||||
* @param sFilter
|
||||
* @param sTitle
|
||||
* @constructor
|
||||
*/
|
||||
function SearchFormForeignKeys(id, sTargetClass, sAttCode, oSearchWidgetElmt, sFilter, sTitle)
|
||||
{
|
||||
this.id = id;
|
||||
//this.sOriginalTargetClass = sTargetClass;
|
||||
this.sTargetClass = sTargetClass;
|
||||
this.sFilter = sFilter;
|
||||
this.sTitle = sTitle;
|
||||
this.sAttCode = sAttCode;
|
||||
this.oSearchWidgetElmt = oSearchWidgetElmt;
|
||||
this.emptyHtml = ''; // content to be displayed when the search results are empty (when opening the dialog)
|
||||
this.emptyOnClose = true; // Workaround for the JQuery dialog being very slow when opening and closing if the content contains many INPUT tags
|
||||
this.ajax_request = null;
|
||||
// this.bSelectMode = bSelectMode; // true if the edited field is a SELECT, false if it's an autocomplete
|
||||
// this.bSearchMode = bSearchMode; // true if selecting a value in the context of a search form
|
||||
var me = this;
|
||||
|
||||
this.Init = function()
|
||||
{
|
||||
// make sure that the form is clean
|
||||
$('#linkedset_'+this.id+' .selection').each( function() { this.checked = false; });
|
||||
$('#'+this.id+'_btnRemove').prop('disabled', false);
|
||||
|
||||
|
||||
$('<div id="dlg_'+me.id+'"></div>').appendTo(document.body);
|
||||
|
||||
// me.trace(dialog);
|
||||
|
||||
//TODO : check and remove all unneded code bellow this line!!
|
||||
|
||||
$('#'+this.id+'_linksToRemove').val('');
|
||||
|
||||
$('#linkedset_'+me.id).on('remove', function() {
|
||||
// prevent having the dlg div twice
|
||||
$('#dlg_'+me.id).remove();
|
||||
});
|
||||
|
||||
$('#'+this.iInputId).closest('form').submit(function() {
|
||||
return me.OnFormSubmit();
|
||||
});
|
||||
};
|
||||
|
||||
this.StopPendingRequest = function()
|
||||
{
|
||||
if (me.ajax_request)
|
||||
{
|
||||
me.ajax_request.abort();
|
||||
me.ajax_request = null;
|
||||
}
|
||||
};
|
||||
|
||||
this.ShowModalSearchForeignKeys = function()
|
||||
{
|
||||
// // Query the server to get the form to search for target objects
|
||||
// if (me.bSelectMode)
|
||||
// {
|
||||
// $('#fstatus_'+me.id).html('<img src="../images/indicator.gif" />');
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// $('#label_'+me.id).addClass('dlg_loading');
|
||||
// }
|
||||
$('#label_'+me.id).addClass('dlg_loading');
|
||||
var theMap = {
|
||||
sAttCode: me.sAttCode,
|
||||
iInputId: me.id,
|
||||
sTitle: me.sTitle,
|
||||
sTargetClass: me.sTargetClass,
|
||||
// bSearchMode: me.bSearchMode,
|
||||
operation: 'ShowModalSearchForeignKeys'
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Make sure that we cancel any pending request before issuing another
|
||||
// since responses may arrive in arbitrary order
|
||||
me.StopPendingRequest();
|
||||
|
||||
// Run the query and get the result back directly in HTML
|
||||
me.ajax_request = $.post( AddAppContext(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'), theMap,
|
||||
function(data)
|
||||
{
|
||||
// $('#dlg_'+me.id).html(data);
|
||||
$('#dlg_'+me.id).empty().append($(data)); // $(data).filter(':not(script)'));
|
||||
$('#dlg_'+me.id).dialog('open');
|
||||
me.UpdateSizes();
|
||||
me.UpdateButtons();
|
||||
me.ajax_request = null;
|
||||
FixSearchFormsDisposition();
|
||||
me.ListResultsSearchForeignKeys();
|
||||
},
|
||||
'html'
|
||||
);
|
||||
};
|
||||
|
||||
this.UpdateSizes = function()
|
||||
{
|
||||
var dlg = $('#dlg_'+me.id);
|
||||
// Adjust the dialog's size to fit into the screen
|
||||
if (dlg.width() > ($(window).width()-40))
|
||||
{
|
||||
dlg.width($(window).width()-40);
|
||||
}
|
||||
if (dlg.height() > ($(window).height()-70))
|
||||
{
|
||||
dlg.height($(window).height()-70);
|
||||
}
|
||||
var searchForm = dlg.find('div.display_block:first'); // Top search form, enclosing display_block
|
||||
var results = $('#SearchResultsToAdd_'+me.id);
|
||||
var oPadding = {};
|
||||
var aKeys = ['top', 'right', 'bottom', 'left'];
|
||||
for(k in aKeys)
|
||||
{
|
||||
oPadding[aKeys[k]] = 0;
|
||||
if (dlg.css('padding-'+aKeys[k]))
|
||||
{
|
||||
oPadding[aKeys[k]] = parseInt(dlg.css('padding-'+aKeys[k]).replace('px', ''));
|
||||
}
|
||||
}
|
||||
//var width = dlg.innerWidth() - oPadding['right'] - oPadding['left'] - 22; // 5 (margin-left) + 5 (padding-left) + 5 (padding-right) + 5 (margin-right) + 2 for rounding !
|
||||
var height = dlg.innerHeight()-oPadding['top']-oPadding['bottom']-22;
|
||||
var form_height = searchForm.outerHeight();
|
||||
results.height(height - form_height - 40); // Leave some space for the buttons
|
||||
};
|
||||
|
||||
this.UpdateButtons = function()
|
||||
{
|
||||
var okBtn = $('#btn_ok_'+me.id);
|
||||
if ($('#count_'+me.id).val() > 0)
|
||||
{
|
||||
okBtn.removeAttr('disabled');
|
||||
}
|
||||
else
|
||||
{
|
||||
okBtn.prop('disabled', 'disabled');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
this.ListResultsSearchForeignKeys = function ()
|
||||
{
|
||||
var theMap = {
|
||||
sTargetClass: me.sTargetClass,
|
||||
iInputId: me.id,
|
||||
sFilter: me.sFilter
|
||||
// bSearchMode: me.bSearchMode
|
||||
};
|
||||
|
||||
// Gather the parameters from the search form
|
||||
$('#fs_'+me.id+' :input').each( function() {
|
||||
if (this.name !== '')
|
||||
{
|
||||
var val = $(this).val(); // supports multiselect as well
|
||||
if (val !== null)
|
||||
{
|
||||
theMap[this.name] = val;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
theMap['sRemoteClass'] = theMap['class']; // swap 'class' (defined in the form) and 'remoteClass'
|
||||
theMap.operation = 'ListResultsSearchForeignKeys'; // Override what is defined in the form itself
|
||||
theMap.sAttCode = me.sAttCode;
|
||||
var sSearchAreaId = '#SearchResultsToAdd_'+me.id;
|
||||
//$(sSearchAreaId).html('<div style="text-align:center;width:100%;height:24px;vertical-align:middle;"><img src="../images/indicator.gif" /></div>');
|
||||
$(sSearchAreaId).block();
|
||||
me.UpdateButtons();
|
||||
|
||||
// Make sure that we cancel any pending request before issuing another
|
||||
// since responses may arrive in arbitrary order
|
||||
me.StopPendingRequest();
|
||||
|
||||
// Run the query and display the results
|
||||
me.ajax_request = $.post(AddAppContext(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'), theMap,
|
||||
function(data)
|
||||
{
|
||||
$(sSearchAreaId).html(data);
|
||||
$(sSearchAreaId+' .listResults').tableHover();
|
||||
$('#fr_'+me.id+' input:radio').click(function() { me.UpdateButtons(); });
|
||||
me.UpdateButtons();
|
||||
me.ajax_request = null;
|
||||
$('#count_'+me.id).change(function(){
|
||||
me.UpdateButtons();
|
||||
});
|
||||
me.UpdateSizes();
|
||||
},
|
||||
'html'
|
||||
);
|
||||
|
||||
return false; // Don't submit the form, stay in the current page !
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
this.DoAddObjects = function () {
|
||||
// Gather the parameters from the search form
|
||||
var theMap = {};
|
||||
var context = $('#SearchResultsToAdd_'+me.id);
|
||||
var selectionMode = $(':input[name="selectionMode"]', context);
|
||||
if (selectionMode.length > 0) {
|
||||
// Paginated table retrieve the mode and the exceptions
|
||||
theMap['selectionMode'] = selectionMode.val();
|
||||
$('#fs_SearchFormToAdd_'+me.id+' :input').each(function () {
|
||||
theMap[this.name] = this.value;
|
||||
});
|
||||
|
||||
$(':input[name="storedSelection[]"]', context).each(function () {
|
||||
if (typeof theMap[this.name] === "undefined") {
|
||||
theMap[this.name] = [];
|
||||
}
|
||||
theMap[this.name].push(this.value);
|
||||
$(this).remove(); // Remove the selection for the next time the dialog re-opens
|
||||
});
|
||||
}
|
||||
|
||||
// Normal table, retrieve all the checked check-boxes
|
||||
$(':checked[name="selectObject[]"]', context).each(
|
||||
function () {
|
||||
if ((this.name !== '') && ((this.type !== 'checkbox') || (this.checked))) {
|
||||
var arrayExpr = /\[\]$/;
|
||||
if (arrayExpr.test(this.name)) {
|
||||
// Array
|
||||
if (typeof theMap[this.name] === "undefined") {
|
||||
theMap[this.name] = [];
|
||||
}
|
||||
theMap[this.name].push(this.value);
|
||||
}
|
||||
else {
|
||||
theMap[this.name] = this.value;
|
||||
}
|
||||
}
|
||||
$(this).parents('tr:first').remove(); // Remove the whole line, so that, next time the dialog gets displayed it's no longer there
|
||||
}
|
||||
);
|
||||
theMap["class"] = me.sTargetClass;
|
||||
theMap['operation'] = 'GetFullListForeignKeysFromSelection';
|
||||
$('#busy_'+me.iInputId).html(' <img src="../images/indicator.gif"/>');
|
||||
// Run the query and display the results
|
||||
$.ajax(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', {
|
||||
"data": theMap,
|
||||
"method": "POST"
|
||||
})
|
||||
.done(function (data) {
|
||||
if (Object.keys(data).length > 0) {
|
||||
me.oSearchWidgetElmt.trigger("itop.search.criteria_enum.add_selected_values", data);
|
||||
}
|
||||
})
|
||||
.fail(function (data) {
|
||||
try {
|
||||
console.error(data);
|
||||
} catch (e) {
|
||||
}
|
||||
})
|
||||
;
|
||||
|
||||
$('#dlg_'+me.id).dialog('close');
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
// Workaround for a ui.jquery limitation: if the content of
|
||||
// the dialog contains many INPUTs, closing and opening the
|
||||
// dialog is very slow. So empty it each time.
|
||||
this.OnClose = function()
|
||||
{
|
||||
me.StopPendingRequest();
|
||||
// called by the dialog, so in the context 'this' points to the jQueryObject
|
||||
if (me.emptyOnClose)
|
||||
{
|
||||
$('#SearchResultsToAdd_'+me.id).html(me.emptyHtml);
|
||||
}
|
||||
$('#label_'+me.id).removeClass('dlg_loading');
|
||||
$('#label_'+me.id).focus();
|
||||
me.ajax_request = null;
|
||||
};
|
||||
|
||||
this.DoSelectObjectClass = function()
|
||||
{
|
||||
// Retrieving selected value
|
||||
var oSelectedClass = $('#ac_create_'+me.id+' select');
|
||||
if(oSelectedClass.length !== 1) return;
|
||||
|
||||
// Setting new target class
|
||||
me.sTargetClass = oSelectedClass.val();
|
||||
|
||||
// Opening real creation form
|
||||
$('#ac_create_'+me.id).dialog('close');
|
||||
me.CreateObject();
|
||||
};
|
||||
|
||||
this.Update = function()
|
||||
{
|
||||
if ($('#'+me.id).prop('disabled'))
|
||||
{
|
||||
$('#v_'+me.id).html('');
|
||||
$('#label_'+me.id).prop('disabled', 'disabled');
|
||||
$('#label_'+me.id).css({'background': 'transparent'});
|
||||
$('#mini_add_'+me.id).hide();
|
||||
$('#mini_tree_'+me.id).hide();
|
||||
$('#mini_search_'+me.id).hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
$('#label_'+me.id).removeAttr('disabled');
|
||||
$('#label_'+me.id).css({'background': '#fff url(../images/ac-background.gif) no-repeat right'});
|
||||
$('#mini_add_'+me.id).show();
|
||||
$('#mini_tree_'+me.id).show();
|
||||
$('#mini_search_'+me.id).show();
|
||||
}
|
||||
};
|
||||
}
|
||||
817
js/utils.js
817
js/utils.js
File diff suppressed because it is too large
Load Diff
@@ -130,7 +130,7 @@ function DisplayDetails($oP, $sClass, $oObj, $id)
|
||||
$sClassLabel = MetaModel::GetName($sClass);
|
||||
$oSearch = new DBObjectSearch($sClass);
|
||||
$oBlock = new DisplayBlock($oSearch, 'search', false);
|
||||
$oBlock->Display($oP, 0);
|
||||
$oBlock->Display($oP, 0, array('table_id' => 'search-widget-results-outer'));
|
||||
|
||||
// The object could be listed, check if it is actually allowed to view it
|
||||
$oSet = CMDBObjectSet::FromObject($oObj);
|
||||
@@ -198,7 +198,7 @@ function DisplaySearchSet($oP, $oFilter, $bSearchForm = true, $sBaseClass = '',
|
||||
{
|
||||
if ($bSearchForm)
|
||||
{
|
||||
$aParams = array('open' => $bSearchFormOpen);
|
||||
$aParams = array('open' => $bSearchFormOpen, 'table_id' => '1');
|
||||
if (!empty($sBaseClass))
|
||||
{
|
||||
$aParams['baseClass'] = $sBaseClass;
|
||||
@@ -484,9 +484,9 @@ try
|
||||
if ($bSearchForm)
|
||||
{
|
||||
$oBlock = new DisplayBlock($oFilter, 'search', false);
|
||||
$oBlock->Display($oP, 0);
|
||||
$oBlock->Display($oP, 0, array('table_id' => 'search-widget-result-outer'));
|
||||
}
|
||||
$oP->P('<b>'.Dict::Format('UI:Error:IncorrectOQLQuery_Message', $e->getHtmlDesc()).'</b>');
|
||||
$oP->add('<div id="search-widget-result-outer"><p><b>'.Dict::Format('UI:Error:IncorrectOQLQuery_Message', $e->getHtmlDesc()).'</b></p></div>');
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
|
||||
@@ -110,6 +110,7 @@ if ($oFilter != null)
|
||||
$aExtraParams['open'] = true;
|
||||
$aExtraParams['baseClass'] = $sBaseClass;
|
||||
$aExtraParams['action'] = utils::GetAbsoluteUrlAppRoot().'pages/UniversalSearch.php';
|
||||
$aExtraParams['table_id'] = '1';
|
||||
//$aExtraParams['class'] = $sClassName;
|
||||
$oBlock->Display($oP, 0, $aExtraParams);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
139
pages/ajax.searchform.php
Normal file
139
pages/ajax.searchform.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?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');
|
||||
require_once(APPROOT.'/application/wizardhelper.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 = 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');
|
||||
|
||||
$sListParams = utils::ReadParam('list_params', '{}', false, 'raw_data');
|
||||
$aListParams = (array)json_decode($sListParams, true);
|
||||
|
||||
$aParams = json_decode($sParams, true);
|
||||
if (array_key_exists('hidden_criteria', $aListParams))
|
||||
{
|
||||
$sHiddenCriteria = $aListParams['hidden_criteria'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$sHiddenCriteria = '';
|
||||
}
|
||||
$oFilter = CriterionParser::Parse($aParams['base_oql'], $aParams['criterion'], $sHiddenCriteria);
|
||||
|
||||
if (isset($aListParams['debug']))
|
||||
{
|
||||
$sOQL = $oFilter->ToOQL();
|
||||
$oPage->add("<div class=\"header_message message_info\">$sOQL</div>\n");
|
||||
}
|
||||
|
||||
//IssueLog::Info('Search OQL: "'.$oFilter->ToOQL().'"');
|
||||
$oDisplayBlock = new DisplayBlock($oFilter, 'list', false);
|
||||
|
||||
foreach($aListParams as $key => $value)
|
||||
{
|
||||
$aExtraParams[$key] = $value;
|
||||
}
|
||||
|
||||
if (array_key_exists('table_inner_id', $aListParams))
|
||||
{
|
||||
$sListId = $aListParams['table_inner_id'];
|
||||
}
|
||||
|
||||
if (array_key_exists('json', $aListParams))
|
||||
{
|
||||
$aJson = $aListParams['json'];
|
||||
$sJson = json_encode($aJson);
|
||||
$oWizardHelper = WizardHelper::FromJSON($sJson);
|
||||
$oObj = $oWizardHelper->GetTargetObject();
|
||||
if (array_key_exists('query_params', $aExtraParams))
|
||||
{
|
||||
$aExtraParams['query_params']['this'] = $oObj;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aExtraParams['query_params'] = array('this' => $oObj);
|
||||
}
|
||||
|
||||
// // Current extkey value, so we can display event if it is not available anymore (eg. archived).
|
||||
// $iCurrentExtKeyId = (is_null($oObj)) ? 0 : $oObj->Get($this->sAttCode);
|
||||
// $aExtraParams['current_extkey_id'] = $iCurrentExtKeyId;
|
||||
|
||||
}
|
||||
|
||||
$aExtraParams['display_limit'] = true;
|
||||
$aExtraParams['truncated'] = true;
|
||||
if (isset($sListId))
|
||||
{
|
||||
$oDisplayBlock->Display($oPage, $sListId, $aExtraParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oDisplayBlock->RenderContent($oPage, $aExtraParams);
|
||||
}
|
||||
|
||||
|
||||
$oPage->output();
|
||||
|
||||
} catch (AjaxSearchException $e)
|
||||
{
|
||||
http_response_code($e->getCode());
|
||||
// note: transform to cope with XSS attacks
|
||||
echo '<html><head></head><body><div>' . htmlentities($e->GetMessage(), ENT_QUOTES, 'utf-8') . '</div></body></html>';
|
||||
IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString());
|
||||
} catch (SecurityException $e)
|
||||
{
|
||||
http_response_code(403);
|
||||
// note: transform to cope with XSS attacks
|
||||
echo '<html><head></head><body><div>' . htmlentities($e->GetMessage(), ENT_QUOTES, 'utf-8') . '</div></body></html>';
|
||||
IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString());
|
||||
} catch (Exception $e)
|
||||
{
|
||||
http_response_code(500);
|
||||
// note: transform to cope with XSS attacks
|
||||
echo '<html><head></head><body><div>' . htmlentities($e->GetMessage(), ENT_QUOTES, 'utf-8') . '</div></body></html>';
|
||||
IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString());
|
||||
}
|
||||
35
sources/application/search/ajaxsearchexception.class.inc.php
Normal file
35
sources/application/search/ajaxsearchexception.class.inc.php
Normal 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
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
<?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\CriterionConversion;
|
||||
|
||||
|
||||
use AttributeDate;
|
||||
use AttributeDateTime;
|
||||
use AttributeDefinition;
|
||||
use AttributeEnum;
|
||||
use Combodo\iTop\Application\Search\AjaxSearchException;
|
||||
use Combodo\iTop\Application\Search\CriterionConversionAbstract;
|
||||
use Combodo\iTop\Application\Search\SearchForm;
|
||||
|
||||
class CriterionToOQL extends CriterionConversionAbstract
|
||||
{
|
||||
|
||||
public static function Convert($aCriteria)
|
||||
{
|
||||
if (!empty($aCriteria['oql']))
|
||||
{
|
||||
return $aCriteria['oql'];
|
||||
}
|
||||
|
||||
$aRef = explode('.', $aCriteria['ref']);
|
||||
for($i = 0; $i < count($aRef); $i++)
|
||||
{
|
||||
$aRef[$i] = '`'.$aRef[$i].'`';
|
||||
}
|
||||
$sRef = implode('.', $aRef);
|
||||
|
||||
$sOperator = $aCriteria['operator'];
|
||||
|
||||
$aMappedOperators = array(
|
||||
self::OP_CONTAINS => 'ContainsToOql',
|
||||
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_IN => 'InToOql',
|
||||
self::OP_ALL => 'AllToOql',
|
||||
);
|
||||
|
||||
if (array_key_exists($sOperator, $aMappedOperators))
|
||||
{
|
||||
$sFct = $aMappedOperators[$sOperator];
|
||||
|
||||
return self::$sFct($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 array();
|
||||
}
|
||||
|
||||
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 $aValues[$iIndex]['value'];
|
||||
}
|
||||
|
||||
protected static function ContainsToOql($sRef, $aCriteria)
|
||||
{
|
||||
$aValues = self::GetValues($aCriteria);
|
||||
$sValue = self::GetValue($aValues, 0);
|
||||
|
||||
return "({$sRef} LIKE '%{$sValue}%')";
|
||||
}
|
||||
|
||||
protected static function StartsWithToOql($sRef, $aCriteria)
|
||||
{
|
||||
$aValues = self::GetValues($aCriteria);
|
||||
$sValue = self::GetValue($aValues, 0);
|
||||
|
||||
return "({$sRef} LIKE '{$sValue}%')";
|
||||
}
|
||||
|
||||
protected static function EndsWithToOql($sRef, $aCriteria)
|
||||
{
|
||||
$aValues = self::GetValues($aCriteria);
|
||||
$sValue = self::GetValue($aValues, 0);
|
||||
|
||||
return "({$sRef} LIKE '%{$sValue}')";
|
||||
}
|
||||
|
||||
protected static function EmptyToOql($sRef, $aCriteria)
|
||||
{
|
||||
if (isset($aCriteria['widget']))
|
||||
{
|
||||
switch ($aCriteria['widget'])
|
||||
{
|
||||
case AttributeDefinition::SEARCH_WIDGET_TYPE_NUMERIC:
|
||||
case AttributeDefinition::SEARCH_WIDGET_TYPE_EXTERNAL_FIELD:
|
||||
return "ISNULL({$sRef})";
|
||||
}
|
||||
}
|
||||
|
||||
return "({$sRef} = '')";
|
||||
}
|
||||
|
||||
protected static function NotEmptyToOql($sRef, $aCriteria)
|
||||
{
|
||||
return "({$sRef} != '')";
|
||||
}
|
||||
|
||||
protected static function InToOql($sRef, $aCriteria)
|
||||
{
|
||||
$sAttCode = $aCriteria['code'];
|
||||
$sClass = $aCriteria['class'];
|
||||
$aValues = self::GetValues($aCriteria);
|
||||
|
||||
if (count($aValues) == 0)
|
||||
{
|
||||
// Ignore when nothing is selected
|
||||
return "1";
|
||||
}
|
||||
|
||||
$bFilterOnUndefined = false;
|
||||
try
|
||||
{
|
||||
$aAttributeDefs = \MetaModel::ListAttributeDefs($sClass);
|
||||
if (array_key_exists($sAttCode, $aAttributeDefs))
|
||||
{
|
||||
$oAttDef = $aAttributeDefs[$sAttCode];
|
||||
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'))
|
||||
{
|
||||
$bFilterOnUndefined = true;
|
||||
unset($aValues[$i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\CoreException $e)
|
||||
{
|
||||
}
|
||||
|
||||
$aInValues = array();
|
||||
foreach($aValues as $aValue)
|
||||
{
|
||||
$aInValues[] = $aValue['value'];
|
||||
}
|
||||
$sInList = implode("','", $aInValues);
|
||||
|
||||
if ($bFilterOnUndefined)
|
||||
{
|
||||
$sFilterOnUndefined = "ISNULL({$sRef})";
|
||||
if (count($aValues) === 0)
|
||||
{
|
||||
return $sFilterOnUndefined;
|
||||
}
|
||||
|
||||
if (count($aInValues) == 1)
|
||||
{
|
||||
// Add 'AND 1' to group the 'OR' inside an AND list for OQL parsing
|
||||
return "((({$sRef} = '$sInList') OR {$sFilterOnUndefined}) AND 1)";
|
||||
}
|
||||
|
||||
// Add 'AND 1' to group the 'OR' inside an AND list for OQL parsing
|
||||
return "(({$sRef} IN ('$sInList') OR {$sFilterOnUndefined}) AND 1)";
|
||||
}
|
||||
|
||||
if (count($aInValues) == 1)
|
||||
{
|
||||
return "({$sRef} = '$sInList')";
|
||||
}
|
||||
|
||||
return "({$sRef} IN ('$sInList'))";
|
||||
}
|
||||
|
||||
protected static function BetweenDatesToOql($sRef, $aCriteria)
|
||||
{
|
||||
$aOQL = array();
|
||||
|
||||
$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))
|
||||
{
|
||||
$oDate = $oFormat->parse($sStartDate);
|
||||
$sStartDate = $oDate->format($sAttributeClass::GetSQLFormat());
|
||||
$aOQL[] = "({$sRef} >= '$sStartDate')";
|
||||
}
|
||||
|
||||
$sEndDate = $aValues[1]['value'];
|
||||
if (!empty($sEndDate))
|
||||
{
|
||||
$oDate = $oFormat->parse($sEndDate);
|
||||
$sEndDate = $oDate->format($sAttributeClass::GetSQLFormat());
|
||||
$aOQL[] = "({$sRef} <= '$sEndDate')";
|
||||
}
|
||||
|
||||
$sOQL = implode(' AND ', $aOQL);
|
||||
|
||||
if (empty($sOQL))
|
||||
{
|
||||
$sOQL = "1";
|
||||
}
|
||||
|
||||
return $sOQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sRef
|
||||
* @param $aCriteria
|
||||
*
|
||||
* @return string
|
||||
* @throws \Combodo\iTop\Application\Search\AjaxSearchException
|
||||
*/
|
||||
protected static function BetweenToOql($sRef, $aCriteria)
|
||||
{
|
||||
$aOQL = array();
|
||||
|
||||
$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($sRef, $aCriteria)
|
||||
{
|
||||
return "1";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,644 @@
|
||||
<?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/>
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convert structures from OQL expressions into structure for the search form
|
||||
*/
|
||||
namespace Combodo\iTop\Application\Search\CriterionConversion;
|
||||
|
||||
|
||||
use AttributeDate;
|
||||
use AttributeDateTime;
|
||||
use AttributeDefinition;
|
||||
use Combodo\iTop\Application\Search\CriterionConversionAbstract;
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use Dict;
|
||||
use Exception;
|
||||
use MetaModel;
|
||||
|
||||
class CriterionToSearchForm extends CriterionConversionAbstract
|
||||
{
|
||||
|
||||
/**
|
||||
* @param array $aAndCriterionRaw
|
||||
* @param array $aFieldsByCategory
|
||||
*
|
||||
* @param array $aClasses all the classes of the filter
|
||||
*
|
||||
* @param bool $bIsRemovable
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function Convert($aAndCriterionRaw, $aFieldsByCategory, $aClasses, $bIsRemovable = true)
|
||||
{
|
||||
$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(
|
||||
AttributeDefinition::SEARCH_WIDGET_TYPE_STRING => 'TextToSearchForm',
|
||||
AttributeDefinition::SEARCH_WIDGET_TYPE_EXTERNAL_FIELD => 'ExternalFieldToSearchForm',
|
||||
AttributeDefinition::SEARCH_WIDGET_TYPE_DATE => 'DateTimeToSearchForm',
|
||||
AttributeDefinition::SEARCH_WIDGET_TYPE_DATE_TIME => 'DateTimeToSearchForm',
|
||||
AttributeDefinition::SEARCH_WIDGET_TYPE_NUMERIC => 'NumericToSearchForm',
|
||||
AttributeDefinition::SEARCH_WIDGET_TYPE_EXTERNAL_KEY => 'ExternalKeyToSearchForm',
|
||||
AttributeDefinition::SEARCH_WIDGET_TYPE_HIERARCHICAL_KEY => 'ExternalKeyToSearchForm',
|
||||
AttributeDefinition::SEARCH_WIDGET_TYPE_ENUM => 'EnumToSearchForm',
|
||||
);
|
||||
|
||||
foreach($aAndCriterionRaw as $aCriteria)
|
||||
{
|
||||
if (isset($aCriteria['label']))
|
||||
{
|
||||
$aCriteria['label'] = preg_replace("@\)$@", '', $aCriteria['label']);
|
||||
$aCriteria['label'] = preg_replace("@^\(@", '', $aCriteria['label']);
|
||||
}
|
||||
$aCriteria['is_removable'] = $bIsRemovable;
|
||||
|
||||
$sClass = '';
|
||||
if (isset($aCriteria['ref']))
|
||||
{
|
||||
$aRef = explode('.', $aCriteria['ref']);
|
||||
if (isset($aClasses[$aRef[0]]))
|
||||
{
|
||||
$sClass = $aClasses[$aRef[0]];
|
||||
$aCriteria['class'] = $sClass;
|
||||
}
|
||||
}
|
||||
|
||||
// Check criteria validity
|
||||
if (!isset($aCriteria['ref']) || !isset($aAllFields[$aCriteria['ref']]))
|
||||
{
|
||||
$aCriteria['widget'] = AttributeDefinition::SEARCH_WIDGET_TYPE_RAW;
|
||||
$aCriteria['label'] = Dict::S('UI:Search:Criteria:Raw:Filtered');
|
||||
if (isset($aCriteria['ref']))
|
||||
{
|
||||
try
|
||||
{
|
||||
$aCriteria['label'] = Dict::Format('UI:Search:Criteria:Raw:FilteredOn', MetaModel::GetName($sClass));
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
if (array_key_exists('widget', $aCriteria))
|
||||
{
|
||||
if (array_key_exists($aCriteria['widget'], $aMappingOperatorToFunction))
|
||||
{
|
||||
$sFct = $aMappingOperatorToFunction[$aCriteria['widget']];
|
||||
$aAndCriterion[] = self::$sFct($aCriteria, $aAllFields);
|
||||
}
|
||||
else
|
||||
{
|
||||
$aAndCriterion[] = $aCriteria;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Regroup criterion by variable name (no ref first)
|
||||
usort($aAndCriterion, function ($a, $b) {
|
||||
if (array_key_exists('ref', $a) || array_key_exists('ref', $b))
|
||||
{
|
||||
if (array_key_exists('ref', $a) && array_key_exists('ref', $b))
|
||||
{
|
||||
$iRefCmp = strcmp($a['ref'], $b['ref']);
|
||||
if ($iRefCmp != 0) return $iRefCmp;
|
||||
|
||||
return strcmp($a['operator'], $b['operator']);
|
||||
}
|
||||
if (array_key_exists('ref', $a))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
if (array_key_exists('oql', $a) && array_key_exists('oql', $b))
|
||||
{
|
||||
return strcmp($a['oql'], $b['oql']);
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
$aMergeFctByWidget = array(
|
||||
AttributeDefinition::SEARCH_WIDGET_TYPE_DATE => 'MergeDate',
|
||||
AttributeDefinition::SEARCH_WIDGET_TYPE_DATE_TIME => 'MergeDateTime',
|
||||
AttributeDefinition::SEARCH_WIDGET_TYPE_NUMERIC => 'MergeNumeric',
|
||||
AttributeDefinition::SEARCH_WIDGET_TYPE_ENUM => 'MergeEnumExtKeys',
|
||||
AttributeDefinition::SEARCH_WIDGET_TYPE_EXTERNAL_KEY => 'MergeEnumExtKeys',
|
||||
);
|
||||
|
||||
$aPrevCriterion = null;
|
||||
$aMergedCriterion = array();
|
||||
foreach($aAndCriterion as $aCurrCriterion)
|
||||
{
|
||||
if (!is_null($aPrevCriterion))
|
||||
{
|
||||
if (array_key_exists('ref', $aPrevCriterion))
|
||||
{
|
||||
// If previous has ref, the current has ref as the array is sorted with all without ref first
|
||||
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;
|
||||
}
|
||||
|
||||
// Sort by label criterion by variable name (no ref first)
|
||||
usort($aMergedCriterion, function ($a, $b) {
|
||||
if (($a['widget'] === AttributeDefinition::SEARCH_WIDGET_TYPE_RAW) ||
|
||||
($b['widget'] === AttributeDefinition::SEARCH_WIDGET_TYPE_RAW))
|
||||
{
|
||||
if (($a['widget'] === AttributeDefinition::SEARCH_WIDGET_TYPE_RAW) &&
|
||||
($b['widget'] === AttributeDefinition::SEARCH_WIDGET_TYPE_RAW))
|
||||
{
|
||||
return strcmp($a['label'], $b['label']);
|
||||
}
|
||||
if ($a['widget'] === AttributeDefinition::SEARCH_WIDGET_TYPE_RAW)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return strcmp($a['label'], $b['label']);
|
||||
});
|
||||
|
||||
return $aMergedCriterion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $aPrevCriterion
|
||||
* @param $aCurrCriterion
|
||||
* @param $aMergedCriterion
|
||||
*
|
||||
* @return Current criteria or null if merged
|
||||
* @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_dates';
|
||||
$oFormat = AttributeDate::GetFormat();
|
||||
$sLastDate = $aPrevCriterion['values'][0]['value'];
|
||||
$oDate = new DateTime($sLastDate);
|
||||
if ($sPrevOperator == '<')
|
||||
{
|
||||
// previous day to include ends
|
||||
$oDate->sub(DateInterval::createFromDateString('1 day'));
|
||||
}
|
||||
$sLastDateValue = $oDate->format(AttributeDate::GetSQLFormat());
|
||||
$sLastDateLabel = $oFormat->format($oDate);
|
||||
|
||||
$sFirstDate = $aCurrCriterion['values'][0]['value'];
|
||||
$oDate = new DateTime($sFirstDate);
|
||||
if ($sCurrOperator == '>')
|
||||
{
|
||||
// next day to include ends
|
||||
$oDate->add(DateInterval::createFromDateString('1 day'));
|
||||
}
|
||||
$sFirstDateValue = $oDate->format(AttributeDate::GetSQLFormat());
|
||||
$sFirstDateLabel = $oFormat->format($oDate);
|
||||
|
||||
$aCurrCriterion['values'] = array();
|
||||
$aCurrCriterion['values'][] = array('value' => $sFirstDateValue, 'label' => $sFirstDateLabel);
|
||||
$aCurrCriterion['values'][] = array('value' => $sLastDateValue, 'label' => $sLastDateLabel);
|
||||
|
||||
$aCurrCriterion['oql'] = "({$aPrevCriterion['oql']} AND {$aCurrCriterion['oql']})";
|
||||
$aCurrCriterion['label'] = $aPrevCriterion['label'].' '.Dict::S('Expression:Operator:AND', 'AND').' '.$aCurrCriterion['label'];
|
||||
|
||||
$aMergedCriterion[] = $aCurrCriterion;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $aPrevCriterion
|
||||
* @param $aCurrCriterion
|
||||
* @param $aMergedCriterion
|
||||
*
|
||||
* @return Current criteria or null if merged
|
||||
* @throws \Exception
|
||||
*/
|
||||
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);
|
||||
$aCurrCriterion['operator'] = 'between_dates';
|
||||
$sInterval = '1 second';
|
||||
|
||||
if ($sPrevOperator == '<')
|
||||
{
|
||||
// previous day/second to include ends
|
||||
$oDate->sub(DateInterval::createFromDateString($sInterval));
|
||||
}
|
||||
$sLastDateValue = $oDate->format(AttributeDateTime::GetSQLFormat());
|
||||
$sLastDateLabel = AttributeDateTime::GetFormat()->Format($sLastDateValue);
|
||||
|
||||
$oDate = new DateTime($sFirstDate);
|
||||
if ($sCurrOperator == '>')
|
||||
{
|
||||
// next day/second to include ends
|
||||
$oDate->add(DateInterval::createFromDateString($sInterval));
|
||||
}
|
||||
$sFirstDateValue = $oDate->format(AttributeDateTime::GetSQLFormat());
|
||||
$sFirstDateLabel = AttributeDateTime::GetFormat()->Format($sFirstDateValue);
|
||||
|
||||
$aCurrCriterion['values'] = array();
|
||||
$aCurrCriterion['values'][] = array('value' => $sFirstDateValue, 'label' => $sFirstDateLabel);
|
||||
$aCurrCriterion['values'][] = array('value' => $sLastDateValue, 'label' => $sLastDateLabel);
|
||||
|
||||
$aCurrCriterion['oql'] = "({$aPrevCriterion['oql']} AND {$aCurrCriterion['oql']})";
|
||||
$aCurrCriterion['label'] = $aPrevCriterion['label'].' '.Dict::S('Expression:Operator:AND',
|
||||
'AND').' '.$aCurrCriterion['label'];
|
||||
|
||||
$aMergedCriterion[] = $aCurrCriterion;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $aPrevCriterion
|
||||
* @param $aCurrCriterion
|
||||
* @param $aMergedCriterion
|
||||
*
|
||||
* @return Current criteria or null if merged
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected static function MergeNumeric($aPrevCriterion, $aCurrCriterion, &$aMergedCriterion)
|
||||
{
|
||||
$sPrevOperator = $aPrevCriterion['operator'];
|
||||
$sCurrOperator = $aCurrCriterion['operator'];
|
||||
if (($sPrevOperator != '<=') || ($sCurrOperator != '>='))
|
||||
{
|
||||
$aMergedCriterion[] = $aPrevCriterion;
|
||||
|
||||
return $aCurrCriterion;
|
||||
}
|
||||
|
||||
// Merge into 'between' operation.
|
||||
$sLastNum = $aPrevCriterion['values'][0]['value'];
|
||||
$sFirstNum = $aCurrCriterion['values'][0]['value'];
|
||||
$aCurrCriterion['values'] = array();
|
||||
$aCurrCriterion['values'][] = array('value' => $sFirstNum, 'label' => "$sFirstNum");
|
||||
$aCurrCriterion['values'][] = array('value' => $sLastNum, 'label' => "$sLastNum");
|
||||
|
||||
$aCurrCriterion['oql'] = "({$aPrevCriterion['oql']} AND {$aCurrCriterion['oql']})";
|
||||
$aCurrCriterion['label'] = $aPrevCriterion['label'].' '.Dict::S('Expression:Operator:AND', 'AND').' '.$aCurrCriterion['label'];
|
||||
$aCurrCriterion['operator'] = 'between';
|
||||
|
||||
$aMergedCriterion[] = $aCurrCriterion;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function SerializeValues($aValues)
|
||||
{
|
||||
$aSerializedValues = array();
|
||||
foreach($aValues as $aValue)
|
||||
{
|
||||
$aSerializedValues[] = serialize($aValue);
|
||||
}
|
||||
|
||||
return $aSerializedValues;
|
||||
}
|
||||
|
||||
protected static function MergeEnumExtKeys($aPrevCriterion, $aCurrCriterion, &$aMergedCriterion)
|
||||
{
|
||||
$aFirstValues = self::SerializeValues($aPrevCriterion['values']);
|
||||
$aNextValues = self::SerializeValues($aCurrCriterion['values']);
|
||||
|
||||
// Keep only the common values
|
||||
$aCurrCriterion['values'] = array_map("unserialize", array_intersect($aFirstValues, $aNextValues));
|
||||
|
||||
$aMergedCriterion[] = $aCurrCriterion;
|
||||
return null;
|
||||
}
|
||||
|
||||
protected static function TextToSearchForm($aCriteria, $aFields)
|
||||
{
|
||||
$sOperator = $aCriteria['operator'];
|
||||
$sValue = $aCriteria['values'][0]['value'];
|
||||
|
||||
$bStartWithPercent = substr($sValue, 0, 1) == '%' ? true : false;
|
||||
$bEndWithPercent = substr($sValue, -1) == '%' ? true : false;
|
||||
|
||||
switch (true)
|
||||
{
|
||||
case ('' == $sValue and ($sOperator == '=' or $sOperator == 'LIKE')):
|
||||
$aCriteria['operator'] = CriterionConversionAbstract::OP_EMPTY;
|
||||
break;
|
||||
case ('' == $sValue and $sOperator == '!='):
|
||||
$aCriteria['operator'] = CriterionConversionAbstract::OP_NOT_EMPTY;
|
||||
break;
|
||||
case ($sOperator == 'LIKE' && $bStartWithPercent && $bEndWithPercent):
|
||||
$aCriteria['operator'] = CriterionConversionAbstract::OP_CONTAINS;
|
||||
$sValue = substr($sValue, 1, -1);
|
||||
$aCriteria['values'][0]['value'] = $sValue;
|
||||
$aCriteria['values'][0]['label'] = "$sValue";
|
||||
break;
|
||||
case ($sOperator == 'LIKE' && $bStartWithPercent):
|
||||
$aCriteria['operator'] = CriterionConversionAbstract::OP_ENDS_WITH;
|
||||
$sValue = substr($sValue, 1);
|
||||
$aCriteria['values'][0]['value'] = $sValue;
|
||||
$aCriteria['values'][0]['label'] = "$sValue";
|
||||
break;
|
||||
case ($sOperator == 'LIKE' && $bEndWithPercent):
|
||||
$aCriteria['operator'] = CriterionConversionAbstract::OP_STARTS_WITH;
|
||||
$sValue = substr($sValue, 0, -1);
|
||||
$aCriteria['values'][0]['value'] = $sValue;
|
||||
$aCriteria['values'][0]['label'] = "$sValue";
|
||||
break;
|
||||
}
|
||||
|
||||
return $aCriteria;
|
||||
}
|
||||
|
||||
protected static function ExternalFieldToSearchForm($aCriteria, $aFields)
|
||||
{
|
||||
$sOperator = $aCriteria['operator'];
|
||||
$sValue = $aCriteria['values'][0]['value'];
|
||||
|
||||
$bStartWithPercent = substr($sValue, 0, 1) == '%' ? true : false;
|
||||
$bEndWithPercent = substr($sValue, -1) == '%' ? true : false;
|
||||
|
||||
switch (true)
|
||||
{
|
||||
case ($sOperator == 'ISNULL'):
|
||||
case ('' == $sValue and ($sOperator == 'LIKE')):
|
||||
$aCriteria['operator'] = CriterionConversionAbstract::OP_EMPTY;
|
||||
break;
|
||||
case ('' == $sValue and $sOperator == '!='):
|
||||
$aCriteria['operator'] = CriterionConversionAbstract::OP_NOT_EMPTY;
|
||||
break;
|
||||
case ($sOperator == 'LIKE' && $bStartWithPercent && $bEndWithPercent):
|
||||
$aCriteria['operator'] = CriterionConversionAbstract::OP_CONTAINS;
|
||||
$sValue = substr($sValue, 1, -1);
|
||||
$aCriteria['values'][0]['value'] = $sValue;
|
||||
$aCriteria['values'][0]['label'] = "$sValue";
|
||||
break;
|
||||
case ($sOperator == 'LIKE' && $bStartWithPercent):
|
||||
$aCriteria['operator'] = CriterionConversionAbstract::OP_ENDS_WITH;
|
||||
$sValue = substr($sValue, 1);
|
||||
$aCriteria['values'][0]['value'] = $sValue;
|
||||
$aCriteria['values'][0]['label'] = "$sValue";
|
||||
break;
|
||||
case ($sOperator == 'LIKE' && $bEndWithPercent):
|
||||
$aCriteria['operator'] = CriterionConversionAbstract::OP_STARTS_WITH;
|
||||
$sValue = substr($sValue, 0, -1);
|
||||
$aCriteria['values'][0]['value'] = $sValue;
|
||||
$aCriteria['values'][0]['label'] = "$sValue";
|
||||
break;
|
||||
}
|
||||
|
||||
return $aCriteria;
|
||||
}
|
||||
|
||||
protected static function DateTimeToSearchForm($aCriteria, $aFields)
|
||||
{
|
||||
if (!array_key_exists('is_relative', $aCriteria) || !$aCriteria['is_relative'])
|
||||
{
|
||||
// Convert '=' in 'between'
|
||||
if (isset($aCriteria['operator']) && ($aCriteria['operator'] === '='))
|
||||
{
|
||||
$aCriteria['operator'] = CriterionConversionAbstract::OP_BETWEEN_DATES;
|
||||
$sWidget = $aCriteria['widget'];
|
||||
if ($sWidget == AttributeDefinition::SEARCH_WIDGET_TYPE_DATE)
|
||||
{
|
||||
$aCriteria['values'][1] = $aCriteria['values'][0];
|
||||
}
|
||||
else
|
||||
{
|
||||
$sDate = $aCriteria['values'][0]['value'];
|
||||
$oDate = new DateTime($sDate);
|
||||
|
||||
$sFirstDateValue = $oDate->format(AttributeDateTime::GetSQLFormat());
|
||||
$sFirstDateLabel = AttributeDateTime::GetFormat()->Format($sFirstDateValue);
|
||||
$aCriteria['values'][0] = array('value' => $sFirstDateValue, 'label' => "$sFirstDateLabel");
|
||||
|
||||
|
||||
$oDate->add(DateInterval::createFromDateString('1 day'));
|
||||
$oDate->sub(DateInterval::createFromDateString('1 second'));
|
||||
|
||||
$sLastDateValue = $oDate->format(AttributeDateTime::GetSQLFormat());
|
||||
$sLastDateLabel = AttributeDateTime::GetFormat()->Format($sLastDateValue);
|
||||
$aCriteria['values'][1] = array('value' => $sLastDateValue, 'label' => "$sLastDateLabel");
|
||||
}
|
||||
}
|
||||
|
||||
return $aCriteria;
|
||||
}
|
||||
|
||||
if (isset($aCriteria['values'][0]['value']))
|
||||
{
|
||||
$sLabel = $aCriteria['values'][0]['value'];
|
||||
if (isset($aCriteria['verb']))
|
||||
{
|
||||
switch ($aCriteria['verb'])
|
||||
{
|
||||
case 'DATE_SUB':
|
||||
$sLabel = '-'.$sLabel;
|
||||
break;
|
||||
case 'DATE_ADD':
|
||||
$sLabel = '+'.$sLabel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isset($aCriteria['unit']))
|
||||
{
|
||||
$sLabel .= Dict::S('Expression:Unit:Short:'.$aCriteria['unit'], $aCriteria['unit']);
|
||||
}
|
||||
$aCriteria['values'][0]['label'] = "$sLabel";
|
||||
}
|
||||
|
||||
// Temporary until the JS widget support relative dates
|
||||
$aCriteria['widget'] = AttributeDefinition::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
return $aCriteria;
|
||||
}
|
||||
|
||||
protected static function NumericToSearchForm($aCriteria, $aFields)
|
||||
{
|
||||
if ($aCriteria['operator'] == 'ISNULL')
|
||||
{
|
||||
$aCriteria['operator'] = CriterionConversionAbstract::OP_EMPTY;
|
||||
}
|
||||
|
||||
return $aCriteria;
|
||||
}
|
||||
|
||||
protected static function EnumToSearchForm($aCriteria, $aFields)
|
||||
{
|
||||
$sOperator = $aCriteria['operator'];
|
||||
switch ($sOperator)
|
||||
{
|
||||
case '=':
|
||||
// Same as IN
|
||||
$aCriteria['operator'] = CriterionConversionAbstract::OP_IN;
|
||||
break;
|
||||
case 'NOT IN':
|
||||
case 'NOTIN':
|
||||
case '!=':
|
||||
// Same as NOT IN
|
||||
$aCriteria = self::RevertValues($aCriteria, $aFields);
|
||||
break;
|
||||
case 'IN':
|
||||
// Nothing special to do
|
||||
break;
|
||||
case 'OR':
|
||||
case 'ISNULL':
|
||||
// Special case when undefined and/or other values are selected
|
||||
$aCriteria['operator'] = CriterionConversionAbstract::OP_IN;
|
||||
if (isset($aCriteria['has_undefined']) && $aCriteria['has_undefined'])
|
||||
{
|
||||
if (!isset($aCriteria['values']))
|
||||
{
|
||||
$aCriteria['values'] = array();
|
||||
}
|
||||
// Convention for 'undefined' enums
|
||||
$aCriteria['values'][] = array('value' => 'null', 'label' => 'null');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Unknown operator
|
||||
$aCriteria['widget'] = AttributeDefinition::SEARCH_WIDGET_TYPE_RAW;
|
||||
break;
|
||||
}
|
||||
|
||||
return $aCriteria;
|
||||
}
|
||||
|
||||
protected static function ExternalKeyToSearchForm($aCriteria, $aFields)
|
||||
{
|
||||
$sOperator = $aCriteria['operator'];
|
||||
switch ($sOperator)
|
||||
{
|
||||
case '=':
|
||||
// Same as IN
|
||||
$aCriteria['operator'] = CriterionConversionAbstract::OP_IN;
|
||||
break;
|
||||
case 'IN':
|
||||
// Nothing special to do
|
||||
break;
|
||||
default:
|
||||
// Unknown operator
|
||||
$aCriteria['widget'] = AttributeDefinition::SEARCH_WIDGET_TYPE_RAW;
|
||||
break;
|
||||
}
|
||||
|
||||
return $aCriteria;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $aCriteria
|
||||
* @param $aFields
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected static function RevertValues($aCriteria, $aFields)
|
||||
{
|
||||
$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'];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Can't obtain the list of allowed values, just set as unknown
|
||||
$aCriteria['widget'] = AttributeDefinition::SEARCH_WIDGET_TYPE_RAW;
|
||||
}
|
||||
}
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
return $aCriteria;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?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;
|
||||
|
||||
|
||||
abstract class CriterionConversionAbstract
|
||||
{
|
||||
|
||||
const OP_CONTAINS = 'contains';
|
||||
const OP_STARTS_WITH = 'starts_with';
|
||||
const OP_ENDS_WITH = 'ends_with';
|
||||
const OP_EMPTY = 'empty';
|
||||
const OP_NOT_EMPTY = 'not_empty';
|
||||
const OP_IN = 'IN';
|
||||
const OP_BETWEEN_DATES = 'between_dates';
|
||||
const OP_BETWEEN = 'between';
|
||||
const OP_ALL = 'all';
|
||||
|
||||
}
|
||||
|
||||
103
sources/application/search/criterionparser.class.inc.php
Normal file
103
sources/application/search/criterionparser.class.inc.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?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 Combodo\iTop\Application\Search\CriterionConversion\CriterionToOQL;
|
||||
use DBObjectSearch;
|
||||
use Expression;
|
||||
use IssueLog;
|
||||
use OQLException;
|
||||
|
||||
class CriterionParser
|
||||
{
|
||||
|
||||
/**
|
||||
* @param $sBaseOql
|
||||
* @param $aCriterion
|
||||
* @param $sHiddenCriteria
|
||||
*
|
||||
* @return \DBSearch
|
||||
*/
|
||||
public static function Parse($sBaseOql, $aCriterion, $sHiddenCriteria = null)
|
||||
{
|
||||
$aExpression = array();
|
||||
$aOr = $aCriterion['or'];
|
||||
foreach($aOr as $aAndList)
|
||||
{
|
||||
|
||||
$sExpression = self::ParseAndList($aAndList['and']);
|
||||
if (!empty($sExpression))
|
||||
{
|
||||
$aExpression[] = $sExpression;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$oSearch = DBObjectSearch::FromOQL($sBaseOql);
|
||||
|
||||
if (!empty($sHiddenCriteria))
|
||||
{
|
||||
$oHiddenCriteriaExpression = Expression::FromOQL($sHiddenCriteria);
|
||||
$oSearch->AddConditionExpression($oHiddenCriteriaExpression);
|
||||
}
|
||||
|
||||
if (empty($aExpression))
|
||||
{
|
||||
return $oSearch;
|
||||
}
|
||||
|
||||
$oExpression = Expression::FromOQL(implode(" OR ", $aExpression));
|
||||
$oSearch->AddConditionExpression($oExpression);
|
||||
|
||||
return $oSearch;
|
||||
} catch (OQLException $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function ParseAndList($aAnd)
|
||||
{
|
||||
$aExpression = array();
|
||||
foreach($aAnd as $aCriteria)
|
||||
{
|
||||
$aExpression[] = CriterionToOQL::Convert($aCriteria);
|
||||
}
|
||||
|
||||
if (empty($aExpression))
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
return '('.implode(" AND ", $aExpression).')';
|
||||
}
|
||||
}
|
||||
483
sources/application/search/searchform.class.inc.php
Normal file
483
sources/application/search/searchform.class.inc.php
Normal file
@@ -0,0 +1,483 @@
|
||||
<?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 Combodo\iTop\Application\Search\CriterionConversion\CriterionToSearchForm;
|
||||
use CoreException;
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use Dict;
|
||||
use Exception;
|
||||
use Expression;
|
||||
use IssueLog;
|
||||
use MetaModel;
|
||||
use TrueExpression;
|
||||
use utils;
|
||||
use WebPage;
|
||||
|
||||
class SearchForm
|
||||
{
|
||||
|
||||
/**
|
||||
* @param \WebPage $oPage
|
||||
* @param \CMDBObjectSet $oSet
|
||||
* @param array $aExtraParams
|
||||
*
|
||||
* @return string
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionMissingString
|
||||
*/
|
||||
public function GetSearchForm(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array())
|
||||
{
|
||||
$sHtml = '';
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sClassName = $oSet->GetFilter()->GetClass();
|
||||
$aListParams = array();
|
||||
|
||||
foreach($aExtraParams as $key => $value)
|
||||
{
|
||||
$aListParams[$key] = $value;
|
||||
}
|
||||
|
||||
// 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";
|
||||
$aListParams['currentId'] = "$iSearchFormId";
|
||||
}
|
||||
// Check if the current class has some sub-classes
|
||||
if (isset($aExtraParams['baseClass']))
|
||||
{
|
||||
$sRootClass = $aExtraParams['baseClass'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRootClass = $sClassName;
|
||||
}
|
||||
|
||||
$sJson = utils::ReadParam('json', '', false, 'raw_data');
|
||||
if (!empty($sJson))
|
||||
{
|
||||
$aListParams['json'] = json_decode($sJson, true);
|
||||
}
|
||||
|
||||
if (!isset($aExtraParams['result_list_outer_selector']))
|
||||
{
|
||||
if (isset($aExtraParams['table_id']))
|
||||
{
|
||||
$aExtraParams['result_list_outer_selector'] = $aExtraParams['table_id'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$aExtraParams['result_list_outer_selector'] = "search_form_result_{$sSearchFormId}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$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', '{$aExtraParams['result_list_outer_selector']}')\">\n".implode('',
|
||||
$aOptions)."</select>\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sClassesCombo = MetaModel::GetName($sClassName);
|
||||
}
|
||||
$sAction = (isset($aExtraParams['action'])) ? $aExtraParams['action'] : utils::GetAbsoluteUrlAppRoot().'pages/UI.php';
|
||||
$sStyle = (isset($aExtraParams['open']) && ($aExtraParams['open'] == 'true')) ? 'opened' : '';
|
||||
$sHtml .= "<form id=\"fs_{$sSearchFormId}\" action=\"{$sAction}\" class=\"{$sStyle}\">\n"; // Don't use $_SERVER['SCRIPT_NAME'] since the form may be called asynchronously (from ajax.php)
|
||||
$sHtml .= "<h2 class=\"sf_title\"><span class=\"sft_picto fa fa-search\"></span>" . Dict::Format('UI:SearchFor_Class_Objects', $sClassesCombo) . "<a class=\"sft_toggler fa fa-caret-down pull-right\" href=\"#\" title=\"" . Dict::S('UI:Search:Toggle') . "\"></a><a class=\"sft_refresh fa fa-search pull-right\" href=\"#\" title=\"" . Dict::S('UI:Button:Refresh') . "\"></a></h2>\n";
|
||||
$sHtml .= "<div id=\"fs_{$sSearchFormId}_message\" class=\"sf_message header_message\"></div>\n";
|
||||
$sHtml .= "<div id=\"fs_{$sSearchFormId}_criterion_outer\">\n</div>\n";
|
||||
$sHtml .= "</form>\n";
|
||||
|
||||
if (isset($aExtraParams['query_params']))
|
||||
{
|
||||
$aArgs = $aExtraParams['query_params'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$aArgs = array();
|
||||
}
|
||||
|
||||
$bIsRemovable = true;
|
||||
if (isset($aExtraParams['selection_mode']) && $aExtraParams['selection_mode'])
|
||||
{
|
||||
// Mark all criterion as read-only and non-removable
|
||||
$bIsRemovable = false;
|
||||
}
|
||||
$aFields = $this->GetFields($oSet);
|
||||
$oSearch = $oSet->GetFilter();
|
||||
$aCriterion = $this->GetCriterion($oSearch, $aFields, $aArgs, $bIsRemovable);
|
||||
$aClasses = $oSearch->GetSelectedClasses();
|
||||
$sClassAlias = '';
|
||||
foreach($aClasses as $sAlias => $sClass)
|
||||
{
|
||||
$sClassAlias = $sAlias;
|
||||
}
|
||||
|
||||
$oBaseSearch = $oSearch->DeepClone();
|
||||
$oBaseSearch->ResetCondition();
|
||||
$sBaseOQL = str_replace(' WHERE 1', '', $oBaseSearch->ToOQL());
|
||||
|
||||
|
||||
if (isset($aExtraParams['table_inner_id']))
|
||||
{
|
||||
$sDataConfigListSelector = $aExtraParams['table_inner_id'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$sDataConfigListSelector = $aExtraParams['result_list_outer_selector'];
|
||||
}
|
||||
if (!isset($aExtraParams['table_inner_id']))
|
||||
{
|
||||
$aListParams['table_inner_id'] = "table_inner_id_{$sSearchFormId}";
|
||||
}
|
||||
$bOpen = false;
|
||||
if (isset($aExtraParams['open']))
|
||||
{
|
||||
$bOpen = $aExtraParams['open'];
|
||||
}
|
||||
|
||||
$sDebug = utils::ReadParam('debug', 'false', false, 'parameter');
|
||||
if ($sDebug == 'true')
|
||||
{
|
||||
$aListParams['debug'] = 'true';
|
||||
}
|
||||
|
||||
$aDaysMin = array(Dict::S('DayOfWeek-Sunday-Min'), Dict::S('DayOfWeek-Monday-Min'), Dict::S('DayOfWeek-Tuesday-Min'), Dict::S('DayOfWeek-Wednesday-Min'),
|
||||
Dict::S('DayOfWeek-Thursday-Min'), Dict::S('DayOfWeek-Friday-Min'), Dict::S('DayOfWeek-Saturday-Min'));
|
||||
$aMonthsShort = array(Dict::S('Month-01-Short'), Dict::S('Month-02-Short'), Dict::S('Month-03-Short'), Dict::S('Month-04-Short'), Dict::S('Month-05-Short'), Dict::S('Month-06-Short'),
|
||||
Dict::S('Month-07-Short'), Dict::S('Month-08-Short'), Dict::S('Month-09-Short'), Dict::S('Month-10-Short'), Dict::S('Month-11-Short'), Dict::S('Month-12-Short'));
|
||||
|
||||
$aSearchParams = array(
|
||||
'criterion_outer_selector' => "#fs_{$sSearchFormId}_criterion_outer",
|
||||
'result_list_outer_selector' => "#{$aExtraParams['result_list_outer_selector']}",
|
||||
'data_config_list_selector' => "#{$sDataConfigListSelector}",
|
||||
'endpoint' => utils::GetAbsoluteUrlAppRoot().'pages/ajax.searchform.php',
|
||||
'init_opened' => $bOpen,
|
||||
'datepicker' => array(
|
||||
'dayNamesMin' => $aDaysMin,
|
||||
'monthNamesShort' => $aMonthsShort,
|
||||
'firstDay' => (int) Dict::S('Calendar-FirstDayOfWeek'),
|
||||
// 'date_format' => AttributeDate::GetFormat()->ToDatePicker(),
|
||||
// 'date_time_format' => AttributeDateTime::GetFormat()->ToMomentJS(),
|
||||
),
|
||||
'list_params' => $aListParams,
|
||||
'search' => array(
|
||||
'has_hidden_criteria' => (array_key_exists('hidden_criteria', $aListParams) && !empty($aListParams['hidden_criteria'])),
|
||||
'fields' => $aFields,
|
||||
'criterion' => $aCriterion,
|
||||
'class_name' => $sClassName,
|
||||
'class_alias' => $sClassAlias,
|
||||
'base_oql' => $sBaseOQL,
|
||||
),
|
||||
);
|
||||
|
||||
$oPage->add_ready_script('$("#fs_'.$sSearchFormId.'").search_form_handler('.json_encode($aSearchParams).');');
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DBObjectSet $oSet
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function GetFields($oSet)
|
||||
{
|
||||
$oSearch = $oSet->GetFilter();
|
||||
$aAllClasses = $oSearch->GetSelectedClasses();
|
||||
$aAuthorizedClasses = array();
|
||||
foreach($aAllClasses as $sAlias => $sClassName)
|
||||
{
|
||||
if (\UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO)
|
||||
{
|
||||
$aAuthorizedClasses[$sAlias] = $sClassName;
|
||||
}
|
||||
}
|
||||
$aAllFields = array('zlist' => array(), 'others' => array());
|
||||
try
|
||||
{
|
||||
foreach($aAuthorizedClasses as $sAlias => $sClass)
|
||||
{
|
||||
$aZList = array();
|
||||
$aOthers = array();
|
||||
|
||||
$this->PopulateFiledList($sClass, $sAlias, $aZList, $aOthers);
|
||||
|
||||
$aAllFields[$sAlias.'_zlist'] = $aZList;
|
||||
$aAllFields[$sAlias.'_others'] = $aOthers;
|
||||
}
|
||||
}
|
||||
catch (CoreException $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
}
|
||||
$aSelectedClasses = $oSearch->GetSelectedClasses();
|
||||
foreach($aSelectedClasses as $sAlias => $sClassName)
|
||||
{
|
||||
$aAllFields['zlist'] = array_merge($aAllFields['zlist'], $aAllFields[$sAlias.'_zlist']);
|
||||
unset($aAllFields[$sAlias.'_zlist']);
|
||||
$aAllFields['others'] = array_merge($aAllFields['others'], $aAllFields[$sAlias.'_others']);
|
||||
unset($aAllFields[$sAlias.'_others']);
|
||||
|
||||
}
|
||||
|
||||
return $aAllFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sClass
|
||||
* @param $sAlias
|
||||
* @param $aZList
|
||||
* @param $aOthers
|
||||
*
|
||||
* @throws \CoreException
|
||||
*/
|
||||
protected function PopulateFiledList($sClass, $sAlias, &$aZList, &$aOthers)
|
||||
{
|
||||
$aAttributeDefs = MetaModel::ListAttributeDefs($sClass);
|
||||
$aList = MetaModel::GetZListItems($sClass, 'standard_search');
|
||||
foreach($aList as $sAttCode)
|
||||
{
|
||||
if (array_key_exists($sAttCode, $aAttributeDefs))
|
||||
{
|
||||
$oAttDef = $aAttributeDefs[$sAttCode];
|
||||
$aZList = $this->AppendField($sClass, $sAlias, $sAttCode, $oAttDef, $aZList);
|
||||
unset($aAttributeDefs[$sAttCode]);
|
||||
}
|
||||
}
|
||||
uasort($aZList, function ($aItem1, $aItem2) {
|
||||
return strcmp($aItem1['label'], $aItem2['label']);
|
||||
});
|
||||
$aZList = $this->AppendId($sClass, $sAlias, $aZList);
|
||||
|
||||
foreach($aAttributeDefs as $sAttCode => $oAttDef)
|
||||
{
|
||||
if ($this->IsSubAttribute($oAttDef)) continue;
|
||||
|
||||
$aOthers = $this->AppendField($sClass, $sAlias, $sAttCode, $oAttDef, $aOthers);
|
||||
}
|
||||
uasort($aOthers, function ($aItem1, $aItem2) {
|
||||
return strcmp($aItem1['label'], $aItem2['label']);
|
||||
});
|
||||
}
|
||||
|
||||
protected function IsSubAttribute($oAttDef)
|
||||
{
|
||||
return (($oAttDef instanceof AttributeFriendlyName) || ($oAttDef instanceof AttributeExternalField) || ($oAttDef instanceof AttributeSubItem));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $oAttrDef
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function GetFieldAllowedValues($oAttrDef)
|
||||
{
|
||||
if ($oAttrDef->IsExternalKey(EXTKEY_ABSOLUTE))
|
||||
{
|
||||
$sTargetClass = $oAttrDef->GetTargetClass();
|
||||
try
|
||||
{
|
||||
$oSearch = new DBObjectSearch($sTargetClass);
|
||||
} catch (Exception $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
|
||||
return array('values' => array());
|
||||
}
|
||||
$oSearch->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true);
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
$iCount = $oSet->Count();
|
||||
if ($iCount > MetaModel::GetConfig()->Get('max_combo_length'))
|
||||
{
|
||||
return array('autocomplete' => true, 'count' => $iCount);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (method_exists($oAttrDef, 'GetAllowedValuesAsObjectSet'))
|
||||
{
|
||||
$oSet = $oAttrDef->GetAllowedValuesAsObjectSet();
|
||||
$iCount = $oSet->Count();
|
||||
if ($iCount > MetaModel::GetConfig()->Get('max_combo_length'))
|
||||
{
|
||||
return array('autocomplete' => true, 'count' => $iCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$aAllowedValues = $oAttrDef->GetAllowedValues();
|
||||
|
||||
return array('values' => $aAllowedValues, 'count' => count($aAllowedValues));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DBObjectSearch $oSearch
|
||||
* @param array $aFields
|
||||
*
|
||||
* @param array $aArgs
|
||||
*
|
||||
* @param bool $bIsRemovable
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function GetCriterion($oSearch, $aFields, $aArgs = array(), $bIsRemovable = true)
|
||||
{
|
||||
$oExpression = $oSearch->GetCriteria();
|
||||
|
||||
if (!empty($aArgs))
|
||||
{
|
||||
$aArgs = MetaModel::PrepareQueryArguments($aArgs);
|
||||
|
||||
$sOQL = $oExpression->Render($aArgs);
|
||||
$oExpression = Expression::FromOQL($sOQL);
|
||||
}
|
||||
|
||||
$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) || ($oAndSubExpr->Render() == 1))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$aAndCriterion[] = $oAndSubExpr->GetCriterion($oSearch);
|
||||
}
|
||||
$aAndCriterion = CriterionToSearchForm::Convert($aAndCriterion, $aFields, $oSearch->GetJoinedClasses(), $bIsRemovable);
|
||||
$aOrCriterion[] = array('and' => $aAndCriterion);
|
||||
}
|
||||
|
||||
return array('or' => $aOrCriterion);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sClass
|
||||
* @param $sClassAlias
|
||||
* @param $aFields
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function AppendId($sClass, $sClassAlias, $aFields)
|
||||
{
|
||||
$aField = array();
|
||||
$aField['code'] = 'id';
|
||||
$aField['class'] = $sClass;
|
||||
$aField['class_alias'] = $sClassAlias;
|
||||
$aField['label'] = 'Id';
|
||||
$aField['widget'] = AttributeDefinition::SEARCH_WIDGET_TYPE_NUMERIC;
|
||||
$aField['is_null_allowed'] = false;
|
||||
$aNewFields = array($sClassAlias.'.id' => $aField);
|
||||
$aFields = array_merge($aNewFields, $aFields);
|
||||
return $aFields;
|
||||
}
|
||||
/**
|
||||
* @param $sClass
|
||||
* @param $sClassAlias
|
||||
* @param $sFilterCode
|
||||
* @param $oAttDef
|
||||
* @param $aFields
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function AppendField($sClass, $sClassAlias, $sFilterCode, $oAttDef, $aFields)
|
||||
{
|
||||
if (!is_null($oAttDef) && ($oAttDef->GetSearchType() != AttributeDefinition::SEARCH_WIDGET_TYPE_RAW))
|
||||
{
|
||||
if (method_exists($oAttDef, 'GetLabelForSearchField'))
|
||||
{
|
||||
$sLabel = $oAttDef->GetLabelForSearchField();
|
||||
}
|
||||
else
|
||||
{
|
||||
$sLabel = $oAttDef->GetLabel();
|
||||
}
|
||||
|
||||
if (method_exists($oAttDef, 'GetTargetClass'))
|
||||
{
|
||||
$sTargetClass = $oAttDef->GetTargetClass();
|
||||
}
|
||||
else
|
||||
{
|
||||
$sTargetClass = $oAttDef->GetHostClass();
|
||||
}
|
||||
|
||||
$aField = array();
|
||||
$aField['code'] = $sFilterCode;
|
||||
$aField['class'] = $sClass;
|
||||
$aField['class_alias'] = $sClassAlias;
|
||||
$aField['target_class'] = $sTargetClass;
|
||||
$aField['label'] = $sLabel;
|
||||
$aField['widget'] = $oAttDef->GetSearchType();
|
||||
$aField['allowed_values'] = self::GetFieldAllowedValues($oAttDef);
|
||||
$aField['is_null_allowed'] = $oAttDef->IsNullAllowed();
|
||||
$aFields[$sClassAlias.'.'.$sFilterCode] = $aField;
|
||||
|
||||
// Sub items
|
||||
//
|
||||
// if ($oAttDef->IsSearchable())
|
||||
// {
|
||||
// $sShortLabel = $oAttDef->GetLabel();
|
||||
// $sLabel = $sShortAlias.$oAttDef->GetLabel();
|
||||
// $aSubAttr = $this->GetSubAttributes($sClass, $sFilterCode, $oAttDef);
|
||||
// $aValidSubAttr = array();
|
||||
// foreach($aSubAttr as $aSubAttDef)
|
||||
// {
|
||||
// $aValidSubAttr[] = array('attcodeex' => $aSubAttDef['code'], 'code' => $sShortAlias.$aSubAttDef['code'], 'label' => $aSubAttDef['label'], 'unique_label' => $sShortAlias.$aSubAttDef['unique_label']);
|
||||
// }
|
||||
// $aAllFields[] = array('attcodeex' => $sFilterCode, 'code' => $sShortAlias.$sFilterCode, 'label' => $sShortLabel, 'unique_label' => $sLabel, 'subattr' => $aValidSubAttr);
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
return $aFields;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -19,9 +19,6 @@
|
||||
|
||||
namespace Combodo\iTop\Form\Field;
|
||||
|
||||
use \AttributeDatetime;
|
||||
use \Combodo\iTop\Form\Field\StringField;
|
||||
|
||||
/**
|
||||
* A field for Dates and Date & Times, supporting custom formats
|
||||
*/
|
||||
@@ -30,7 +27,7 @@ class DateTimeField extends StringField
|
||||
protected $sJSDateTimeFormat;
|
||||
protected $sPHPDateTimeFormat;
|
||||
protected $bDateOnly;
|
||||
|
||||
|
||||
/**
|
||||
* Overloaded constructor
|
||||
*
|
||||
@@ -42,9 +39,10 @@ class DateTimeField extends StringField
|
||||
parent::__construct($sId, $onFinalizeCallback);
|
||||
$this->bDateOnly = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the PHP format string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function GetPHPDateTimeFormat()
|
||||
@@ -55,14 +53,16 @@ class DateTimeField extends StringField
|
||||
/**
|
||||
*
|
||||
* @param string $sFormat
|
||||
*
|
||||
* @return \Combodo\iTop\Form\Field\DateTimeField
|
||||
*/
|
||||
public function SetPHPDateTimeFormat($sDateTimeFormat)
|
||||
{
|
||||
$this->sPHPDateTimeFormat = $sDateTimeFormat;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -74,23 +74,26 @@ class DateTimeField extends StringField
|
||||
/**
|
||||
*
|
||||
* @param string $sFormat
|
||||
*
|
||||
* @return \Combodo\iTop\Form\Field\DateTimeField
|
||||
*/
|
||||
public function SetJSDateTimeFormat($sDateTimeFormat)
|
||||
{
|
||||
$this->sDateTimeFormat = $sDateTimeFormat;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the DateOnly flag
|
||||
*
|
||||
* @return \Combodo\iTop\Form\Field\DateTimeField
|
||||
*/
|
||||
public function SetDateOnly($bDateOnly)
|
||||
{
|
||||
return $this->bDateOnly = $bDateOnly;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
|
||||
@@ -18,16 +18,16 @@
|
||||
|
||||
namespace Combodo\iTop\Renderer\Console\FieldRenderer;
|
||||
|
||||
use \Dict;
|
||||
use AttributeDate;
|
||||
use AttributeDateTime;
|
||||
use AttributeDuration;
|
||||
use Combodo\iTop\Form\Field\TextAreaField;
|
||||
use Combodo\iTop\Renderer\FieldRenderer;
|
||||
use Combodo\iTop\Renderer\RenderingOutput;
|
||||
use Combodo\iTop\Form\Field\TextAreaField;
|
||||
use \InlineImage;
|
||||
use \UserRights;
|
||||
use \AttributeDuration;
|
||||
use \DateTimeFormat;
|
||||
use \AttributeDateTime;
|
||||
use \AttributeDate;
|
||||
use DateTimeFormat;
|
||||
use Dict;
|
||||
use InlineImage;
|
||||
use UserRights;
|
||||
|
||||
class ConsoleSimpleFieldRenderer extends FieldRenderer
|
||||
{
|
||||
@@ -217,7 +217,6 @@ EOF
|
||||
|
||||
switch ($sFieldClass)
|
||||
{
|
||||
case 'Combodo\\iTop\Form\\Field\\DateTimeField':
|
||||
case 'Combodo\\iTop\\Form\\Field\\DateTimeField':
|
||||
$sDateTimeFormat = $this->oField->GetPHPDateTimeFormat();
|
||||
$oFormat = new DateTimeFormat($sDateTimeFormat);
|
||||
|
||||
@@ -59,7 +59,7 @@ try
|
||||
throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'oql'));
|
||||
}
|
||||
$oFilter = DBObjectSearch::FromOQL($sOQL);
|
||||
$oBlock1 = new DisplayBlock($oFilter, 'search', false, array('menu'=>false));
|
||||
$oBlock1 = new DisplayBlock($oFilter, 'search', false, array('menu' => false, 'table_id' => '1'));
|
||||
$oBlock1->Display($oP, 0);
|
||||
$oP->add('<p class="page-header">'.MetaModel::GetClassIcon('SynchroReplica').Dict::S('Core:SynchroReplica:ListOfReplicas').'</p>');
|
||||
$iSourceId = utils::ReadParam('datasource', null);
|
||||
|
||||
@@ -37,7 +37,6 @@ use Hypervisor;
|
||||
use lnkContactToFunctionalCI;
|
||||
use MetaModel;
|
||||
use Person;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Server;
|
||||
use Ticket;
|
||||
use URP_UserProfile;
|
||||
@@ -92,14 +91,6 @@ class ItopDataTestCase extends ItopTestCase
|
||||
return $this->testOrgId;
|
||||
}
|
||||
|
||||
/**
|
||||
* PHPUnit complains that there is no test... so here is a dummy one.
|
||||
*/
|
||||
public function testDummy()
|
||||
{
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
/// Database Utilities
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
@@ -152,7 +143,7 @@ class ItopDataTestCase extends ItopTestCase
|
||||
$oTicket = self::createObject('UserRequest', array(
|
||||
'ref' => 'Ticket_'.$iNum,
|
||||
'title' => 'BUG 789_'.$iNum,
|
||||
'request_type' => 'incident',
|
||||
//'request_type' => 'incident',
|
||||
'description' => 'method UpdateImpactedItems() reconstruit le lnkContactToTicket donc impossible de rajouter des champs dans cette classe',
|
||||
'org_id' => $this->getTestOrgId(),
|
||||
));
|
||||
@@ -176,7 +167,7 @@ class ItopDataTestCase extends ItopTestCase
|
||||
$oTicket = self::createObject('UserRequest', array(
|
||||
'ref' => 'Ticket_'.$iNum,
|
||||
'title' => 'BUG 1161_'.$iNum,
|
||||
'request_type' => 'incident',
|
||||
//'request_type' => 'incident',
|
||||
'description' => 'Add aggregate functions',
|
||||
'time_spent' => $iTimeSpent,
|
||||
'caller_id' => $iCallerId,
|
||||
@@ -188,16 +179,20 @@ class ItopDataTestCase extends ItopTestCase
|
||||
|
||||
/**
|
||||
* Create a Server in database
|
||||
*
|
||||
* @param int $iNum
|
||||
* @param null $iRackUnit
|
||||
*
|
||||
* @return Server
|
||||
* @throws Exception
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function CreateServer($iNum)
|
||||
protected function CreateServer($iNum, $iRackUnit = null)
|
||||
{
|
||||
/** @var Server $oServer */
|
||||
$oServer = self::createObject('Server', array(
|
||||
'name' => 'Server_'.$iNum,
|
||||
'org_id' => $this->getTestOrgId(),
|
||||
'nb_u' => $iRackUnit,
|
||||
));
|
||||
$this->debug("Created {$oServer->GetName()} ({$oServer->GetKey()})");
|
||||
return $oServer;
|
||||
|
||||
@@ -47,6 +47,9 @@
|
||||
<testsuite name="Tickets">
|
||||
<directory>itop-tickets</directory>
|
||||
</testsuite>
|
||||
<testsuite name="Application">
|
||||
<directory>application</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<!-- Code coverage white list -->
|
||||
|
||||
570
test/application/search/CriterionConversionTest.php
Normal file
570
test/application/search/CriterionConversionTest.php
Normal file
@@ -0,0 +1,570 @@
|
||||
<?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: 16:46
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
class CriterionConversionTest extends ItopDataTestCase
|
||||
{
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
require_once(APPROOT."sources/application/search/criterionconversionabstract.class.inc.php");
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider ToOqlProvider
|
||||
*
|
||||
* @param $sJSONCriterion
|
||||
* @param $sExpectedOQL
|
||||
*/
|
||||
public function testToOql($sJSONCriterion, $sExpectedOQL)
|
||||
{
|
||||
$sOql = CriterionToOQL::Convert(
|
||||
json_decode($sJSONCriterion, true)
|
||||
);
|
||||
|
||||
$this->debug($sOql);
|
||||
$this->assertEquals($sExpectedOQL, $sOql);
|
||||
}
|
||||
|
||||
public function ToOqlProvider()
|
||||
{
|
||||
return array(
|
||||
'>' => array(
|
||||
'{
|
||||
"ref": "UserRequest.start_date",
|
||||
"values": [
|
||||
{
|
||||
"value": "2017-01-01",
|
||||
"label": "2017-01-01 00:00:00"
|
||||
}
|
||||
],
|
||||
"operator": ">",
|
||||
"oql": ""
|
||||
}',
|
||||
"(`UserRequest`.`start_date` > '2017-01-01')"
|
||||
),
|
||||
'contains' => array(
|
||||
'{
|
||||
"ref": "Contact.name",
|
||||
"values": [
|
||||
{
|
||||
"value": "toto",
|
||||
"label": "toto"
|
||||
}
|
||||
],
|
||||
"operator": "contains",
|
||||
"oql": ""
|
||||
}',
|
||||
"(`Contact`.`name` LIKE '%toto%')"
|
||||
),
|
||||
'starts_with' => array(
|
||||
'{
|
||||
"ref": "Contact.name",
|
||||
"values": [
|
||||
{
|
||||
"value": "toto",
|
||||
"label": "toto"
|
||||
}
|
||||
],
|
||||
"operator": "starts_with",
|
||||
"oql": ""
|
||||
}',
|
||||
"(`Contact`.`name` LIKE 'toto%')"
|
||||
),
|
||||
'ends_with' => array(
|
||||
'{
|
||||
"ref": "Contact.name",
|
||||
"values": [
|
||||
{
|
||||
"value": "toto",
|
||||
"label": "toto"
|
||||
}
|
||||
],
|
||||
"operator": "ends_with",
|
||||
"oql": ""
|
||||
}',
|
||||
"(`Contact`.`name` LIKE '%toto')"
|
||||
),
|
||||
'empty' => array(
|
||||
'{
|
||||
"ref": "Contact.name",
|
||||
"values": [
|
||||
{
|
||||
"value": "",
|
||||
"label": ""
|
||||
}
|
||||
],
|
||||
"operator": "empty",
|
||||
"oql": ""
|
||||
}',
|
||||
"(`Contact`.`name` = '')"
|
||||
),
|
||||
'not_empty' => array(
|
||||
'{
|
||||
"ref": "Contact.name",
|
||||
"values": [
|
||||
{
|
||||
"value": "",
|
||||
"label": ""
|
||||
}
|
||||
],
|
||||
"operator": "not_empty",
|
||||
"oql": ""
|
||||
}',
|
||||
"(`Contact`.`name` != '')"
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider ToSearchFormProvider
|
||||
*
|
||||
* @param $aCriterion
|
||||
* @param $sExpectedOperator
|
||||
*
|
||||
* @throws \OQLException
|
||||
*/
|
||||
function testToSearchForm($aCriterion, $sExpectedOperator)
|
||||
{
|
||||
$oSearchForm = new SearchForm();
|
||||
$oSearch = \DBSearch::FromOQL("SELECT Contact");
|
||||
$aFields = $oSearchForm->GetFields(new \DBObjectSet($oSearch));
|
||||
$aRes = CriterionToSearchForm::Convert($aCriterion, $aFields, $oSearch->GetJoinedClasses());
|
||||
$this->debug($aRes);
|
||||
$this->assertEquals($sExpectedOperator, $aRes[0]['operator']);
|
||||
}
|
||||
|
||||
function ToSearchFormProvider()
|
||||
{
|
||||
return array(
|
||||
'=' => array(
|
||||
json_decode('[
|
||||
{
|
||||
"ref": "Contact.name",
|
||||
"widget": "string",
|
||||
"values": [
|
||||
{
|
||||
"value": "toto",
|
||||
"label": "toto"
|
||||
}
|
||||
],
|
||||
"operator": "=",
|
||||
"oql": "(`Contact`.`name` = \'toto\')"
|
||||
}
|
||||
]', true),
|
||||
'='
|
||||
),
|
||||
'starts_with' => array(
|
||||
json_decode('[
|
||||
{
|
||||
"ref": "Contact.name",
|
||||
"widget": "string",
|
||||
"values": [
|
||||
{
|
||||
"value": "toto%",
|
||||
"label": "toto%"
|
||||
}
|
||||
],
|
||||
"operator": "LIKE",
|
||||
"oql": "(`Contact`.`name` LIKE \'toto%\')"
|
||||
}
|
||||
]', true),
|
||||
'starts_with'
|
||||
),
|
||||
'ends_with' => array(
|
||||
json_decode('[
|
||||
{
|
||||
"ref": "Contact.name",
|
||||
"widget": "string",
|
||||
"values": [
|
||||
{
|
||||
"value": "%toto",
|
||||
"label": "%toto"
|
||||
}
|
||||
],
|
||||
"operator": "LIKE",
|
||||
"oql": "(`Contact`.`name` LIKE \'%toto\')"
|
||||
}
|
||||
]', true),
|
||||
'ends_with'
|
||||
),
|
||||
'contains' => array(
|
||||
json_decode('[
|
||||
{
|
||||
"widget": "string",
|
||||
"ref": "Contact.name",
|
||||
"values": [
|
||||
{
|
||||
"value": "%toto%",
|
||||
"label": "%toto%"
|
||||
}
|
||||
],
|
||||
"operator": "LIKE",
|
||||
"oql": "(`Contact`.`name` LIKE \'%toto%\')"
|
||||
}
|
||||
]', true),
|
||||
'contains'
|
||||
),
|
||||
'empty1' => array(
|
||||
json_decode('[
|
||||
{
|
||||
"widget": "string",
|
||||
"ref": "Contact.name",
|
||||
"values": [
|
||||
{
|
||||
"value": "",
|
||||
"label": ""
|
||||
}
|
||||
],
|
||||
"operator": "LIKE",
|
||||
"oql": "(`Contact`.`name` LIKE \'\')"
|
||||
}
|
||||
]', true),
|
||||
'empty'
|
||||
),
|
||||
'empty2' => array(
|
||||
json_decode('[
|
||||
{
|
||||
"widget": "string",
|
||||
"ref": "Contact.name",
|
||||
"values": [
|
||||
{
|
||||
"value": "",
|
||||
"label": ""
|
||||
}
|
||||
],
|
||||
"operator": "=",
|
||||
"oql": "(`Contact`.`name` = \'\')"
|
||||
}
|
||||
]', true),
|
||||
'empty'
|
||||
),
|
||||
'not_empty' => array(
|
||||
json_decode('[
|
||||
{
|
||||
"widget": "string",
|
||||
"ref": "Contact.name",
|
||||
"values": [
|
||||
{
|
||||
"value": "",
|
||||
"label": ""
|
||||
}
|
||||
],
|
||||
"operator": "!=",
|
||||
"oql": "(`Contact`.`name` != \'\')"
|
||||
}
|
||||
]', true),
|
||||
'not_empty'
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider OqlProvider
|
||||
*
|
||||
* @param $sOQL
|
||||
*
|
||||
* @param $sExpectedOQL
|
||||
*
|
||||
* @param $aExpectedCriterion
|
||||
*
|
||||
* @throws \OQLException
|
||||
*/
|
||||
function testOqlToForSearchToOql($sOQL, $sExpectedOQL, $aExpectedCriterion)
|
||||
{
|
||||
$this->debug($sOQL);
|
||||
$oSearchForm = new SearchForm();
|
||||
$oSearch = \DBSearch::FromOQL($sOQL);
|
||||
$aFields = $oSearchForm->GetFields(new \DBObjectSet($oSearch));
|
||||
/** @var \DBObjectSearch $oSearch */
|
||||
$aCriterion = $oSearchForm->GetCriterion($oSearch, $aFields);
|
||||
|
||||
$aAndCriterion = $aCriterion['or'][0]['and'];
|
||||
|
||||
$aNewCriterion = array();
|
||||
foreach($aAndCriterion as $aCriteria)
|
||||
{
|
||||
if ($aCriteria['widget'] != \AttributeDefinition::SEARCH_WIDGET_TYPE_RAW)
|
||||
{
|
||||
unset($aCriteria['oql']);
|
||||
foreach($aFields as $aCatFields)
|
||||
{
|
||||
if (isset($aCatFields[$aCriteria['ref']]))
|
||||
{
|
||||
$aField = $aCatFields[$aCriteria['ref']];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isset($aField))
|
||||
{
|
||||
$aCriteria['code'] = $aField['code'];
|
||||
$aCriteria['class'] = $aField['class'];
|
||||
}
|
||||
}
|
||||
|
||||
$aNewCriterion[] = $aCriteria;
|
||||
}
|
||||
$this->debug($aNewCriterion);
|
||||
|
||||
$this->assertFalse($this->array_diff_assoc_recursive($aExpectedCriterion, $aNewCriterion));
|
||||
|
||||
$aCriterion['or'][0]['and'] = $aNewCriterion;
|
||||
|
||||
$oSearch->ResetCondition();
|
||||
$oFilter = CriterionParser::Parse($oSearch->ToOQL(), $aCriterion);
|
||||
|
||||
$sResultOQL = $oFilter->ToOQL();
|
||||
$this->debug($sResultOQL);
|
||||
|
||||
$this->assertEquals($sExpectedOQL, $sResultOQL);
|
||||
}
|
||||
|
||||
function OqlProvider()
|
||||
{
|
||||
return array(
|
||||
'no criteria' => array(
|
||||
'OQL' => 'SELECT WebApplication',
|
||||
'ExpectedOQL' => "SELECT `WebApplication` FROM WebApplication AS `WebApplication` WHERE 1",
|
||||
'ExpectedCriterion' => array(),
|
||||
),
|
||||
'string starts' => array(
|
||||
'OQL' => "SELECT Contact WHERE name LIKE 'toto%'",
|
||||
'ExpectedOQL' => "SELECT `Contact` FROM Contact AS `Contact` WHERE (`Contact`.`name` LIKE 'toto%')",
|
||||
'ExpectedCriterion' => array(array('widget' => 'string', 'operator' => 'starts_with', 'values' => array(array('value' => 'toto')))),
|
||||
),
|
||||
'string ends' => array(
|
||||
'OQL' => "SELECT Contact WHERE name LIKE '%toto'",
|
||||
'ExpectedOQL' => "SELECT `Contact` FROM Contact AS `Contact` WHERE (`Contact`.`name` LIKE '%toto')",
|
||||
'ExpectedCriterion' => array(array('widget' => 'string', 'operator' => 'ends_with', 'values' => array(array('value' => 'toto')))),
|
||||
),
|
||||
'string contains 1' => array(
|
||||
'OQL' => "SELECT Contact WHERE name LIKE '%toto%'",
|
||||
'ExpectedOQL' => "SELECT `Contact` FROM Contact AS `Contact` WHERE (`Contact`.`name` LIKE '%toto%')",
|
||||
'ExpectedCriterion' => array(array('widget' => 'string', 'operator' => 'contains', 'values' => array(array('value' => 'toto')))),
|
||||
),
|
||||
'string contains 2' => array(
|
||||
'OQL' => "SELECT Person AS B WHERE B.name LIKE '%A%'",
|
||||
'ExpectedOQL' => "SELECT `B` FROM Person AS `B` WHERE (`B`.`name` LIKE '%A%')",
|
||||
'ExpectedCriterion' => array(array('widget' => 'string', 'operator' => 'contains', 'values' => array(array('value' => 'A')))),
|
||||
),
|
||||
'string regexp' => array(
|
||||
'OQL' => "SELECT Server WHERE name REGEXP '^dbserver[0-9]+\\\\\\\\..+\\\\\\\\.[a-z]{2,3}$'",
|
||||
'ExpectedOQL' => "SELECT `Server` FROM Server AS `Server` WHERE (`Server`.`name` REGEXP '^dbserver[0-9]+\\\\..+\\\\.[a-z]{2,3}$')",
|
||||
'ExpectedCriterion' => array(array('widget' => 'string', 'operator' => 'REGEXP')),
|
||||
),
|
||||
'enum + key =' => array(
|
||||
'OQL' => "SELECT Contact WHERE status = 'active' AND org_id = 3",
|
||||
'ExpectedOQL' => "SELECT `Contact` FROM Contact AS `Contact` WHERE ((`Contact`.`org_id` = '3') AND (`Contact`.`status` = 'active'))",
|
||||
'ExpectedCriterion' => array(array('widget' => 'hierarchical_key', 'operator' => 'IN'), array('widget' => 'enum', 'operator' => 'IN')),
|
||||
),
|
||||
'enum =' => array(
|
||||
'OQL' => "SELECT Contact WHERE status = 'active'",
|
||||
'ExpectedOQL' => "SELECT `Contact` FROM Contact AS `Contact` WHERE (`Contact`.`status` = 'active')",
|
||||
'ExpectedCriterion' => array(array('widget' => 'enum', 'operator' => 'IN', 'values' => array(array('value' => 'active')))),
|
||||
),
|
||||
'enum IN' => array(
|
||||
'OQL' => "SELECT Contact WHERE status IN ('active', 'inactive')",
|
||||
'ExpectedOQL' => "SELECT `Contact` FROM Contact AS `Contact` WHERE 1",
|
||||
'ExpectedCriterion' => array(array('widget' => 'enum', 'operator' => 'IN', 'values' => array(array('value' => 'active'), array('value' => 'inactive')))),
|
||||
),
|
||||
'enum NOT IN 1' => array(
|
||||
'OQL' => "SELECT Contact WHERE status NOT IN ('active')",
|
||||
'ExpectedOQL' => "SELECT `Contact` FROM Contact AS `Contact` WHERE (`Contact`.`status` = 'inactive')",
|
||||
'ExpectedCriterion' => array(array('widget' => 'enum', 'operator' => 'IN', 'values' => array(array('value' => 'inactive')))),
|
||||
),
|
||||
'enum NOT IN 2' => array(
|
||||
'OQL' => "SELECT Person AS p JOIN UserRequest AS u ON u.agent_id = p.id WHERE u.status != 'closed'",
|
||||
'ExpectedOQL' => "SELECT `p` FROM Person AS `p` JOIN UserRequest AS `u` ON `u`.agent_id = `p`.id WHERE (`u`.`status` != 'closed')",
|
||||
'ExpectedCriterion' => array(array('widget' => 'raw')),
|
||||
),
|
||||
'enum undefined 1' => array(
|
||||
'OQL' => "SELECT FunctionalCI WHERE ((business_criticity = 'high') OR ISNULL(business_criticity)) AND 1",
|
||||
'ExpectedOQL' => "SELECT `FunctionalCI` FROM FunctionalCI AS `FunctionalCI` WHERE (((`FunctionalCI`.`business_criticity` = 'high') OR ISNULL(`FunctionalCI`.`business_criticity`)) AND 1)",
|
||||
'ExpectedCriterion' => array(array('widget' => 'enum', 'has_undefined' => true, 'operator' => 'IN', 'values' => array(array('value' => 'high'), array('value' => 'null')))),
|
||||
),
|
||||
'enum undefined 2' => array(
|
||||
'OQL' => "SELECT FunctionalCI WHERE ((business_criticity IN ('high', 'medium')) OR ISNULL(business_criticity)) AND 1",
|
||||
'ExpectedOQL' => "SELECT `FunctionalCI` FROM FunctionalCI AS `FunctionalCI` WHERE (((`FunctionalCI`.`business_criticity` IN ('high', 'medium')) OR ISNULL(`FunctionalCI`.`business_criticity`)) AND 1)",
|
||||
'ExpectedCriterion' => array(array('widget' => 'enum', 'has_undefined' => true, 'operator' => 'IN', 'values' => array(array('value' => 'high'), array('value' => 'medium'), array('value' => 'null')))),
|
||||
),
|
||||
'enum undefined 3' => array(
|
||||
'OQL' => "SELECT FunctionalCI WHERE ISNULL(business_criticity)",
|
||||
'ExpectedOQL' => "SELECT `FunctionalCI` FROM FunctionalCI AS `FunctionalCI` WHERE ISNULL(`FunctionalCI`.`business_criticity`)",
|
||||
'ExpectedCriterion' => array(array('widget' => 'enum', 'has_undefined' => true, 'operator' => 'IN', 'values' => array(array('value' => 'null')))),
|
||||
),
|
||||
'key NOT IN' => array(
|
||||
'OQL' => "SELECT Contact WHERE org_id NOT IN ('1')",
|
||||
'ExpectedOQL' => "SELECT `Contact` FROM Contact AS `Contact` WHERE (`Contact`.`org_id` NOT IN ('1'))",
|
||||
'ExpectedCriterion' => array(array('widget' => 'raw', 'operator' => 'NOT IN')),
|
||||
),
|
||||
'key IN' => array(
|
||||
'OQL' => "SELECT Contact WHERE org_id IN ('1')",
|
||||
'ExpectedOQL' => "SELECT `Contact` FROM Contact AS `Contact` WHERE (`Contact`.`org_id` = '1')",
|
||||
'ExpectedCriterion' => array(array('widget' => 'hierarchical_key', 'operator' => 'IN')),
|
||||
),
|
||||
'key empty' => array(
|
||||
'OQL' => "SELECT Person WHERE location_id = '0'",
|
||||
'ExpectedOQL' => "SELECT `Person` FROM Person AS `Person` WHERE (`Person`.`location_id` = '0')",
|
||||
'ExpectedCriterion' => array(array('widget' => 'external_key', 'operator' => 'IN', 'values' => array(array('value' => '0')))),
|
||||
),
|
||||
'Double field' => array(
|
||||
'OQL' => "SELECT UserRequest AS u WHERE u.close_date > u.start_date",
|
||||
'ExpectedOQL' => "SELECT `u` FROM UserRequest AS `u` WHERE (`u`.`close_date` > `u`.`start_date`)",
|
||||
'ExpectedCriterion' => array(array('widget' => 'raw')),
|
||||
),
|
||||
'Date relative 1' => array(
|
||||
'OQL' => "SELECT UserRequest WHERE DATE_SUB(NOW(), INTERVAL 14 DAY) < start_date",
|
||||
'ExpectedOQL' => "SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (DATE_SUB(NOW(), INTERVAL 14 DAY) < `UserRequest`.`start_date`)",
|
||||
'ExpectedCriterion' => array(array('widget' => 'raw')),
|
||||
),
|
||||
'Date relative 2' => array(
|
||||
'OQL' => "SELECT Contract AS c WHERE c.end_date > NOW() AND c.end_date < DATE_ADD(NOW(), INTERVAL 30 DAY)",
|
||||
'ExpectedOQL' => "SELECT `c` FROM Contract AS `c` WHERE ((`c`.`end_date` < DATE_ADD(NOW(), INTERVAL 30 DAY)) AND (`c`.`end_date` > NOW()))",
|
||||
'ExpectedCriterion' => array(array('widget' => 'raw'), array('widget' => 'raw')),
|
||||
),
|
||||
'Date relative 3' => array(
|
||||
'OQL' => "SELECT UserRequest AS u WHERE u.close_date > DATE_ADD(u.start_date, INTERVAL 8 HOUR)",
|
||||
'ExpectedOQL' => "SELECT `u` FROM UserRequest AS `u` WHERE (`u`.`close_date` > DATE_ADD(`u`.`start_date`, INTERVAL 8 HOUR))",
|
||||
'ExpectedCriterion' => array(),
|
||||
),
|
||||
'Date relative 4' => array(
|
||||
'OQL' => "SELECT UserRequest AS u WHERE u.start_date < DATE_SUB(NOW(), INTERVAL 60 MINUTE) AND u.status = 'new'",
|
||||
'ExpectedOQL' => "SELECT `u` FROM UserRequest AS `u` WHERE ((`u`.`start_date` < DATE_SUB(NOW(), INTERVAL 60 MINUTE)) AND (`u`.`status` = 'new'))",
|
||||
'ExpectedCriterion' => array(array('widget' => 'raw')),
|
||||
),
|
||||
'Date between 1' => array(
|
||||
'OQL' => "SELECT UserRequest WHERE start_date > '2017-01-01 00:00:00' AND '2018-01-01 00:00:00' >= start_date",
|
||||
'ExpectedOQL' => "SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`start_date` >= '2017-01-01 00:00:01') AND (`UserRequest`.`start_date` <= '2018-01-01 00:00:00'))",
|
||||
'ExpectedCriterion' => array(array('widget' => 'date_time', 'operator' => 'between_dates')),
|
||||
),
|
||||
'Date between 2' => 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",
|
||||
'ExpectedOQL' => "SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((((`UserRequest`.`org_id` = '3') AND (`UserRequest`.`start_date` >= '2017-01-01 00:00:01')) AND (`UserRequest`.`start_date` <= '2018-01-01 00:00:00')) AND (`UserRequest`.`status` = 'active'))",
|
||||
'ExpectedCriterion' => array(array('widget' => 'hierarchical_key', 'operator' => 'IN'), array('widget' => 'date_time', 'operator' => 'between_dates'), array('widget' => 'enum', 'operator' => 'IN')),
|
||||
),
|
||||
'Date between 3' => array(
|
||||
'OQL' => "SELECT UserRequest WHERE start_date >= '2017-01-01 00:00:00' AND '2017-01-01 00:00:00' >= start_date",
|
||||
'ExpectedOQL' => "SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`start_date` >= '2017-01-01 00:00:00') AND (`UserRequest`.`start_date` <= '2017-01-01 00:00:00'))",
|
||||
'ExpectedCriterion' => array(array('widget' => 'date_time', 'operator' => 'between_dates')),
|
||||
),
|
||||
'Date between 4' => array(
|
||||
'OQL' => "SELECT UserRequest WHERE start_date >= '2017-01-01 00:00:00' AND '2017-01-01 01:00:00' > start_date",
|
||||
'ExpectedOQL' => "SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`start_date` >= '2017-01-01 00:00:00') AND (`UserRequest`.`start_date` <= '2017-01-01 00:59:59'))",
|
||||
'ExpectedCriterion' => array(array('widget' => 'date_time', 'operator' => 'between_dates')),
|
||||
),
|
||||
'Date between 5' => array(
|
||||
'OQL' => "SELECT UserRequest WHERE start_date >= '2017-01-01 00:00:00' AND '2017-01-02 00:00:00' > start_date",
|
||||
'ExpectedOQL' => "SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`start_date` >= '2017-01-01 00:00:00') AND (`UserRequest`.`start_date` <= '2017-01-01 23:59:59'))",
|
||||
'ExpectedCriterion' => array(array('widget' => 'date_time', 'operator' => 'between_dates')),
|
||||
),
|
||||
'Date between 6' => array(
|
||||
'OQL' => "SELECT UserRequest WHERE start_date >= '2017-01-01' AND '2017-01-02' >= start_date",
|
||||
'ExpectedOQL' => "SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`start_date` >= '2017-01-01 00:00:00') AND (`UserRequest`.`start_date` <= '2017-01-02 00:00:00'))",
|
||||
'ExpectedCriterion' => array(array('widget' => 'date_time', 'operator' => 'between_dates')),
|
||||
),
|
||||
'Date between 7' => array(
|
||||
'OQL' => "SELECT CustomerContract WHERE ((start_date >= '2018-03-01') AND (start_date < '2018-04-01'))",
|
||||
'ExpectedOQL' => "SELECT `CustomerContract` FROM CustomerContract AS `CustomerContract` WHERE ((`CustomerContract`.`start_date` >= '2018-03-01') AND (`CustomerContract`.`start_date` <= '2018-03-31'))",
|
||||
'ExpectedCriterion' => array(array('widget' => 'date', 'operator' => 'between_dates')),
|
||||
),
|
||||
'Date =' => array(
|
||||
'OQL' => "SELECT CustomerContract WHERE (start_date = '2018-03-01')",
|
||||
'ExpectedOQL' => "SELECT `CustomerContract` FROM CustomerContract AS `CustomerContract` WHERE ((`CustomerContract`.`start_date` >= '2018-03-01') AND (`CustomerContract`.`start_date` <= '2018-03-01'))",
|
||||
'ExpectedCriterion' => array(array('widget' => 'date', 'operator' => 'between_dates')),
|
||||
),
|
||||
'Date =2' => array(
|
||||
'OQL' => "SELECT UserRequest WHERE (DATE_FORMAT(start_date, '%Y-%m-%d') = '2018-03-21')",
|
||||
'ExpectedOQL' => "SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`start_date` >= '2018-03-21 00:00:00') AND (`UserRequest`.`start_date` <= '2018-03-21 23:59:59'))",
|
||||
'ExpectedCriterion' => array(array('widget' => 'date_time', 'operator' => 'between_dates')),
|
||||
),
|
||||
'Num between 1' => array(
|
||||
'OQL' => "SELECT Server WHERE nb_u >= 0 AND 1 >= nb_u",
|
||||
'ExpectedOQL' => "SELECT `Server` FROM Server AS `Server` WHERE ((`Server`.`nb_u` >= '0') AND (`Server`.`nb_u` <= '1'))",
|
||||
'ExpectedCriterion' => array(array('widget' => 'numeric', 'operator' => 'between')),
|
||||
),
|
||||
'Num ISNULL' => array(
|
||||
'OQL' => "SELECT Server WHERE ISNULL(nb_u)",
|
||||
'ExpectedOQL' => "SELECT `Server` FROM Server AS `Server` WHERE ISNULL(`Server`.`nb_u`)",
|
||||
'ExpectedCriterion' => array(array('widget' => 'numeric', 'operator' => 'empty')),
|
||||
),
|
||||
'Hierarchical below' => array(
|
||||
'OQL' => "SELECT Person AS P JOIN Organization AS Node ON P.org_id = Node.id JOIN Organization AS Root ON Node.parent_id BELOW Root.id WHERE Root.id=1",
|
||||
'ExpectedOQL' => "SELECT `P` FROM Person AS `P` JOIN Organization AS `Node` ON `P`.org_id = `Node`.id JOIN Organization AS `Root` ON `Node`.parent_id BELOW `Root`.id WHERE (`Root`.`id` = 1)",
|
||||
'ExpectedCriterion' => array(array('widget' => 'raw')),
|
||||
),
|
||||
'IP range' => array(
|
||||
'OQL' => "SELECT DatacenterDevice AS dev WHERE INET_ATON(dev.managementip) > INET_ATON('10.22.32.224') AND INET_ATON(dev.managementip) < INET_ATON('10.22.32.255')",
|
||||
'ExpectedOQL' => "SELECT `dev` FROM DatacenterDevice AS `dev` WHERE ((INET_ATON(`dev`.`managementip`) < INET_ATON('10.22.32.255')) AND (INET_ATON(`dev`.`managementip`) > INET_ATON('10.22.32.224')))",
|
||||
'ExpectedCriterion' => array(array('widget' => 'raw')),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function array_diff_assoc_recursive($array1, $array2)
|
||||
{
|
||||
foreach($array1 as $key => $value)
|
||||
{
|
||||
if (is_array($value))
|
||||
{
|
||||
if (!isset($array2[$key]))
|
||||
{
|
||||
$difference[$key] = $value;
|
||||
}
|
||||
elseif (!is_array($array2[$key]))
|
||||
{
|
||||
$difference[$key] = $value;
|
||||
}
|
||||
else
|
||||
{
|
||||
$new_diff = $this->array_diff_assoc_recursive($value, $array2[$key]);
|
||||
if ($new_diff !== false)
|
||||
{
|
||||
$difference[$key] = $new_diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif (!array_key_exists($key, $array2) || $array2[$key] != $value)
|
||||
{
|
||||
$difference[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return !isset($difference) ? false : $difference;
|
||||
}
|
||||
}
|
||||
87
test/application/search/CriterionParserTest.php
Normal file
87
test/application/search/CriterionParserTest.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?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);
|
||||
$oSearch = CriterionParser::Parse($sBaseOql, $aCriterion);
|
||||
|
||||
//$this->debug($oSearch);
|
||||
|
||||
$this->assertEquals("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`start_date` > '2017-01-01') AND (`UserRequest`.`start_date` < '2018-01-01'))", $oSearch->ToOQL());
|
||||
}
|
||||
}
|
||||
125
test/application/search/SearchFormTest.php
Normal file
125
test/application/search/SearchFormTest.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?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\Test\UnitTest\Application\Search;
|
||||
|
||||
use Combodo\iTop\Application\Search\SearchForm;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use Exception;
|
||||
|
||||
class SearchFormTest extends ItopDataTestCase
|
||||
{
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
require_once(APPROOT."sources/application/search/searchform.class.inc.php");
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider GetFieldsProvider
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testGetFields($sOQL, $iNum, $sList)
|
||||
{
|
||||
$oSearchForm = new SearchForm();
|
||||
$oSearch = \DBSearch::FromOQL($sOQL);
|
||||
$aFields = $oSearchForm->GetFields(new \DBObjectSet($oSearch));
|
||||
$this->debug($sOQL);
|
||||
$this->debug(json_encode($aFields, JSON_PRETTY_PRINT));
|
||||
$this->assertCount($iNum, $aFields[$sList]);
|
||||
|
||||
}
|
||||
|
||||
public function GetFieldsProvider()
|
||||
{
|
||||
return array(
|
||||
array("SELECT Contact", 8, 'zlist'),
|
||||
array("SELECT Contact AS C WHERE C.status = 'active'", 3, 'others'),
|
||||
array("SELECT Person", 12, 'zlist'),
|
||||
array(
|
||||
"SELECT Person AS p JOIN UserRequest AS u ON u.agent_id = p.id WHERE u.status != 'closed'",
|
||||
12,
|
||||
'zlist'
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @dataProvider GetCriterionProvider
|
||||
*
|
||||
* @param $sOQL
|
||||
* @param $iOrCount
|
||||
*
|
||||
*/
|
||||
public function testGetCriterion($sOQL, $iOrCount)
|
||||
{
|
||||
$oSearchForm = new SearchForm();
|
||||
try
|
||||
{
|
||||
$oSearch = \DBSearch::FromOQL($sOQL);
|
||||
$aFields = $oSearchForm->GetFields(new \DBObjectSet($oSearch));
|
||||
$aCriterion = $oSearchForm->GetCriterion($oSearch, $aFields);
|
||||
} catch (\OQLException $e)
|
||||
{
|
||||
$this->assertTrue(false);
|
||||
|
||||
return;
|
||||
}
|
||||
$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']);
|
||||
}
|
||||
|
||||
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 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 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),
|
||||
array(
|
||||
'OQL' => "SELECT FunctionalCI WHERE ((business_criticity IN ('high', 'medium')) OR ISNULL(business_criticity)) AND 1",
|
||||
1
|
||||
),
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,6 @@ namespace Combodo\iTop\Test\UnitTest\iTopTickets;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use Exception;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
|
||||
/**
|
||||
@@ -799,7 +798,7 @@ class ItopTicketTest extends ItopDataTestCase
|
||||
$oServer2->GetKey() => 'manual');
|
||||
$this->CheckFunctionalCIList($oTicket2, $aWaitedCIList);
|
||||
$this->CheckContactList($oTicket2);
|
||||
$this->assertEquals(5, $oTicket2->Get('functionalcis_list')->Count());
|
||||
$this->assertEquals(2, $oTicket2->Get('functionalcis_list')->Count());
|
||||
$this->assertEquals(1, $oTicket2->Get('contacts_list')->Count());
|
||||
|
||||
// The first ticket is not impacted
|
||||
@@ -910,7 +909,7 @@ class ItopTicketTest extends ItopDataTestCase
|
||||
$oServer2->GetKey() => 'manual');
|
||||
$this->CheckFunctionalCIList($oTicket2, $aWaitedCIList);
|
||||
$this->CheckContactList($oTicket2);
|
||||
$this->assertEquals(6, $oTicket2->Get('functionalcis_list')->Count());
|
||||
$this->assertEquals(3, $oTicket2->Get('functionalcis_list')->Count());
|
||||
$this->assertEquals(1, $oTicket2->Get('contacts_list')->Count());
|
||||
|
||||
// The first ticket is not impacted
|
||||
|
||||
Reference in New Issue
Block a user