diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index c605428675..aed1eeed63 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -1654,12 +1654,8 @@ EOF * @throws \CoreException * @throws \DictExceptionMissingString */ - public static function GetFormElementForField( - $oPage, $sClass, $sAttCode, $oAttDef, $value = '', $sDisplayValue = '', $iId = '', $sNameSuffix = '', - $iFlags = 0, $aArgs = array(), $bPreserveCurrentValue = true - ) { - static $iInputId = 0; - $sFieldPrefix = ''; + public static function GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value = '', $sDisplayValue = '', $iId = '', $sNameSuffix = '', $iFlags = 0, $aArgs = array(), $bPreserveCurrentValue = true) + { $sFormPrefix = isset($aArgs['formPrefix']) ? $aArgs['formPrefix'] : ''; $sFieldPrefix = isset($aArgs['prefix']) ? $sFormPrefix.$aArgs['prefix'] : $sFormPrefix; if ($sDisplayValue == '') @@ -2054,16 +2050,23 @@ EOF break; - case 'ObjectAttcodeSet': - $iFieldSize = $oAttDef->GetMaxSize(); - if (is_array($sDisplayValue)) + case 'ClassAttCodeSet': + $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'/js/selectize.min.js'); + $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/selectize.default.css'); + $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'/js/jquery.itop-tagset-widget.js'); + + $oPage->add_dict_entry('Core:AttributeTagSet:placeholder'); + + /** @var \ormSet $value */ + if (isset($aArgs['this'])) { - $sDisplayValue = implode(', ', $sDisplayValue); + $oAttDef->SetTargetClass($aArgs['this']); } - $sHTMLValue = "
{$sValidationSpan}{$sReloadSpan}"; - $aEventsList[] ='validate'; - $aEventsList[] ='keyup'; - $aEventsList[] ='change'; + $sJson = $oAttDef->GetJsonForWidget($value); + $sInputId = "attr_{$sFormPrefix}{$sAttCode}"; + $sHTMLValue = "
{$sValidationSpan}{$sReloadSpan}"; + $sScript = "$('#$sInputId').tagset_widget();"; + $oPage->add_ready_script($sScript); break; case 'String': @@ -3315,6 +3318,17 @@ EOF $this->Set($sAttCode, $oTagSet); break; + case 'ClassAttCodeSet': + /** @var ormSet $oSet */ + $oSet = $this->Get($sAttCode); + if (is_null($oSet)) + { + $oSet = new ormSet(get_class($this), $sAttCode); + } + $oSet->ApplyDelta($value); + $this->Set($sAttCode, $oSet); + break; + default: if (!is_null($value)) { @@ -3506,6 +3520,7 @@ EOF break; case 'TagSet': + case 'ClassAttCodeSet': $sTagSetJson = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data'); $value = json_decode($sTagSetJson, true); break; @@ -4077,7 +4092,7 @@ EOF $sTip = addslashes($sTip); $sReadyScript .= "$('#multi_values_$sAttCode').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );"; - if ($oAttDef->GetEditClass() == 'TagSet') + if (($oAttDef->GetEditClass() == 'TagSet') || ($oAttDef->GetEditClass() == 'ClassAttCodeSet')) { // Set the value by adding the values to the first one reset($aMultiValues); diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 9d9e3e473e..3d56fe9aa2 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -31,6 +31,7 @@ require_once('ormstopwatch.class.inc.php'); require_once('ormpassword.class.inc.php'); require_once('ormcaselog.class.inc.php'); require_once('ormlinkset.class.inc.php'); +require_once('ormset.class.inc.php'); require_once('ormtagset.class.inc.php'); require_once('htmlsanitizer.class.inc.php'); require_once(APPROOT.'sources/autoload.php'); @@ -6705,23 +6706,54 @@ class AttributeExternalField extends AttributeDefinition * @see TagSetFieldData * @since 2.6 N°931 tag fields */ -class AttributeTagSet extends AttributeDBFieldVoid +class AttributeTagSet extends AttributeSet { const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_TAG_SET; static public function ListExpectedParams() { - return array_merge(parent::ListExpectedParams(), array('is_null_allowed', 'tag_max_nb', 'tag_code_max_len')); + return array_merge(parent::ListExpectedParams(), array('tag_code_max_len')); } - public function GetDefaultValue(DBObject $oHostObject = null) + /** + * @param \ormTagSet $oValue + * + * @return string JSON to be used in the itop.tagset_widget JQuery widget + * @throws \CoreException + */ + public function GetJsonForWidget($oValue) { - return null; - } + $aJson = array(); - public function IsNullAllowed() - { - return $this->Get("is_null_allowed"); + // possible_values + $aTagSetObjectData = $this->GetAllowedValues(); + $aTagSetKeyValData = array(); + foreach($aTagSetObjectData as $sTagCode => $sTagLabel) + { + $aTagSetKeyValData[] = [ + 'code' => $sTagCode, + 'label' => $sTagLabel, + ]; + } + $aJson['possible_values'] = $aTagSetKeyValData; + + if (is_null($oValue)) + { + $aJson['partial_values'] = array(); + $aJson['orig_value'] = array(); + } + else + { + $aJson['partial_values'] = $oValue->GetModifiedTags(); + $aJson['orig_value'] = array_merge($oValue->GetValue(), $oValue->GetModifiedTags()); + } + $aJson['added'] = array(); + $aJson['removed'] = array(); + + $iMaxTags = $this->GetMaxItems(); + $aJson['max_tags_allowed'] = $iMaxTags; + + return json_encode($aJson); } /** @@ -6745,7 +6777,7 @@ class AttributeTagSet extends AttributeDBFieldVoid } else { - $oTagSet = new ormTagSet($sClass, $sAttCode, $this->GetTagMaxNb()); + $oTagSet = new ormTagSet($sClass, $sAttCode, $this->GetMaxItems()); } $aGoodTags = array(); foreach($aTagCodes as $sTagCode) @@ -6757,7 +6789,7 @@ class AttributeTagSet extends AttributeDBFieldVoid if ($oTagSet->IsValidTag($sTagCode)) { $aGoodTags[] = $sTagCode; - if (!$bNoLimit && (count($aGoodTags) === $this->GetTagMaxNb())) + if (!$bNoLimit && (count($aGoodTags) === $this->GetMaxItems())) { // extra and bad tags are ignored break; @@ -6769,11 +6801,6 @@ class AttributeTagSet extends AttributeDBFieldVoid return $oTagSet; } - public function GetTagMaxNb() - { - return $this->Get('tag_max_nb'); - } - public function GetTagCodeMaxLength() { return $this->Get('tag_code_max_len'); @@ -6800,17 +6827,9 @@ class AttributeTagSet extends AttributeDBFieldVoid return ''; } - protected function GetSQLCol($bFullSpec = false) - { - $iLen = $this->GetMaxSize(); - return "VARCHAR($iLen)" - .CMDBSource::GetSqlStringColumnDefinition() - .($bFullSpec ? $this->GetSQLColSpec() : ''); - } - public function GetMaxSize() { - return $iLen = ($this->GetTagMaxNb() * $this->GetTagCodeMaxLength()) + 1; + return $iLen = ($this->GetMaxItems() * $this->GetTagCodeMaxLength()) + 1; } public function RequiresIndex() @@ -6862,20 +6881,6 @@ class AttributeTagSet extends AttributeDBFieldVoid return $this->GetExistingTagsFromString($sValue); } - /** - * @param $aCols - * @param string $sPrefix - * - * @return mixed - * @throws \Exception - */ - public function FromImportToValue($aCols, $sPrefix = '') - { - $sValue = $aCols["$sPrefix"]; - - return $this->MakeRealValue($sValue, null); - } - /** * force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing! * @@ -6887,7 +6892,7 @@ class AttributeTagSet extends AttributeDBFieldVoid */ public function MakeRealValue($proposedValue, $oHostObj) { - $oTagSet = new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetTagMaxNb()); + $oTagSet = new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems()); if (is_string($proposedValue) && !empty($proposedValue)) { $proposedValue = trim("$proposedValue"); @@ -6924,7 +6929,7 @@ class AttributeTagSet extends AttributeDBFieldVoid if ($bLocalizedValue && !empty($sProposedValue)) { $oTagSet = new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), - $this->GetCode(), $this->GetTagMaxNb()); + $this->GetCode(), $this->GetMaxItems()); $aLabels = explode($sSepItem, $sProposedValue); $aCodes = array(); foreach($aLabels as $sTagLabel) @@ -6942,7 +6947,7 @@ class AttributeTagSet extends AttributeDBFieldVoid public function GetNullValue() { - return new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetTagMaxNb()); + return new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems()); } public function IsNull($proposedValue) @@ -6989,18 +6994,6 @@ class AttributeTagSet extends AttributeDBFieldVoid )); } - /** - * @param string $sValue - * @param null $oHostObj - * - * @return string - * @throws \CoreWarning - */ - public function GetAsPlainText($sValue, $oHostObj = null) - { - return $this->GetValueLabel($sValue); - } - /** * @param $value * @@ -7070,7 +7063,7 @@ class AttributeTagSet extends AttributeDBFieldVoid $aTagCodes = explode(' ', $value); $aValues = array(); $oTagSet = new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), - $this->GetCode(), $this->GetTagMaxNb()); + $this->GetCode(), $this->GetMaxItems()); foreach($aTagCodes as $sTagCode) { try @@ -7364,7 +7357,7 @@ class AttributeTagSet extends AttributeDBFieldVoid */ public function FromJSONToValue($json) { - $oSet = new ormTagSet($this->GetHostClass(), $this->GetCode(), $this->GetTagMaxNb()); + $oSet = new ormTagSet($this->GetHostClass(), $this->GetCode(), $this->GetMaxItems()); $oSet->SetValue($json); return $oSet; @@ -9360,7 +9353,48 @@ class AttributeSet extends AttributeDBFieldVoid { static public function ListExpectedParams() { - return array_merge(parent::ListExpectedParams(), array('is_null_allowed')); + return array_merge(parent::ListExpectedParams(), array('is_null_allowed', 'max_items')); + } + + /** + * @param \ormSet $oValue + * + * @return string JSON to be used in the itop.tagset_widget JQuery widget + * @throws \CoreException + */ + public function GetJsonForWidget($oValue) + { + $aJson = array(); + + // possible_values + $aSetObjectData = $this->GetAllowedValues(); + $aSetKeyValData = array(); + foreach($aSetObjectData as $sCode => $sLabel) + { + $aSetKeyValData[] = [ + 'code' => $sCode, + 'label' => $sLabel, + ]; + } + $aJson['possible_values'] = $aSetKeyValData; + + if (is_null($oValue)) + { + $aJson['partial_values'] = array(); + $aJson['orig_value'] = array(); + } + else + { + $aJson['partial_values'] = $oValue->GetModified(); + $aJson['orig_value'] = array_merge($oValue->GetValues(), $oValue->GetModified()); + } + $aJson['added'] = array(); + $aJson['removed'] = array(); + + $iMaxTags = $this->GetMaxItems(); + $aJson['max_tags_allowed'] = $iMaxTags; + + return json_encode($aJson); } public function GetDefaultValue(DBObject $oHostObject = null) @@ -9384,6 +9418,10 @@ class AttributeSet extends AttributeDBFieldVoid { return $value; } + if ($value instanceof ormSet) + { + $value = $value->GetValues(); + } if (is_array($value)) { return implode(', ', $value); @@ -9409,7 +9447,6 @@ class AttributeSet extends AttributeDBFieldVoid * @param string $sPrefix * * @return mixed - * @throws \CoreException * @throws \Exception */ public function FromSQLToValue($aCols, $sPrefix = '') @@ -9444,30 +9481,24 @@ class AttributeSet extends AttributeDBFieldVoid */ public function MakeRealValue($proposedValue, $oHostObj) { - if (empty($proposedValue)) - { - return array(); - } - if (is_string($proposedValue)) + $oSet = new ormSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems()); + if (is_string($proposedValue) && !empty($proposedValue)) { $proposedValue = trim("$proposedValue"); - $proposedValue = explode(',', $proposedValue); $aValues = array(); - foreach($proposedValue as $sValue) + foreach (explode(',', $proposedValue) as $sCode) { - $sValue = trim($sValue); - $aValues[$sValue] = $sValue; + $sValue = trim($sCode); + $aValues[] = $sValue; } - return $aValues; + $oSet->SetValues($aValues); } - - if (is_array($proposedValue)) + elseif ($proposedValue instanceof ormSet) { - return $proposedValue; + $oSet = $proposedValue; } - throw new CoreUnexpectedValue("Wrong format"); - + return $oSet; } /** @@ -9488,14 +9519,25 @@ class AttributeSet extends AttributeDBFieldVoid return $this->MakeRealValue($sProposedValue, null); } + /** + * @return null|\ormSet + * @throws \CoreException + * @throws \Exception + */ public function GetNullValue() { - return null; + return new ormSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems()); } public function IsNull($proposedValue) { - return empty($proposedValue); + if (empty($proposedValue)) + { + return true; + } + + /** @var \ormSet $proposedValue */ + return $proposedValue->Count() == 0; } /** @@ -9504,11 +9546,14 @@ class AttributeSet extends AttributeDBFieldVoid * @param $sValue * * @return string label corresponding to the given value (in plain text) - * @throws \CoreWarning * @throws \Exception */ public function GetValueLabel($sValue) { + if ($sValue instanceof ormSet) + { + $sValue = $sValue->GetValues(); + } if (is_array($sValue)) { return implode(', ', $sValue); @@ -9521,7 +9566,7 @@ class AttributeSet extends AttributeDBFieldVoid * @param null $oHostObj * * @return string - * @throws \CoreWarning + * @throws \Exception */ public function GetAsPlainText($sValue, $oHostObj = null) { @@ -9532,7 +9577,6 @@ class AttributeSet extends AttributeDBFieldVoid * @param $value * * @return string - * @throws \CoreWarning */ public function ScalarToSQL($value) { @@ -9540,6 +9584,10 @@ class AttributeSet extends AttributeDBFieldVoid { return ''; } + if ($value instanceof ormSet) + { + $value = $value->GetValues(); + } if (is_array($value)) { return implode(', ', $value); @@ -9554,29 +9602,54 @@ class AttributeSet extends AttributeDBFieldVoid * * @return string|null * - * @throws \CoreException * @throws \Exception */ public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) { + if ($value instanceof ormSet) + { + $value = $value->GetValues(); + } if (is_array($value)) { return implode(', ', $value); } return $value; } + + public function GetMaxItems() + { + return $this->Get('max_items'); + } } -class AttributeObjectAttCodeSet extends AttributeSet +class AttributeClassAttCodeSet extends AttributeSet { + private $sTargetClass; + + /** + * @param \DBObject $oHostObj + * + * @throws \CoreException + */ + public function SetTargetClass($oHostObj) + { + if (!empty($oHostObj)) + { + $sTargetClass = $this->Get('class_field'); + $sClass = $oHostObj->Get($sTargetClass); + $this->sTargetClass = $sClass; + } + } + static public function ListExpectedParams() { - return array_merge(parent::ListExpectedParams(), array('class')); + return array_merge(parent::ListExpectedParams(), array('class_field', 'attribute_definition_list')); } public function GetEditClass() { - return "ObjectAttcodeSet"; + return "ClassAttCodeSet"; } public function GetMaxSize() @@ -9584,6 +9657,43 @@ class AttributeObjectAttCodeSet extends AttributeSet return 255; } + public function GetAllowedValues($aArgs = array(), $sContains = '') + { + if (!empty($this->sTargetClass)) + { + $aAllowedAttributes = array(); + $aAllAttributes = MetaModel::GetAttributesList($this->sTargetClass); + $sAttDefList = $this->Get('attribute_definition_list'); + if (!empty($sAttDefList)) + { + $aAllowedDefs = array(); + foreach(explode(',', $sAttDefList) as $sAttDefName) + { + $sAttDefName = trim($sAttDefName); + $aAllowedDefs[$sAttDefName] = $sAttDefName; + } + foreach($aAllAttributes as $sAttCode) + { + $oAttDef = MetaModel::GetAttributeDef($this->sTargetClass, $sAttCode); + if (isset($aAllowedDefs[get_class($oAttDef)])) + { + $aAllowedAttributes[$sAttCode] = MetaModel::GetLabel($this->sTargetClass, $sAttCode); + } + } + } + else + { + foreach($aAllAttributes as $sAttCode) + { + $aAllowedAttributes[$sAttCode] = MetaModel::GetLabel($this->sTargetClass, $sAttCode); + } + } + return $aAllowedAttributes; + } + + return null; + } + /** * force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing! * @@ -9595,45 +9705,68 @@ class AttributeObjectAttCodeSet extends AttributeSet */ public function MakeRealValue($proposedValue, $oHostObj) { - $aAllowedAttributes = array(); - $sClass = ''; - if (!empty($oHostObj)) - { - $sTargetClass = $this->Get('class'); - $sClass = $oHostObj->Get($sTargetClass); - $aAllowedAttributes = MetaModel::GetAttributesList($sClass); - } + $oSet = new ormSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems()); + $this->SetTargetClass($oHostObj); + $aAllowedAttributes = $this->GetAllowedValues(); if (is_string($proposedValue) && !empty($proposedValue)) { $proposedValue = trim("$proposedValue"); - $proposedValue = explode(',', $proposedValue); $aValues = array(); - foreach($proposedValue as $sValue) + foreach(explode(',', $proposedValue) as $sValue) { $sAttCode = trim($sValue); - if (empty($aAllowedAttributes) || in_array($sAttCode, $aAllowedAttributes)) + if (empty($aAllowedAttributes) || isset($aAllowedAttributes[$sAttCode])) { $aValues[$sAttCode] = $sAttCode; } else { - throw new CoreUnexpectedValue("The attribute {$sAttCode} does not exist in class {$sClass}"); + throw new CoreUnexpectedValue("The attribute {$sAttCode} does not exist in class {$this->sTargetClass}"); } } - return $aValues; + $oSet->SetValues($aValues); + } + elseif ($proposedValue instanceof ormSet) + { + $oSet = $proposedValue; } - return $proposedValue; + return $oSet; } + /** + * @param $value + * @param \DBObject $oHostObject + * @param bool $bLocalize + * + * @return string|null + * + * @throws \Exception + */ + public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) + { + $this->SetTargetClass($oHostObject); + if ($value instanceof ormSet) + { + $value = $value->GetValues(); + } + if (is_array($value)) + { + if (!empty($this->sTargetClass) && $bLocalize) + { + $aLocalizedValues = array(); + foreach($value as $sAttCode) + { + $aLocalizedValues[] = MetaModel::GetLabel($this->sTargetClass, $sAttCode); + } + $value = $aLocalizedValues; + } + return implode(', ', $value); + } + return $value; + } } -/** - * The attribute dedicated to the friendly name automatic attribute (not written) - * - * @package iTopORM - */ - /** * The attribute dedicated to the friendly name automatic attribute (not written) * diff --git a/core/dbobject.class.php b/core/dbobject.class.php index df40aaf368..aaad1cb105 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -1301,6 +1301,34 @@ abstract class DBObject implements iDisplay return "Bad type"; } + elseif ($oAtt instanceof AttributeClassAttCodeSet) + { + if (is_string($toCheck)) + { + $oTag = new ormSet(get_class($this), $sAttCode); + try + { + $aValues = array(); + foreach(explode(',', $toCheck) as $sValue) + { + $aValues[] = trim($sValue); + } + $oTag->SetValues($aValues); + } catch (Exception $e) + { + return "Set value '$toCheck' is not a valid set"; + } + + return true; + } + + if ($toCheck instanceof ormSet) + { + return true; + } + + return "Bad type"; + } elseif ($oAtt->IsScalar()) { $aValues = $oAtt->GetAllowedValues($this->ToArgsForQuery()); diff --git a/core/ormset.class.inc.php b/core/ormset.class.inc.php new file mode 100644 index 0000000000..99efeb5d94 --- /dev/null +++ b/core/ormset.class.inc.php @@ -0,0 +1,379 @@ + + * + */ + +/** + * Created by PhpStorm. + * Date: 24/08/2018 + * Time: 14:35 + */ +final class ormSet +{ + private $sClass; // class of the field + private $sAttCode; // attcode of the field + private $aOriginalObjects = null; + + /** + * Object from the original set, minus the removed objects + */ + private $aPreserved = array(); + + /** + * New items + */ + private $aAdded = array(); + + /** + * Removed items + */ + private $aRemoved = array(); + + /** + * Modified items (mass edit) + */ + private $aModified = array(); + + /** + * @var int Max number of tags in collection + */ + private $iLimit; + + /** + * __toString magical function overload. + */ + public function __toString() + { + $aValue = $this->GetValues(); + if (!empty($aValue)) + { + return implode(', ', $aValue); + } + else + { + return ' '; + } + } + + /** + * ormSet constructor. + * + * @param string $sClass + * @param string $sAttCode + * @param int $iLimit + * + * @throws \Exception + */ + public function __construct($sClass, $sAttCode, $iLimit = 12) + { + $this->sAttCode = $sAttCode; + + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if (!$oAttDef instanceof AttributeSet) + { + throw new Exception("ormSet: field {$sClass}:{$sAttCode} is not a set"); + } + $this->sClass = $sClass; + $this->iLimit = $iLimit; + } + + /** + * @return string + */ + public function GetClass() + { + return $this->sClass; + } + + /** + * @return string + */ + public function GetAttCode() + { + return $this->sAttCode; + } + + /** + * + * @param array $aItems + * + * @throws \CoreException + * @throws \CoreUnexpectedValue when a code is invalid + */ + public function SetValues($aItems) + { + if (!is_array($aItems)) + { + throw new CoreUnexpectedValue("Wrong value {$aItems} for {$this->sClass}:{$this->sAttCode}"); + } + + $oValues = array(); + $iCount = 0; + $bError = false; + foreach($aItems as $oItem) + { + $iCount++; + if (($this->iLimit != 0) && ($iCount > $this->iLimit)) + { + $bError = true; + continue; + } + $oValues[] = $oItem; + } + + $this->aPreserved = &$oValues; + $this->aRemoved = array(); + $this->aAdded = array(); + $this->aModified = array(); + $this->aOriginalObjects = $oValues; + + if ($bError) + { + throw new CoreException("Maximum number of items ({$this->iLimit}) reached for {$this->sClass}:{$this->sAttCode}"); + } + } + + public function Count() + { + return count($this->aPreserved) + count($this->aAdded) - count($this->aRemoved); + } + + /** + * @return array of codes + */ + public function GetValues() + { + $aValues = array_merge($this->aPreserved, $this->aAdded); + + sort($aValues); + + return $aValues; + } + + /** + * @return array of tag labels indexed by code for only the added tags + */ + private function GetAdded() + { + return $this->aAdded; + } + + /** + * @return array of tag labels indexed by code for only the removed tags + */ + private function GetRemoved() + { + return $this->aRemoved; + } + + /** Get the delta with another ItemSet + * + * $aDelta['added] = array of tag codes for only the added tags + * $aDelta['removed'] = array of tag codes for only the removed tags + * + * @param \ormSet $oOtherSet + * + * @return array + * + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \Exception + */ + public function GetDelta(ormSet $oOtherSet) + { + $oSet = new ormSet($this->sClass, $this->sAttCode); + // Set the initial value + $aOrigItems = $this->GetValues(); + $oSet->SetValues($aOrigItems); + + // now remove everything + foreach($aOrigItems as $oItem) + { + $oSet->Remove($oItem); + } + + // now add the tags of the other ItemSet + foreach($oOtherSet->GetValues() as $oItem) + { + $oSet->Add($oItem); + } + + $aDelta = array(); + $aDelta['added'] = $oSet->GetAdded(); + $aDelta['removed'] = $oSet->GetRemoved(); + + return $aDelta; + } + + /** + * @return string[] list of codes for partial entries + */ + public function GetModified() + { + return $this->aModified; + } + + /** + * Apply a delta to the current ItemSet + * $aDelta['added] = array of tag code for only the added tags + * $aDelta['removed'] = array of tag code for only the removed tags + * + * @param $aDelta + * + * @throws \CoreException + */ + public function ApplyDelta($aDelta) + { + if (isset($aDelta['removed'])) + { + foreach($aDelta['removed'] as $oItem) + { + $this->Remove($oItem); + } + } + if (isset($aDelta['added'])) + { + foreach($aDelta['added'] as $oItem) + { + $this->Add($oItem); + } + } + } + + /** + * @param string $oItem + * + * @throws \CoreException + */ + public function Add($oItem) + { + if ($this->Count() === $this->iLimit) + { + throw new CoreException("Maximum number of items ({$this->iLimit}) reached for {$this->sClass}:{$this->sAttCode}"); + } + if ($this->IsItemInList($this->aPreserved, $oItem) || $this->IsItemInList($this->aAdded, $oItem)) + { + // nothing to do, already existing tag + return; + } + // if removed and added again + if (($this->RemoveItemFromList($this->aRemoved, $oItem)) !== false) + { + // put it back into preserved + $this->aPreserved[] = $oItem; + // no need to add it to aModified : was already done when calling RemoveItem method + } + else + { + $this->aAdded[] = $oItem; + $this->aModified[] = $oItem; + } + } + + /** + * @param $oItem + */ + public function Remove($oItem) + { + if ($this->IsItemInList($this->aRemoved, $oItem)) + { + // nothing to do, already removed tag + return; + } + + if ($this->RemoveItemFromList($this->aAdded, $oItem) !== false) + { + $this->aModified[] = $oItem; + + return; // if present in added, can't be in preserved ! + } + + if ($this->RemoveItemFromList($this->aPreserved, $oItem) !== false) + { + $this->aModified[] = $oItem; + $this->aRemoved[] = $oItem; + } + } + + private function IsItemInList($aItemList, $oItem) + { + return in_array($oItem, $aItemList); + } + + /** + * @param \DBObject[] $aItemList + * @param $oItem + * + * @return bool|\DBObject false if not found, else the removed element + */ + private function RemoveItemFromList(&$aItemList, $oItem) + { + if (!($this->IsItemInList($aItemList, $oItem))) + { + return false; + } + foreach ($aItemList as $index => $value) + { + if ($value === $oItem) + { + unset($aItemList[$index]); + return $oItem; + } + } + + return false; + } + + /** + * Populates the added and removed arrays for bulk edit + * + * @param string[] $aItems + * + * @throws \CoreException + */ + public function GenerateDiffFromItems($aItems) + { + foreach($this->GetValues() as $oCurrentItem) + { + if (!in_array($oCurrentItem, $aItems)) + { + $this->Remove($oCurrentItem); + } + } + + foreach($aItems as $oNewItem) + { + $this->Add($oNewItem); + } + } + + /** + * Compare Item Set + * + * @param \ormSet $other + * + * @return bool true if same tag set + */ + public function Equals(ormSet $other) + { + return implode(', ', $this->GetValue()) === implode(', ', $other->GetValue()); + } + + +} \ No newline at end of file diff --git a/core/tagsetfield.class.inc.php b/core/tagsetfield.class.inc.php index 13e1519c63..c7198616ca 100644 --- a/core/tagsetfield.class.inc.php +++ b/core/tagsetfield.class.inc.php @@ -277,7 +277,6 @@ abstract class TagSetFieldData extends cmdbAbstractObject return false; } - /** * Display Tag Usage * @@ -333,6 +332,11 @@ abstract class TagSetFieldData extends cmdbAbstractObject public static function GetClassName($sClass) { + if ($sClass == 'TagSetFieldData') + { + $aWords = preg_split('/(?=[A-Z]+)/', $sClass); + return trim(implode(' ', $aWords)); + } try { $aTagFieldInfo = self::ExtractTagFieldName($sClass); diff --git a/core/trigger.class.inc.php b/core/trigger.class.inc.php index 0f9da40c13..d811dbbd74 100644 --- a/core/trigger.class.inc.php +++ b/core/trigger.class.inc.php @@ -427,7 +427,7 @@ class TriggerOnObjectUpdate extends TriggerOnObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeObjectAttCodeSet('target_attcodes', array("allowed_values" => null, "class" => "target_class", "sql" => "target_attcodes", "default_value" => null, "is_null_allowed" => true, "depends_on" => array('target_class')))); + MetaModel::Init_AddAttribute(new AttributeClassAttCodeSet('target_attcodes', array("allowed_values" => null, "class_field" => "target_class", "sql" => "target_attcodes", "default_value" => null, "is_null_allowed" => true, "max_items" => 20, "min_items" => 0, "attribute_definition_list" => null, "depends_on" => array('target_class')))); // Display lists MetaModel::Init_SetZListItems('details', array('description', 'target_class', 'filter', 'target_attcodes', 'action_list')); // Attributes to be displayed for the complete details @@ -444,11 +444,13 @@ class TriggerOnObjectUpdate extends TriggerOnObject } // Check the attribute - $aAttCodes = $this->Get('target_attcodes'); + $oAttCodeSet = $this->Get('target_attcodes'); + $aAttCodes = $oAttCodeSet->GetValues(); if (empty($aAttCodes)) { return true; } + foreach($aAttCodes as $sAttCode) { if (array_key_exists($sAttCode, $aChanges)) diff --git a/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml b/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml index b0ab245129..89862b4844 100755 --- a/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml +++ b/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml @@ -200,6 +200,7 @@ tagfield true all + 12 diff --git a/dictionaries/en.dictionary.itop.core.php b/dictionaries/en.dictionary.itop.core.php index 95c20f68b6..67b2184d1e 100644 --- a/dictionaries/en.dictionary.itop.core.php +++ b/dictionaries/en.dictionary.itop.core.php @@ -595,7 +595,7 @@ Dict::Add('EN US', 'English', 'English', array( Dict::Add('EN US', 'English', 'English', array( 'Class:TriggerOnObjectUpdate' => 'Trigger (on object update)', 'Class:TriggerOnObjectUpdate+' => 'Trigger on object update of [a child class of] the given class', - 'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Target attributes', + 'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Target fields', 'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => '', )); diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index ee6f43ae22..dc15cd4f7b 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -1395,7 +1395,7 @@ EOF $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql'); $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); $aParameters['depends_on'] = $sDependencies; - $aParameters['tag_max_nb'] = $this->GetPropNumber($oField, 'tag_max_nb', 12); + $aParameters['max_items'] = $this->GetPropNumber($oField, 'max_items', 12); $aParameters['tag_code_max_len'] = $this->GetPropNumber($oField, 'tag_code_max_len', 20); if ($aParameters['tag_code_max_len'] > 255) { diff --git a/test/core/TagSetFieldDataTest.php b/test/core/TagSetFieldDataTest.php index c741c8f1a6..14286a764c 100644 --- a/test/core/TagSetFieldDataTest.php +++ b/test/core/TagSetFieldDataTest.php @@ -253,7 +253,7 @@ class TagSetFieldDataTest extends ItopDataTestCase { /** @var \AttributeTagSet $oAttDef */ $oAttDef = \MetaModel::GetAttributeDef(TAG_CLASS, TAG_ATTCODE); - $iMaxTags = $oAttDef->GetTagMaxNb(); + $iMaxTags = $oAttDef->GetMaxItems(); for ($i = 0; $i < $iMaxTags; $i++) { $sTagCode = 'MaxTag'.$i;