diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index d0298dc2c..20873b740 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -663,7 +663,30 @@ EOF { // n:n links $oLinkingAttDef = MetaModel::GetAttributeDef($sLinkedClass, $oAttDef->GetExtKeyToRemote()); + $sLinkingAttCode = $oLinkingAttDef->GetCode(); $sTargetClass = $oLinkingAttDef->GetTargetClass(); + + // N°2334 fields to display for n:n relations + $aLnkAttDefsToDisplay = MetaModel::GetZListAttDefsFilteredForIndirectLinkClass($sClass, $sAttCode); + $aRemoteAttDefsToDisplay = MetaModel::GetZListAttDefsFilteredForIndirectRemoteClass($sTargetClass); + $aLnkAttCodesToDisplay = array_map(function ($oLnkAttDef) { + return ormLinkSet::LINK_ALIAS.'.'.$oLnkAttDef->GetCode(); + }, + $aLnkAttDefsToDisplay + ); + if (!in_array(ormLinkSet::LINK_ALIAS.'.'.$sLinkingAttCode, $aLnkAttCodesToDisplay)) + { + // we need to display a link to the remote class instance ! + $aLnkAttCodesToDisplay[] = ormLinkSet::LINK_ALIAS.'.'.$sLinkingAttCode; + } + $aRemoteAttCodesToDisplay = array_map(function ($oRemoteAttDef) { + return ormLinkSet::REMOTE_ALIAS.'.'.$oRemoteAttDef->GetCode(); + }, + $aRemoteAttDefsToDisplay + ); + $aAttCodesToDisplay = array_merge($aLnkAttCodesToDisplay, $aRemoteAttCodesToDisplay); + $sAttCodesToDisplay = implode(',', $aAttCodesToDisplay); + $aParams = array( 'link_attr' => $oAttDef->GetExtKeyToMe(), 'object_id' => $this->GetKey(), @@ -671,8 +694,12 @@ EOF 'view_link' => false, 'menu' => false, //'menu_actions_target' => '_blank', - 'display_limit' => true, // By default limit the list to speed up the initial load & display + // By default limit the list to speed up the initial load & display + 'display_limit' => true, 'table_id' => $sClass.'_'.$sAttCode, + // N°2334 specify fields to display for n:n relations + 'zlist' => false, + 'extra_fields' => $sAttCodesToDisplay, ); } $oPage->p(MetaModel::GetClassIcon($sTargetClass)." ".$oAttDef->GetDescription()); @@ -1286,7 +1313,14 @@ HTML /** * @param \WebPage $oPage * @param \CMDBObjectSet $oSet - * @param array $aExtraParams + * @param array $aExtraParams key used : + * * * @return string * @throws \CoreException @@ -1365,7 +1399,7 @@ HTML } // Filter the list to removed linked set since we are not able to display them here - foreach($aList[$sAlias] as $index => $sAttCode) + foreach ($aList[$sAlias] as $index => $sAttCode) { $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode); if ($oAttDef instanceof AttributeLinkedSet) @@ -1374,6 +1408,11 @@ HTML unset($aList[$sAlias][$index]); } } + + if (empty($aList[$sAlias])) + { + unset($aList[$sAlias], $aAuthorizedClasses[$sAlias]); + } } $sSelectMode = 'none'; @@ -3111,7 +3150,7 @@ EOF $this->GetOwnershipJSHandler($oPage, $sOwnershipToken); } - // Note: This part (inline images activation) is duplicated in self::DisplayModifyForm and several other places. Maybe it should be refactored so it automatically activates when an HTML field is present, or be an option of the attribute. See bug n°1240. + // Note: This part (inline images activation) is duplicated in self::DisplayModifyForm and several other places. Maybe it should be refactored so it automatically activates when an HTML field is present, or be an option of the attribute. See bug N°1240. $sTempId = utils::GetUploadTempId($iTransactionId); $oPage->add_ready_script(InlineImage::EnableCKEditorImageUpload($this, $sTempId)); } diff --git a/application/datatable.class.inc.php b/application/datatable.class.inc.php index adbeaecae..226e1d614 100644 --- a/application/datatable.class.inc.php +++ b/application/datatable.class.inc.php @@ -971,13 +971,13 @@ class DataTableSettings implements Serializable * @throws \CoreException * @throws \DictExceptionMissingString */ - static public function GetDataModelSettings($aClassAliases, $bViewLink, $aDefaultLists) + public static function GetDataModelSettings($aClassAliases, $bViewLink, $aDefaultLists) { $oSettings = new DataTableSettings($aClassAliases); // Retrieve the class specific settings for each class/alias based on the 'list' ZList //TODO let the caller pass some other default settings (another Zlist, extre fields...) $aColumns = array(); - foreach($aClassAliases as $sAlias => $sClass) + foreach ($aClassAliases as $sAlias => $sClass) { if ($aDefaultLists == null) { diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index 2d709ae94..02804fc5d 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -46,7 +46,7 @@ class UILinksWidget * UILinksWidget constructor. * * @param string $sClass - * @param string $sAttCode + * @param string $sAttCode AttributeLinkedSetIndirect attcode * @param int $iInputId * @param string $sNameSuffix * @param bool $bDuplicatesAllowed @@ -73,41 +73,35 @@ class UILinksWidget /** @var AttributeExternalKey $oLinkingAttDef */ $oLinkingAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $this->m_sExtKeyToRemote); $this->m_sRemoteClass = $oLinkingAttDef->GetTargetClass(); - $sExtKeyToMe = $oAttDef->GetExtKeyToMe(); - $sStateAttCode = MetaModel::GetStateAttributeCode($this->m_sClass); - $sDefaultState = MetaModel::GetDefaultState($this->m_sClass); $this->m_aEditableFields = array(); $this->m_aTableConfig = array(); - $this->m_aTableConfig['form::checkbox'] = array( 'label' => "m_sAttCode}{$this->m_sNameSuffix} .selection', this.checked); oWidget".$this->m_iInputId.".OnSelectChange();\">", 'description' => Dict::S('UI:SelectAllToggle+')); + $this->m_aTableConfig['form::checkbox'] = array( + 'label' => "m_sAttCode}{$this->m_sNameSuffix} .selection', this.checked); oWidget".$this->m_iInputId.".OnSelectChange();\">", + 'description' => Dict::S('UI:SelectAllToggle+'), + ); - foreach(MetaModel::FlattenZList(MetaModel::GetZListItems($this->m_sLinkedClass, 'list')) as $sAttCode) + $aLnkAttDefsToDisplay = MetaModel::GetZListAttDefsFilteredForIndirectLinkClass($sClass, $sAttCode); + foreach ($aLnkAttDefsToDisplay as $oLnkAttDef) { - $oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sAttCode); - if ($sStateAttCode == $sAttCode) - { - // State attribute is always hidden from the UI - } - else if ($oAttDef->IsWritable() && ($sAttCode != $sExtKeyToMe) && ($sAttCode != $this->m_sExtKeyToRemote) && ($sAttCode != 'finalclass')) - { - $iFlags = MetaModel::GetAttributeFlags($this->m_sLinkedClass, $sDefaultState, $sAttCode); - if ( !($iFlags & OPT_ATT_HIDDEN) && !($iFlags & OPT_ATT_READONLY) ) - { - $this->m_aEditableFields[] = $sAttCode; - $this->m_aTableConfig[$sAttCode] = array( 'label' => $oAttDef->GetLabel(), 'description' => $oAttDef->GetDescription()); - } - } + $sLnkAttCode = $oLnkAttDef->GetCode(); + $this->m_aEditableFields[] = $sLnkAttCode; + $this->m_aTableConfig[$sLnkAttCode] = array('label' => $oLnkAttDef->GetLabel(), 'description' => $oLnkAttDef->GetDescription()); } - - $this->m_aTableConfig['static::key'] = array( 'label' => MetaModel::GetName($this->m_sRemoteClass), 'description' => MetaModel::GetClassDescription($this->m_sRemoteClass)); - foreach(MetaModel::GetZListItems($this->m_sRemoteClass, 'list') as $sFieldCode) + + $this->m_aTableConfig['static::key'] = array( + 'label' => MetaModel::GetName($this->m_sRemoteClass), + 'description' => MetaModel::GetClassDescription($this->m_sRemoteClass), + ); + + $aRemoteAttDefsToDisplay = MetaModel::GetZListAttDefsFilteredForIndirectRemoteClass($this->m_sRemoteClass); + foreach ($aRemoteAttDefsToDisplay as $oRemoteAttDef) { - // TO DO: check the state of the attribute: hidden or visible ? - if ($sFieldCode != 'finalclass') - { - $oAttDef = MetaModel::GetAttributeDef($this->m_sRemoteClass, $sFieldCode); - $this->m_aTableConfig['static::'.$sFieldCode] = array( 'label' => $oAttDef->GetLabel(), 'description' => $oAttDef->GetDescription()); - } + $sRemoteAttCode = $oRemoteAttDef->GetCode(); + $this->m_aTableConfig['static::'.$sRemoteAttCode] = array( + 'label' => $oRemoteAttDef->GetLabel(), + 'description' => $oRemoteAttDef->GetDescription(), + ); } } diff --git a/core/metamodel.class.php b/core/metamodel.class.php index be80e20a9..0c8cdbd98 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1848,7 +1848,7 @@ abstract class MetaModel * @param string $sClass * @param string $sListCode * - * @return array + * @return array list of attribute codes */ public static function GetZListItems($sClass, $sListCode) { @@ -1868,6 +1868,82 @@ abstract class MetaModel return self::GetZListItems($sParentClass, $sListCode); } + /** + * @param string $sRemoteClass + * + * @return \AttributeDefinition[] list of attdefs to display by default for the remote class + * + * @since 2.8.0 N°2334 + */ + public static function GetZListAttDefsFilteredForIndirectRemoteClass($sRemoteClass) + { + $aAttCodesToPrint = []; + + foreach (MetaModel::GetZListItems($sRemoteClass, 'list') as $sFieldCode) + { + //TODO: check the state of the attribute: hidden or visible ? + if ($sFieldCode == 'finalclass') + { + continue; + } + + $oRemoteAttDef = MetaModel::GetAttributeDef($sRemoteClass, $sFieldCode); + $aAttCodesToPrint[] = $oRemoteAttDef; + } + + return $aAttCodesToPrint; + } + + /** + * @param string $sClass left class + * @param string $sAttCode AttributeLinkedSetIndirect attcode + * + * @return \AttributeDefinition[] list of attdefs to display by default for lnk class + * + * @throws \CoreException + * @since 2.8.0 N°2334 + */ + public static function GetZListAttDefsFilteredForIndirectLinkClass($sClass, $sAttCode) + { + $aAttCodesToPrint = []; + + $oLinkedSetAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $sLinkedClass = $oLinkedSetAttDef->GetLinkedClass(); + $sExtKeyToRemote = $oLinkedSetAttDef->GetExtKeyToRemote(); + $sExtKeyToMe = $oLinkedSetAttDef->GetExtKeyToMe(); + + $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); + $sDefaultState = MetaModel::GetDefaultState($sClass); + + foreach (MetaModel::FlattenZList(MetaModel::GetZListItems($sLinkedClass, 'list')) as $sLnkAttCode) + { + $oLnkAttDef = MetaModel::GetAttributeDef($sLinkedClass, $sLnkAttCode); + if ($sStateAttCode == $sLnkAttCode) + { + // State attribute is always hidden from the UI + continue; + } + if (($sLnkAttCode == $sExtKeyToMe) + || ($sLnkAttCode == $sExtKeyToRemote) + || ($sLnkAttCode == 'finalclass')) + { + continue; + } + if (!($oLnkAttDef->IsWritable())) + { + continue; + } + + $iFlags = MetaModel::GetAttributeFlags($sLinkedClass, $sDefaultState, $sLnkAttCode); + if (!($iFlags & OPT_ATT_HIDDEN) && !($iFlags & OPT_ATT_READONLY)) + { + $aAttCodesToPrint[] = $oLnkAttDef; + } + } + + return $aAttCodesToPrint; + } + /** * @param string $sClass * @param string $sListCode diff --git a/core/ormlinkset.class.inc.php b/core/ormlinkset.class.inc.php index 7de3624fa..afd0dec02 100644 --- a/core/ormlinkset.class.inc.php +++ b/core/ormlinkset.class.inc.php @@ -30,6 +30,9 @@ require_once('dbobjectiterator.php'); class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator { + public const LINK_ALIAS = 'Link'; + public const REMOTE_ALIAS = 'Remote'; + protected $sHostClass; // subclass of DBObject protected $sAttCode; // xxxxxx_list protected $sClass; // class of the links @@ -786,11 +789,13 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator /** * @param bool $bShowObsolete * - * @return \DBObjectSet + * @return \DBObjectSet indirect relations will get `SELECT L,R ...` (l = lnk class, R = remote) * @throws \CoreException * @throws \CoreWarning * @throws \MySQLException * @throws \Exception + * + * @since 2.8.0 N°2334 returns both lnk and remote classes for indirect relations */ public function ToDBObjectSet($bShowObsolete = true) { @@ -802,7 +807,11 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator $sExtKeyToRemote = $oAttDef->GetExtKeyToRemote(); /** @var \AttributeExternalKey $oLinkingAttDef */ $oLinkingAttDef = MetaModel::GetAttributeDef($this->sClass, $sExtKeyToRemote); + + // N°2334 add pointed class (SELECT L,R) to have all fields (lnk + remote) in display + // the pointed class is always present in the search, as generated by \AttributeLinkedSet::GetDefaultValue $sTargetClass = $oLinkingAttDef->GetTargetClass(); + $oRemoteClassSearch = new DBObjectSearch($sTargetClass); if (!$bShowObsolete && MetaModel::IsObsoletable($sTargetClass)) { $oNotObsolete = new BinaryExpression( @@ -810,10 +819,12 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator '=', new ScalarExpression(0) ); - $oNotObsoleteRemote = new DBObjectSearch($sTargetClass); - $oNotObsoleteRemote->AddConditionExpression($oNotObsolete); - $oLinkSearch->AddCondition_PointingTo($oNotObsoleteRemote, $sExtKeyToRemote); + $oRemoteClassSearch->AddConditionExpression($oNotObsolete); } + $oLinkSearch->AddCondition_PointingTo($oRemoteClassSearch, $sExtKeyToRemote); + $oLinkSearch->RenameAlias($oLinkSearch->GetClassAlias(), self::LINK_ALIAS); + $oLinkSearch->RenameAlias($sTargetClass, self::REMOTE_ALIAS); + $oLinkSearch->SetSelectedClasses([self::LINK_ALIAS, self::REMOTE_ALIAS]); } $oLinkSet = new DBObjectSet($oLinkSearch); $oLinkSet->SetShowObsoleteData($bShowObsolete);