diff --git a/application/nicewebpage.class.inc.php b/application/nicewebpage.class.inc.php index 2a622f9bd..a56de6f76 100644 --- a/application/nicewebpage.class.inc.php +++ b/application/nicewebpage.class.inc.php @@ -56,6 +56,7 @@ class NiceWebPage extends WebPage $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'); diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 489178940..b688c5151 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -4948,8 +4948,34 @@ 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; + } - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW; static public function ListExpectedParams() { @@ -5002,6 +5028,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(''); diff --git a/dictionaries/en.dictionary.itop.ui.php b/dictionaries/en.dictionary.itop.ui.php index a7849b8f0..476ac03b7 100644 --- a/dictionaries/en.dictionary.itop.ui.php +++ b/dictionaries/en.dictionary.itop.ui.php @@ -1431,10 +1431,9 @@ When associated with a trigger, each action is given an "order" number, specifyi '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]', - // - String widget - 'UI:Search:Criteria:Title:String:Contains' => '%1$s contains %2$s', - 'UI:Search:Criteria:Title:String:StartsWith' => '%1$s starts with %2$s', - 'UI:Search:Criteria:Title:String:EndsWith' => '%1$s ends with %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', // - Numeric widget // 'UI:Search:Criteria:Title:Numeric:Equals' => '%1$s equals %2$s', // - DateTime widget diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 742c4c857..9e63d6c0d 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -1216,10 +1216,9 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé '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', - // - String widget - 'UI:Search:Criteria:Title:String:Contains' => '%1$s contient %2$s', - 'UI:Search:Criteria:Title:String:StartsWith' => '%1$s commence par %2$s', - 'UI:Search:Criteria:Title:String:EndsWith' => '%1$s fini par %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', // - 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é', diff --git a/js/search/search_form_criteria_external_field.js b/js/search/search_form_criteria_external_field.js new file mode 100644 index 000000000..e032e7aac --- /dev/null +++ b/js/search/search_form_criteria_external_field.js @@ -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 + //------------------ + }); +}); diff --git a/sources/application/search/criterionconversion/criteriontooql.class.inc.php b/sources/application/search/criterionconversion/criteriontooql.class.inc.php index 6c237df07..140e2cf2c 100644 --- a/sources/application/search/criterionconversion/criteriontooql.class.inc.php +++ b/sources/application/search/criterionconversion/criteriontooql.class.inc.php @@ -124,9 +124,14 @@ class CriterionToOQL extends CriterionConversionAbstract protected static function EmptyToOql($sRef, $aCriteria) { - if (isset($aCriteria['widget']) && ($aCriteria['widget'] == AttributeDefinition::SEARCH_WIDGET_TYPE_NUMERIC)) + if (isset($aCriteria['widget'])) { - return "ISNULL({$sRef})"; + switch ($aCriteria['widget']) + { + case AttributeDefinition::SEARCH_WIDGET_TYPE_NUMERIC: + case AttributeDefinition::SEARCH_WIDGET_TYPE_EXTERNAL_FIELD: + return "ISNULL({$sRef})"; + } } return "({$sRef} = '')"; diff --git a/sources/application/search/criterionconversion/criteriontosearchform.class.inc.php b/sources/application/search/criterionconversion/criteriontosearchform.class.inc.php index 666538b28..f85dcf9c3 100644 --- a/sources/application/search/criterionconversion/criteriontosearchform.class.inc.php +++ b/sources/application/search/criterionconversion/criteriontosearchform.class.inc.php @@ -63,6 +63,7 @@ class CriterionToSearchForm extends CriterionConversionAbstract $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', @@ -80,6 +81,7 @@ class CriterionToSearchForm extends CriterionConversionAbstract } $aCriteria['is_removable'] = $bIsRemovable; + $sClass = ''; if (isset($aCriteria['ref'])) { $aRef = explode('.', $aCriteria['ref']); @@ -420,6 +422,46 @@ class CriterionToSearchForm extends CriterionConversionAbstract 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']) diff --git a/sources/application/search/searchform.class.inc.php b/sources/application/search/searchform.class.inc.php index 140011dac..456317c25 100644 --- a/sources/application/search/searchform.class.inc.php +++ b/sources/application/search/searchform.class.inc.php @@ -417,8 +417,15 @@ class SearchForm { if (!is_null($oAttDef) && ($oAttDef->GetSearchType() != AttributeDefinition::SEARCH_WIDGET_TYPE_RAW)) { - $sLabel = $oAttDef->GetLabel(); - + if (method_exists($oAttDef, 'GetLabelForSearchField')) + { + $sLabel = $oAttDef->GetLabelForSearchField(); + } + else + { + $sLabel = $oAttDef->GetLabel(); + } + if (method_exists($oAttDef, 'GetTargetClass')) { $sTargetClass = $oAttDef->GetTargetClass();