diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index f67c75426..7d5a4d6b6 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -39,6 +39,8 @@ use Combodo\iTop\Application\UI\Base\Layout\Object\ObjectFactory; use Combodo\iTop\Application\UI\Base\Layout\TabContainer\Tab\AjaxTab; use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock; use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory; +use Combodo\iTop\Application\UI\Links\Indirect\BlockIndirectLinksViewTable; +use Combodo\iTop\Application\UI\Links\Direct\BlockDirectLinksViewTable; use Combodo\iTop\Renderer\BlockRenderer; use Combodo\iTop\Renderer\Console\ConsoleFormRenderer; @@ -650,8 +652,7 @@ HTML } // Display mode - if (!$oAttDef->IsLinkset()) - { + if (!$oAttDef->IsLinkset()) { continue; } // Process only linkset attributes... @@ -662,12 +663,9 @@ HTML $oLinkSet = $oOrmLinkSet->ToDBObjectSet(utils::ShowObsoleteData()); $iCount = $oLinkSet->Count(); - if ($this->IsNew()) - { + if ($this->IsNew()) { $iFlags = $this->GetInitialStateAttributeFlags($sAttCode); - } - else - { + } else { $iFlags = $this->GetAttributeFlags($sAttCode); } // Adjust the flags according to user rights @@ -712,103 +710,20 @@ HTML $aArgs = array('this' => $this); $bReadOnly = ($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE)); - if ($bEditMode && (!$bReadOnly)) - { + if ($bEditMode && (!$bReadOnly)) { $sInputId = $this->m_iFormId.'_'.$sAttCode; - - if ($oAttDef->IsIndirect()) - { - $oLinkingAttDef = MetaModel::GetAttributeDef($sLinkedClass, $oAttDef->GetExtKeyToRemote()); - $sTargetClass = $oLinkingAttDef->GetTargetClass(); - } - else - { - $sTargetClass = $sLinkedClass; - } - - $oClassIcon = new MedallionIcon(MetaModel::GetClassIcon($sTargetClass, false)); - $oClassIcon->SetDescription($oAttDef->GetDescription())->AddCSSClass('ibo-block-list--medallion'); - $oPage->AddUiBlock($oClassIcon); - $sDisplayValue = ''; // not used $sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $oLinkSet, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).''; $this->AddToFieldsMap($sAttCode, $sInputId); $oPage->add($sHTMLValue); - } - else - { - // Display mode - if (!$oAttDef->IsIndirect()) - { - // 1:n links - $sTargetClass = $sLinkedClass; - - $aDefaults = array($oAttDef->GetExtKeyToMe() => $this->GetKey()); - $oAppContext = new ApplicationContext(); - foreach($oAppContext->GetNames() as $sKey) - { - // The linked object inherits the parent's value for the context - if (MetaModel::IsValidAttCode($sClass, $sKey)) - { - $aDefaults[$sKey] = $this->Get($sKey); - } - } - $aParams = array( - 'target_attr' => $oAttDef->GetExtKeyToMe(), - 'object_id' => $this->GetKey(), - 'menu' => MetaModel::GetConfig()->Get('allow_menu_on_linkset'), - //'menu_actions_target' => '_blank', - 'default' => $aDefaults, - 'table_id' => $sClass.'_'.$sAttCode, - ); + } else { + if ($oAttDef->IsIndirect()) { + $oBlockLinkViewTable = new BlockIndirectLinksViewTable($oPage, $this, $sClass, $sAttCode, $oAttDef); + } else { + $oBlockLinkViewTable = new BlockDirectLinksViewTable($oPage, $this, $sClass, $sAttCode, $oAttDef); } - else { - // 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(), - 'target_attr' => $oAttDef->GetExtKeyToRemote(), - 'view_link' => false, - 'menu' => false, - //'menu_actions_target' => '_blank', - // 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, - ); - } - $oClassIcon = new MedallionIcon(MetaModel::GetClassIcon($sTargetClass, false)); - $oClassIcon->SetDescription($oAttDef->GetDescription())->AddCSSClass('ibo-block-list--medallion'); - $oPage->AddUiBlock($oClassIcon); - $oBlock = new DisplayBlock($oLinkSet->GetFilter(), 'list', false); - $oBlock->Display($oPage, 'rel_'.$sAttCode, $aParams); + $oPage->AddUiBlock($oBlockLinkViewTable); } if (array_key_exists($sAttCode, $aRedundancySettings)) { foreach ($aRedundancySettings[$sAttCode] as $oRedundancyAttDef) { @@ -871,6 +786,10 @@ HTML } } } + + // add hidden input for linkset transactions + $oInputHidden = InputUIBlockFactory::MakeForHidden('linkset_transactions_id', utils::GetNewTransactionId(), 'linkset_transactions_id'); + $oPage->AddUiBlock($oInputHidden); } /** diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 05443b292..f43b12ed4 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -1471,6 +1471,7 @@ JS } else { $iListId = $aExtraParams['currentId']; } + $oBlock = DataTableUIBlockFactory::MakeForRendering($iListId, $oSet, $aExtraParams); $oHtml->AddHtml(""); $oContentBlock->AddSubBlock($oBlock); @@ -1838,7 +1839,7 @@ class MenuBlock extends DisplayBlock if ($bIsModifyAllowed) { $aRegularActions['UI:Menu:Modify'] = array( 'label' => Dict::S('UI:Menu:Modify'), - 'url' => "{$sRootUrl}pages/$sUIPage?operation=object.modify&class=$sClass&id=$id{$sContext}#", + 'url' => "{$sRootUrl}pages/$sUIPage?route=object.modify&class=$sClass&id=$id{$sContext}#", ) + $aActionParams; } if ($bIsCreationAllowed) { diff --git a/application/ui.linksdirectwidget.class.inc.php b/application/ui.linksdirectwidget.class.inc.php index 2bd2d1646..2135a484f 100644 --- a/application/ui.linksdirectwidget.class.inc.php +++ b/application/ui.linksdirectwidget.class.inc.php @@ -4,8 +4,8 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ -use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory; -use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory; +use Combodo\iTop\Application\UI\Links\Direct\BlockDirectLinksEditTable; +use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer; /** * Class UILinksWidgetDirect @@ -23,6 +23,7 @@ class UILinksWidgetDirect /** * UILinksWidgetDirect constructor. + * * @param string $sClass * @param string $sAttCode * @param string $sInputId @@ -80,97 +81,10 @@ class UILinksWidgetDirect */ public function Display(WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj) { - if (empty($aArgs)) { - $aArgs = []; - } + $oBlock = new BlockDirectLinksEditTable($this, $this->sInputid); + $oBlock->InitTable($oPage, $oValue, $sFormPrefix); - $oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode); - switch($oLinksetDef->GetEditMode()) - { - case LINKSET_EDITMODE_NONE: // The linkset is read-only - $this->DisplayAsBlock($oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, false /* bDisplayMenu*/); - break; - - case LINKSET_EDITMODE_ADDONLY: // The only possible action is to open (in a new window) the form to create a new object - if ($oCurrentObj && !$oCurrentObj->IsNew()) - { - $sTargetClass = $oLinksetDef->GetLinkedClass(); - $sExtKeyToMe = $oLinksetDef->GetExtKeyToMe(); - $sDefault = "default[$sExtKeyToMe]=".$oCurrentObj->GetKey(); - $oAppContext = new ApplicationContext(); - $sParams = $oAppContext->GetForLink(); - $oPage->p("".Dict::Format('UI:ClickToCreateNew', Metamodel::GetName($sTargetClass))."\n"); - } - $this->DisplayAsBlock($oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, false /* bDisplayMenu*/); - break; - - case LINKSET_EDITMODE_INPLACE: // The whole linkset can be edited 'in-place' - $this->DisplayEditInPlace($oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj); - break; - - case LINKSET_EDITMODE_ADDREMOVE: // The whole linkset can be edited 'in-place' - $sTargetClass = $oLinksetDef->GetLinkedClass(); - $sExtKeyToMe = $oLinksetDef->GetExtKeyToMe(); - $oExtKeyDef = MetaModel::GetAttributeDef($sTargetClass, $sExtKeyToMe); - $aButtons = array('add'); - if ($oExtKeyDef->IsNullAllowed()) - { - $aButtons = array('add', 'remove'); - } - $this->DisplayEditInPlace($oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj, $aButtons); - break; - - case LINKSET_EDITMODE_ACTIONS: - default: - $this->DisplayAsBlock($oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, true /* bDisplayMenu*/); - } - } - - /** - * @param WebPage $oPage - * @param DBObjectSet $oValue - * @param array $aArgs - * @param string $sFormPrefix - * @param DBObject $oCurrentObj - * @param bool $bDisplayMenu - * - * @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value for $aArgs for PHP 8.0 compatibility (protected method, always called with default value) - */ - protected function DisplayAsBlock(WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj, $bDisplayMenu) - { - $oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode); - $sTargetClass = $oLinksetDef->GetLinkedClass(); - if ($oCurrentObj && $oCurrentObj->IsNew() && $bDisplayMenu) - { - $oPage->p(Dict::Format('UI:BeforeAdding_Class_ObjectsSaveThisObject', MetaModel::GetName($sTargetClass))); - } - else - { - $oFilter = new DBObjectSearch($sTargetClass); - $oFilter->AddCondition($oLinksetDef->GetExtKeyToMe(), $oCurrentObj->GetKey(),'='); - - $aDefaults = array($oLinksetDef->GetExtKeyToMe() => $oCurrentObj->GetKey()); - $oAppContext = new ApplicationContext(); - foreach($oAppContext->GetNames() as $sKey) - { - // The linked object inherits the parent's value for the context - if (MetaModel::IsValidAttCode($this->sClass, $sKey) && $oCurrentObj) - { - $aDefaults[$sKey] = $oCurrentObj->Get($sKey); - } - } - $aParams = array( - 'target_attr' => $oLinksetDef->GetExtKeyToMe(), - 'object_id' => $oCurrentObj ? $oCurrentObj->GetKey() : null, - 'menu' => $bDisplayMenu, - 'menu_actions_target' => '_blank', - 'default' => $aDefaults, - 'table_id' => $this->sClass.'_'.$this->sAttCode, - ); - - $oBlock = new DisplayBlock($oFilter, 'list', false); - $oBlock->Display($oPage, $this->sInputid, $aParams); - } + return ConsoleBlockRenderer::RenderBlockTemplateInPage($oPage, $oBlock); } /** @@ -229,55 +143,6 @@ class UILinksWidgetDirect $oPage->add(''); } - /** - * @param WebPage $oPage - * @param DBObjectSet $oValue - * @param array $aArgs - * @param string $sFormPrefix - * @param DBObject $oCurrentObj - * @param array $aButtons - * - * @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value for $aArgs for PHP 8.0 compatibility (protected method, caller already handles it) - */ - protected function DisplayEditInPlace(WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj, $aButtons = array('create', 'delete')) - { - $aAttribs = $this->GetTableConfig(); - $oValue->Rewind(); - $aData = array(); - while ($oLinkObj = $oValue->Fetch()) { - $aRow = array(); - $aRow['form::select'] = ''; - foreach ($this->aZlist as $sLinkedAttCode) { - $aRow[$sLinkedAttCode] = $oLinkObj->GetAsHTML($sLinkedAttCode); - } - $aData[] = $aRow; - } - $oDiv = UIContentBlockUIBlockFactory::MakeStandard($this->sInputid, ['listContainer']); - $oPage->AddSubBlock($oDiv); - $oDatatable = DataTableUIBlockFactory::MakeForForm($this->sInputid, $aAttribs, $aData); - $oDatatable->SetOptions(['select_mode' => 'custom', 'disable_hyperlinks' => true]); - $oDiv->AddSubBlock($oDatatable); - $sInputName = $sFormPrefix.'attr_'.$this->sAttCode; - $aLabels = array( - 'delete' => Dict::S('UI:Button:Delete'), - // 'modify' => 'Modify...' , - 'creation_title' => Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sLinkedClass)), - 'create' => Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($this->sLinkedClass)), - 'remove' => Dict::S('UI:Button:Remove'), - 'add' => Dict::Format('UI:AddAnExisting_Class', MetaModel::GetName($this->sLinkedClass)), - 'selection_title' => Dict::Format('UI:SelectionOf_Class', MetaModel::GetName($this->sLinkedClass)), - ); - $oContext = new ApplicationContext(); - $sSubmitUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?'.$oContext->GetForLink(); - $sJSONLabels = json_encode($aLabels); - $sJSONButtons = json_encode($aButtons); - $sWizHelper = 'oWizardHelper'.$sFormPrefix; - // Don't automatically launch the search if the table is huge - $bDoSearch = !utils::IsHighCardinality($this->sLinkedClass); - $sJSDoSearch = $bDoSearch ? 'true' : 'false'; - $oPage->add_ready_script("$('#{$this->sInputid}').directlinks({class_name: '$this->sClass', att_code: '$this->sAttCode', input_name:'$sInputName', labels: $sJSONLabels, submit_to: '$sSubmitUrl', buttons: $sJSONButtons, oWizardHelper: $sWizHelper, do_search: $sJSDoSearch});"); - } - /** * @param WebPage $oPage * @param DBObject $oCurrentObj @@ -433,18 +298,21 @@ HTML { } - - protected function GetTableConfig() + + public function GetTableConfig() { $aAttribs = array(); - $aAttribs['form::select'] = array('label' => "sInputid}:not(:disabled)', this.checked);\" class=\"checkAll\">", 'description' => Dict::S('UI:SelectAllToggle+')); + $aAttribs['form::select'] = array( + 'label' => "sInputid}:not(:disabled)', this.checked);oWidget".$this->sInputid.".directlinks('instance')._onSelectChange();\" class=\"checkAll\">", + 'description' => Dict::S('UI:SelectAllToggle+'), + ); - foreach($this->aZlist as $sLinkedAttCode) - { + foreach ($this->aZlist as $sLinkedAttCode) { $oAttDef = MetaModel::GetAttributeDef($this->sLinkedClass, $sLinkedAttCode); $aAttribs[$sLinkedAttCode] = array('label' => MetaModel::GetLabel($this->sLinkedClass, $sLinkedAttCode), 'description' => $oAttDef->GetOrderByHint()); } - return $aAttribs; + + return $aAttribs; } /** @@ -543,12 +411,43 @@ HTML { $sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter } - - if (MetaModel::IsValidAttCode($sDestClass, $sAttCode) && !empty($defaultValue)) - { + + if (MetaModel::IsValidAttCode($sDestClass, $sAttCode) && !empty($defaultValue)) { $oSearch->AddCondition($sAttCode, $defaultValue); } } } } + + + public function GetClass(): string + { + return $this->sClass; + } + + public function GetLinkedClass(): string + { + return $this->sLinkedClass; + } + + public function GetAttCode(): string + { + return $this->sAttCode; + } + + public function GetInputId(): string + { + return $this->sInputid; + } + + public function GetNameSuffix(): string + { + return $this->sNameSuffix; + } + + public function GetZList(): array + { + return $this->aZlist; + } + } diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index c8b599068..f32bdd2d7 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -6,18 +6,18 @@ use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\DataTable\StaticTable\FormTableRow\FormTableRow; -use Combodo\iTop\Application\UI\Links\Indirect\BlockIndirectLinksEdit\BlockIndirectLinksEdit; -use Combodo\iTop\Application\UI\Links\Indirect\BlockObjectPickerDialog\BlockObjectPickerDialog; +use Combodo\iTop\Application\UI\Links\Indirect\BlockIndirectLinksEditTable; +use Combodo\iTop\Application\UI\Links\Indirect\BlockObjectPickerDialog; use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer; require_once(APPROOT.'application/displayblock.class.inc.php'); -class UILinksWidget +class UILinksWidget { protected $m_sClass; protected $m_sAttCode; protected $m_sNameSuffix; - protected $m_iInputId; + protected $m_sInputId; protected $m_aAttributes; protected $m_sExtKeyToRemote; protected $m_sExtKeyToMe; @@ -33,7 +33,7 @@ class UILinksWidget * * @param string $sClass * @param string $sAttCode AttributeLinkedSetIndirect attcode - * @param int $iInputId + * @param string $sInputId * @param string $sNameSuffix * @param bool $bDuplicatesAllowed * @@ -41,13 +41,14 @@ class UILinksWidget * @throws \DictExceptionMissingString * @throws \Exception */ - public function __construct($sClass, $sAttCode, $iInputId, $sNameSuffix = '', $bDuplicatesAllowed = false) + public function __construct($sClass, $sAttCode, $sInputId, $sNameSuffix = '', $bDuplicatesAllowed = false) { $this->m_sClass = $sClass; $this->m_sAttCode = $sAttCode; + $this->m_sInputId = $sInputId; $this->m_sNameSuffix = $sNameSuffix; - $this->m_iInputId = $iInputId; $this->m_bDuplicatesAllowed = $bDuplicatesAllowed; + $this->m_aEditableFields = array(); /** @var AttributeLinkedSetIndirect $oAttDef */ @@ -63,7 +64,7 @@ class UILinksWidget $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();\">", + 'label' => "m_sAttCode}{$this->m_sNameSuffix} .selection', this.checked); oWidget".$this->m_sInputId.".OnSelectChange();\">", 'description' => Dict::S('UI:SelectAllToggle+'), ); @@ -91,234 +92,13 @@ class UILinksWidget } } - /** - * A one-row form for editing a link record - * - * @param WebPage $oP Web page used for the ouput - * @param DBObject $oLinkedObj Remote object - * @param DBObject|int $linkObjOrId Either the lnk object or a unique number for new link records to add - * @param array $aArgs Extra context arguments - * @param DBObject $oCurrentObj The object to which all the elements of the linked set refer to - * @param int $iUniqueId A unique identifier of new links - * @param boolean $bReadOnly Display link as editable or read-only. Default is false (editable) - * - * @return array The HTML fragment of the one-row form - * @throws \ArchivedObjectException - * @throws \CoreException - * @throws \CoreUnexpectedValue - * @throws \Exception - */ - protected function GetFormRow(WebPage $oP, DBObject $oLinkedObj, $linkObjOrId, $aArgs, $oCurrentObj, $iUniqueId, $bReadOnly = false) - { - $sPrefix = "$this->m_sAttCode{$this->m_sNameSuffix}"; - $aRow = array(); - $aFieldsMap = array(); - $iKey = 0; - - if (is_object($linkObjOrId) && (!$linkObjOrId->IsNew())) - { - $iKey = $linkObjOrId->GetKey(); - $iRemoteObjKey = $linkObjOrId->Get($this->m_sExtKeyToRemote); - $sPrefix .= "[$iKey]["; - $sNameSuffix = "]"; // To make a tabular form - $aArgs['prefix'] = $sPrefix; - $aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}{$iKey}"; - $aArgs['this'] = $linkObjOrId; - - if ($bReadOnly) - { - $aRow['form::checkbox'] = ""; - foreach ($this->m_aEditableFields as $sFieldCode) - { - $sDisplayValue = $linkObjOrId->GetEditValue($sFieldCode); - $aRow[$sFieldCode] = $sDisplayValue; - } - } - else - { - $aRow['form::checkbox'] = "m_iInputId.".OnSelectChange();\" value=\"$iKey\">"; - foreach ($this->m_aEditableFields as $sFieldCode) - { - $sSafeFieldId = $this->GetFieldId($linkObjOrId->GetKey(), $sFieldCode); - $this->AddRowForFieldCode($aRow, $sFieldCode, $aArgs, $linkObjOrId, $oP, $sNameSuffix, $sSafeFieldId); - $aFieldsMap[$sFieldCode] = $sSafeFieldId; - } - } - - $sState = $linkObjOrId->GetState(); - $sRemoteKeySafeFieldId = $this->GetFieldId($aArgs['this']->GetKey(), $this->m_sExtKeyToRemote);; - } - else - { - // form for creating a new record - if (is_object($linkObjOrId)) - { - // New link existing only in memory - $oNewLinkObj = $linkObjOrId; - $iRemoteObjKey = $oNewLinkObj->Get($this->m_sExtKeyToRemote); - $oNewLinkObj->Set($this->m_sExtKeyToMe, - $oCurrentObj); // Setting the extkey with the object also fills the related external fields - } - else - { - $iRemoteObjKey = $linkObjOrId; - $oNewLinkObj = MetaModel::NewObject($this->m_sLinkedClass); - $oRemoteObj = MetaModel::GetObject($this->m_sRemoteClass, $iRemoteObjKey); - $oNewLinkObj->Set($this->m_sExtKeyToRemote, - $oRemoteObj); // Setting the extkey with the object alsoo fills the related external fields - $oNewLinkObj->Set($this->m_sExtKeyToMe, - $oCurrentObj); // Setting the extkey with the object also fills the related external fields - } - $sPrefix .= "[-$iUniqueId]["; - $sNameSuffix = "]"; // To make a tabular form - $aArgs['prefix'] = $sPrefix; - $aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}_".($iUniqueId < 0 ? -$iUniqueId : $iUniqueId); - $aArgs['this'] = $oNewLinkObj; - $sInputValue = $iUniqueId > 0 ? "-$iUniqueId" : "$iUniqueId"; - $aRow['form::checkbox'] = "m_iInputId.".OnSelectChange();\" value=\"$sInputValue\">"; - - if ($iUniqueId > 0) - { - // Rows created with ajax call need OnLinkAdded call. - // - $oP->add_ready_script( - <<m_iInputId}.OnLinkAdded($iUniqueId, $iRemoteObjKey); -EOF - ); - } - else - { - // Rows added before loading the form don't have to call OnLinkAdded. - // Listeners are already present and DOM is not recreated - $iPositiveUniqueId = -$iUniqueId; - $oP->add_ready_script(<<m_iInputId}.AddLink($iPositiveUniqueId, $iRemoteObjKey); -EOF - ); - } - - foreach($this->m_aEditableFields as $sFieldCode) - { - $sSafeFieldId = $this->GetFieldId($iUniqueId, $sFieldCode); - $this->AddRowForFieldCode($aRow, $sFieldCode, $aArgs, $oNewLinkObj, $oP, $sNameSuffix, $sSafeFieldId); - $aFieldsMap[$sFieldCode] = $sSafeFieldId; - - $sValue = $oNewLinkObj->Get($sFieldCode); - $oP->add_ready_script( - <<m_iInputId}.OnValueChange($iKey, $iUniqueId, '$sFieldCode', '$sValue'); -JS - ); - } - - $sState = ''; - $sRemoteKeySafeFieldId = $this->GetFieldId($iUniqueId, $this->m_sExtKeyToRemote); - } - - if (!$bReadOnly) - { - $sExtKeyToMeId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToMe); - $aFieldsMap[$this->m_sExtKeyToMe] = $sExtKeyToMeId; - $aRow['form::checkbox'] .= "GetKey()."\">"; - - $sExtKeyToRemoteId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToRemote); - $aFieldsMap[$this->m_sExtKeyToRemote] = $sExtKeyToRemoteId; - $aRow['form::checkbox'] .= ""; - } - - // Adding fields from remote class - // all fields are embedded in a span + added to $aFieldsMap array so that we can refresh them after extkey change - $aRemoteFieldsMap = []; - foreach (MetaModel::GetZListItems($this->m_sRemoteClass, 'list') as $sFieldCode) - { - $sSafeFieldId = $this->GetFieldId($aArgs['this']->GetKey(), $sFieldCode); - $aRow['static::'.$sFieldCode] = "".$oLinkedObj->GetAsHTML($sFieldCode).''; - $aRemoteFieldsMap[$sFieldCode] = $sSafeFieldId; - } - // id field is needed so that remote object could be load server side - $aRemoteFieldsMap['id'] = $sRemoteKeySafeFieldId; - - // Generate WizardHelper to update dependant fields - $this->AddWizardHelperInit($oP, $aArgs['wizHelper'], $this->m_sLinkedClass, $sState, $aFieldsMap); - //instantiate specific WizarHelper instance for remote class fields refresh - $bHasExtKeyUpdatingRemoteClassFields = ( - array_key_exists('replaceDependenciesByRemoteClassFields', $aArgs) - && ($aArgs['replaceDependenciesByRemoteClassFields']) - ); - if ($bHasExtKeyUpdatingRemoteClassFields) - { - $this->AddWizardHelperInit($oP, $aArgs['wizHelperRemote'], $this->m_sRemoteClass, $sState, $aRemoteFieldsMap); - } - - return $aRow; - } - - private function AddRowForFieldCode(&$aRow, $sFieldCode, &$aArgs, $oLnk, $oP, $sNameSuffix, $sSafeFieldId): void - { - if (($sFieldCode === $this->m_sExtKeyToRemote)) - { - // current field is the lnk extkey to the remote class - $aArgs['replaceDependenciesByRemoteClassFields'] = true; - $sRowFieldCode = 'static::key'; - $aArgs['wizHelperRemote'] = $aArgs['wizHelper'].'_remote'; - $aRemoteAttDefs = MetaModel::GetZListAttDefsFilteredForIndirectRemoteClass($this->m_sRemoteClass); - $aRemoteCodes = array_map( - function ($value) { - return $value->GetCode(); - }, - $aRemoteAttDefs - ); - $aArgs['remoteCodes'] = $aRemoteCodes; - } - else - { - $aArgs['replaceDependenciesByRemoteClassFields'] = false; - $sRowFieldCode = $sFieldCode; - } - $sValue = $oLnk->Get($sFieldCode); - $sDisplayValue = $oLnk->GetEditValue($sFieldCode); - $oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode); - - $aRow[$sRowFieldCode] = '
' - .cmdbAbstractObject::GetFormElementForField( - $oP, - $this->m_sLinkedClass, - $sFieldCode, - $oAttDef, - $sValue, - $sDisplayValue, - $sSafeFieldId, - $sNameSuffix, - 0, - $aArgs - ) - .'
'; - } - private function GetFieldId($iLnkId, $sFieldCode, $bSafe = true) { - $sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$iLnkId.']'; + $sFieldId = $this->m_sInputId.'_'.$sFieldCode.'['.$iLnkId.']'; return ($bSafe) ? utils::GetSafeId($sFieldId) : $sFieldId; } - private function AddWizardHelperInit($oP, $sWizardHelperVarName, $sWizardHelperClass, $sState, $aFieldsMap): void - { - $iFieldsCount = count($aFieldsMap); - $sJsonFieldsMap = json_encode($aFieldsMap); - - $oP->add_script( - <<sLinkedSetId = $sLinkedSetId; - $oBlock->sClass = $this->m_sClass; - $oBlock->sAttCode = $this->m_sAttCode; - $oBlock->iInputId = $this->m_iInputId; - $oBlock->sNameSuffix = $this->m_sNameSuffix; - $oBlock->bDuplicates = ($this->m_bDuplicatesAllowed) ? 'true' : 'false'; - $oBlock->oWizHelper = 'oWizardHelper'.$sFormPrefix; - $oBlock->sExtKeyToRemote = $this->m_sExtKeyToRemote; - // Don't automatically launch the search if the table is huge - $oBlock->bJSDoSearch = utils::IsHighCardinality($this->m_sRemoteClass) ? 'false' : 'true'; - $oBlock->sFormPrefix = $sFormPrefix; - $oBlock->sRemoteClass = $this->m_sRemoteClass; - - $oValue->Rewind(); - $aForm = array(); - $iMaxAddedId = 0; - $iAddedId = -1; // Unique id for new links - $oBlock->aRemoved = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$this->m_sAttCode}_tbd", '[]', 'raw_data')); - while ($oCurrentLink = $oValue->Fetch()) { - // We try to retrieve the remote object as usual - if (!in_array($oCurrentLink->GetKey(), $oBlock->aRemoved)) { - $oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote), false /* Must not be found */); - // If successful, it means that we can edit its link - if ($oLinkedObj !== null) { - $bReadOnly = false; - } // Else we retrieve it without restrictions (silos) and will display its link as readonly - else { - $bReadOnly = true; - $oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote), false /* Must not be found */, true); - } - - if ($oCurrentLink->IsNew()) { - $key = $iAddedId--; - } else { - $key = $oCurrentLink->GetKey(); - } - - $iMaxAddedId = max($iMaxAddedId, $key); - $aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj, $key, $bReadOnly); - } - } - $oBlock->iMaxAddedId = (int)$iMaxAddedId; - - $oDataTable = DataTableUIBlockFactory::MakeForForm("{$this->m_sAttCode}{$this->m_sNameSuffix}", $this->m_aTableConfig, $aForm); - $oDataTable->SetOptions(['select_mode' => 'custom', 'disable_hyperlinks' => true]); - $oBlock->AddSubBlock($oDataTable); - - $oBlock->AddControls(); + $oBlock = new BlockIndirectLinksEditTable($this); + $oBlock->InitTable($oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj, $this->m_aTableConfig); return ConsoleBlockRenderer::RenderBlockTemplateInPage($oPage, $oBlock); } - /** - * @param string $sClass - * @param string $sAttCode - * - * @return string - * @throws \Exception - */ - protected static function GetTargetClass($sClass, $sAttCode) - { - /** @var AttributeLinkedSet $oAttDef */ - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - $sLinkedClass = $oAttDef->GetLinkedClass(); - $sTargetClass = ''; - switch(get_class($oAttDef)) - { - case 'AttributeLinkedSetIndirect': - /** @var AttributeExternalKey $oLinkingAttDef */ - /** @var AttributeLinkedSetIndirect $oAttDef */ - $oLinkingAttDef = MetaModel::GetAttributeDef($sLinkedClass, $oAttDef->GetExtKeyToRemote()); - $sTargetClass = $oLinkingAttDef->GetTargetClass(); - break; - - case 'AttributeLinkedSet': - $sTargetClass = $sLinkedClass; - break; - } - - return $sTargetClass; - } - /** * @param WebPage $oPage * @param DBObject $oCurrentObj @@ -473,11 +173,11 @@ JS $sLinkedSetId = "{$this->m_sAttCode}{$this->m_sNameSuffix}"; - $oBlock = new BlockObjectPickerDialog(); + $oBlock = new BlockObjectPickerDialog($this); $oPage->AddUiBlock($oBlock); $oBlock->sLinkedSetId = $sLinkedSetId; - $oBlock->iInputId = $this->m_iInputId; + $oBlock->iInputId = $this->m_sInputId; $oBlock->sLinkedClassName = MetaModel::GetName($this->m_sLinkedClass); $oBlock->sClassName = MetaModel::GetName($this->m_sClass); @@ -548,7 +248,8 @@ JS foreach ($aLinkedObjectIds as $iObjectId) { $oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $iObjectId, false); if (is_object($oLinkedObj)) { - $aRow = $this->GetFormRow($oP, $oLinkedObj, $iObjectId, array(), $oCurrentObj, $iAdditionId); // Not yet created link get negative Ids + $oBlock = new BlockIndirectLinksEditTable($this); + $aRow = $oBlock->GetFormRow($oP, $oLinkedObj, $iObjectId, array(), $oCurrentObj, $iAdditionId); // Not yet created link get negative Ids $oRow = new FormTableRow("{$this->m_sAttCode}{$this->m_sNameSuffix}", $this->m_aTableConfig, $aRow, -$iAdditionId); $oP->AddUiBlock($oRow); $iAdditionId++; @@ -575,7 +276,8 @@ JS foreach ($aLinkedObjectIds as $iObjectId) { $oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $iObjectId, false); if (is_object($oLinkedObj)) { - $aRow = $this->GetFormRow($oP, $oLinkedObj, $iObjectId, array(), $oCurrentObj, $iAdditionId); // Not yet created link get negative Ids + $oBlock = new BlockIndirectLinksEditTable($this); + $aRow = $oBlock->GetFormRow($oP, $oLinkedObj, $iObjectId, array(), $oCurrentObj, $iAdditionId); // Not yet created link get negative Ids $aData = []; foreach ($aRow as $item) { $aData[] = $item; @@ -636,24 +338,77 @@ JS /** @var AttributeExternalKey $oAttDef */ $sTargetClass = $oAttDef->GetTargetClass(); $sHierarchicalKeyCode = MetaModel::IsHierarchicalClass($sTargetClass); - if ($sHierarchicalKeyCode !== false) - { + if ($sHierarchicalKeyCode !== false) { $oFilter = new DBObjectSearch($sTargetClass); $oFilter->AddCondition('id', $defaultValue); $oHKFilter = new DBObjectSearch($sTargetClass); $oHKFilter->AddCondition_PointingTo($oFilter, $sHierarchicalKeyCode, TREE_OPERATOR_BELOW); $oSearch->AddCondition_PointingTo($oHKFilter, $sAttCode); } - } catch (Exception $e) - { } - } - else - { + catch (Exception $e) { + } + } else { $oSearch->AddCondition($sAttCode, $defaultValue); } } } } } + + public function GetLinkedSetId(): string + { + return "{$this->m_sAttCode}{$this->m_sNameSuffix}"; + } + + public function GetClass(): string + { + return $this->m_sClass; + } + + public function GetLinkedClass(): string + { + return $this->m_sLinkedClass; + } + + public function GetAttCode(): string + { + return $this->m_sAttCode; + } + + public function GetInputId(): string + { + return $this->m_sInputId; + } + + public function GetNameSuffix(): string + { + return $this->m_sNameSuffix; + } + + public function IsDuplicatesAllowed(): bool + { + return $this->m_bDuplicatesAllowed; + } + + public function GetExternalKeyToRemote(): string + { + return $this->m_sExtKeyToRemote; + } + + public function GetExternalKeyToMe(): string + { + return $this->m_sExtKeyToMe; + } + + public function GetRemoteClass(): string + { + return $this->m_sRemoteClass; + } + + public function GetEditableFields(): array + { + return $this->m_aEditableFields; + } + } diff --git a/css/backoffice/components/_datatable.scss b/css/backoffice/components/_datatable.scss index 4160af243..08ae5d00a 100644 --- a/css/backoffice/components/_datatable.scss +++ b/css/backoffice/components/_datatable.scss @@ -148,4 +148,20 @@ $ibo-fieldsorter--selected--background-color: $ibo-color-blue-200 !default; @extend %ibo-font-ral-sembol-100; border-bottom: $ibo-vendors-datatables--columns-header--border-bottom; } +} + + + +#table-row-action-confirmation-dialog{ + + .ibo-row-action--confirmation--explanation{ + margin-bottom: 16px; + } + + .ibo-row-action--confirmation--do-not-show-again--checkbox{ + height: auto; + display: inline-block; + width: auto; + } + } \ No newline at end of file diff --git a/dictionaries/ui/application/links/en.dictionary.itop.links.php b/dictionaries/ui/application/links/en.dictionary.itop.links.php new file mode 100644 index 000000000..dbba7cbf4 --- /dev/null +++ b/dictionaries/ui/application/links/en.dictionary.itop.links.php @@ -0,0 +1,26 @@ + 'Detach item', + 'UI:Links:ActionRow:detach:confirmation' => 'Do you really want to detach {item} from current object ?', + 'UI:Links:ActionRow:delete' => 'Delete item', + 'UI:Links:ActionRow:delete:confirmation' => 'Do you really want to delete {item} from current object ?', +)); \ No newline at end of file diff --git a/dictionaries/ui/components/datatable/en.dictionary.itop.datatable.php b/dictionaries/ui/components/datatable/en.dictionary.itop.datatable.php index 6f584e4d4..3c104b368 100644 --- a/dictionaries/ui/components/datatable/en.dictionary.itop.datatable.php +++ b/dictionaries/ui/components/datatable/en.dictionary.itop.datatable.php @@ -19,16 +19,18 @@ // Display DataTable Dict::Add('EN US', 'English', 'English', array( - 'UI:Datatables:Language:Processing' => 'Please wait...', - 'UI:Datatables:Language:LengthMenu' => '_MENU_ per page', - 'UI:Datatables:Language:ZeroRecords' => 'No result', - 'UI:Datatables:Language:Info' => '_TOTAL_ item(s)', - 'UI:Datatables:Language:InfoEmpty' => 'No information', - 'UI:Datatables:Language:EmptyTable' => 'No data available in this table', - 'UI:Datatables:Language:Error' => 'An error occured while running the query', - 'UI:Datatables:Language:DisplayLength:All' => 'All', - 'UI:Datatables:Language:Sort:Ascending' => 'enable for an ascending sort', - 'UI:Datatables:Language:Sort:Descending' => 'enable for a descending sort', - 'UI:Datatables:Column:RowActions:Label' => '', - 'UI:Datatables:Column:RowActions:Description' => '', + 'UI:Datatables:Language:Processing' => 'Please wait...', + 'UI:Datatables:Language:LengthMenu' => '_MENU_ per page', + 'UI:Datatables:Language:ZeroRecords' => 'No result', + 'UI:Datatables:Language:Info' => '_TOTAL_ item(s)', + 'UI:Datatables:Language:InfoEmpty' => 'No information', + 'UI:Datatables:Language:EmptyTable' => 'No data available in this table', + 'UI:Datatables:Language:Error' => 'An error occured while running the query', + 'UI:Datatables:Language:DisplayLength:All' => 'All', + 'UI:Datatables:Language:Sort:Ascending' => 'enable for an ascending sort', + 'UI:Datatables:Language:Sort:Descending' => 'enable for a descending sort', + 'UI:Datatables:Column:RowActions:Label' => '', + 'UI:Datatables:Column:RowActions:Description' => '', + 'UI:Datatables:RowActions:ConfirmationDialog' => 'Action Confirmation', + 'UI:Datatables:RowActions:ConfirmationMessage' => 'Do you confirm action ?', )); \ No newline at end of file diff --git a/js/dataTables.main.js b/js/dataTables.main.js index bf7eb0bf4..43fdb7321 100644 --- a/js/dataTables.main.js +++ b/js/dataTables.main.js @@ -80,28 +80,3 @@ function getMultipleSelectionParams(listId) return oRes; } -/** - * Return column JSON declaration for row actions. - * Could be part of column or columnDefs declaration of datatable.js. - * - * @param sTableId - * @param iColumnTargetIndex - * @returns {*} - * @since 3.1.0 - */ -function getRowActionsColumnDefinition(sTableId, iColumnTargetIndex = -1) -{ - let aColumn = { - type: "html", - orderable: false, - render: function ( data, type, row, meta ) { - return $(`#${sTableId}_actions_buttons_template`).html(); - } - }; - - if (iColumnTargetIndex !== -1) { - aColumn['targets'] = iColumnTargetIndex; - } - - return aColumn; -} diff --git a/js/dataTables.row-actions.js b/js/dataTables.row-actions.js new file mode 100644 index 000000000..a7993a5e2 --- /dev/null +++ b/js/dataTables.row-actions.js @@ -0,0 +1,106 @@ +/* + * @copyright Copyright (C) 2010-2022 Combodo SARL + * @license http://opensource.org/licenses/AGPL-3.0 + */ + +const TABLE_ACTION_CONFIRMATION_PREFIX = 'table_action_row'; +const TABLE_ACTION_CONFIRMATION_DIALOG_SELECTOR = '#table-row-action-confirmation-dialog'; + +/** + * Return column JSON declaration for row actions. + * Could be part of column or columnDefs declaration of datatable.js. + * + * @param sTableId + * @param iColumnTargetIndex + * @returns {*} + * @since 3.1.0 + */ +function getRowActionsColumnDefinition(sTableId, iColumnTargetIndex = -1) +{ + let aColumn = { + type: "html", + orderable: false, + render: function ( data, type, row, meta ) { + return $(`#${sTableId}_actions_buttons_template`).html(); + } + }; + + if (iColumnTargetIndex !== -1) { + aColumn['targets'] = iColumnTargetIndex; + } + + return aColumn; +} + + +/** + * HandleActionRowConfirmation. + * + * @param sTitle title for confirmation dialog + * @param sMessage message of the confirmation dialog + * @param sDoNotShowAgainPreferenceKey iTop preference key to store "do not show again" flag + * @param oConfirmHandler confirm button handler + * @param aConfirmHandlerData confirm button handler data + * @constructor + */ +const HandleActionRowConfirmation = function (sTitle, sMessage, sDoNotShowAgainPreferenceKey, oConfirmHandler, aConfirmHandlerData){ + + // confirmation preference + if(sDoNotShowAgainPreferenceKey != null){ + + // retrieve need confirmation user preference + let bNeedConfirmation = GetUserPreferenceAsBoolean(`${TABLE_ACTION_CONFIRMATION_PREFIX}.${sDoNotShowAgainPreferenceKey}`, true); + + // confirm handler if no confirmation requested + if(!bNeedConfirmation){ + oConfirmHandler(aConfirmHandlerData.datatable, aConfirmHandlerData.tr_element, aConfirmHandlerData.action_id, aConfirmHandlerData.row_data); + return; + } + } + + // fill confirmation dialog + $('.ibo-row-action--confirmation--explanation', $(TABLE_ACTION_CONFIRMATION_DIALOG_SELECTOR)).html(sMessage); + $('.ibo-row-action--confirmation--do-not-show-again', $(TABLE_ACTION_CONFIRMATION_DIALOG_SELECTOR)).toggle(sDoNotShowAgainPreferenceKey != null); + + // open confirmation dialog + $(TABLE_ACTION_CONFIRMATION_DIALOG_SELECTOR).dialog({ + autoOpen: false, + minWidth: 400, + modal: true, + title: sTitle, + autoOpen: true, + position: {my: "center center", at: "center center", of: $('body')}, + close: function () { + // destroy dialog object + $(TABLE_ACTION_CONFIRMATION_DIALOG_SELECTOR).dialog( "destroy" ); + }, + buttons: [ + { + text: Dict.S('UI:Button:Cancel'), + class: 'ibo-is-alternative', + click: function () { + // close dialog + $(TABLE_ACTION_CONFIRMATION_DIALOG_SELECTOR).dialog('close'); + } + }, + { + text: Dict.S('UI:Button:Ok'), + class: 'ibo-is-primary', + click: function () { + // handle "do not show again" user preference + if(sDoNotShowAgainPreferenceKey != null){ + // save preference + const bDoNotShowAgain = $(this).find($('.ibo-row-action--confirmation--do-not-show-again--checkbox')).prop('checked'); + if (bDoNotShowAgain) { + SetUserPreference(`${TABLE_ACTION_CONFIRMATION_PREFIX}.${sDoNotShowAgainPreferenceKey}`, 'false', true); + } + } + // call confirm handler and close dialog + if(oConfirmHandler(aConfirmHandlerData.datatable, aConfirmHandlerData.tr_element, aConfirmHandlerData.action_id, aConfirmHandlerData.row_data)){ + $(TABLE_ACTION_CONFIRMATION_DIALOG_SELECTOR).dialog('close'); + } + } + }, + ], + }); +} diff --git a/js/links/link_set_worker.js b/js/links/link_set_worker.js new file mode 100644 index 000000000..3e1cb144f --- /dev/null +++ b/js/links/link_set_worker.js @@ -0,0 +1,58 @@ +let LinkSetWorker = new function(){ + + // defines + const ROUTER_BASE_URL = '../pages/ajax.render.php'; + const ROUTE_LINK_SET_DELETE_OBJECT = 'linkset.DeleteLinkedObject'; + const ROUTE_LINK_SET_DETACH_OBJECT = 'linkset.DetachLinkedObject'; + + /** + * CallAjaxDeleteLinkedObject. + * + * @param sLinkedObjectClass + * @param sLinkedObjectKey + * @constructor + */ + const CallAjaxDeleteLinkedObject = function(sLinkedObjectClass, sLinkedObjectKey){ + $.post(`${ROUTER_BASE_URL}?route=${ROUTE_LINK_SET_DELETE_OBJECT}`, { + linked_object_class: sLinkedObjectClass, + linked_object_key: sLinkedObjectKey, + transaction_id: $('#linkset_transactions_id').val() + }, function (data) { + if(data.data.success){ + alert('Operation succeeded, todo refresh table !!'); + } + else{ + alert('Operation failed, todo feedback !!'); + } + }); + }; + + /** + * CallAjaxDetachLinkedObject. + * + * @param sLinkedObjectClass + * @param sLinkedObjectKey + * @param sExternalKeyAttCode + * @constructor + */ + const CallAjaxDetachLinkedObject = function(sLinkedObjectClass, sLinkedObjectKey, sExternalKeyAttCode){ + $.post(`${ROUTER_BASE_URL}?route=${ROUTE_LINK_SET_DETACH_OBJECT}`, { + linked_object_class: sLinkedObjectClass, + linked_object_key: sLinkedObjectKey, + external_key_att_code: sExternalKeyAttCode, + transaction_id: $('#linkset_transactions_id').val() + }, function (data) { + if(data.data.success){ + alert('Operation succeeded, todo refresh table !!'); + } + else{ + alert('Operation failed, todo feedback !!'); + } + }); + }; + + return { + DeleteLinkedObject: CallAjaxDeleteLinkedObject, + DetachLinkedObject: CallAjaxDetachLinkedObject + } +}; \ No newline at end of file diff --git a/js/linksdirectwidget.js b/js/linksdirectwidget.js index b5fd109ca..38743b313 100644 --- a/js/linksdirectwidget.js +++ b/js/linksdirectwidget.js @@ -32,18 +32,19 @@ $(function() do_search: true, submit_to: '../pages/ajax.render.php', submit_parameters: {}, - labels: { 'delete': 'Delete', - modify: 'Modify...' , - creation_title: 'Creation of a new object...' , - create: 'Create...', - add: 'Add...', - remove: 'Remove', + labels: { + // 'delete': 'Delete', + // modify: 'Modify...' , + creation_title: 'Creation of a new object...' , + // create: 'Create...', + // add: 'Add...', + // remove: 'Remove', selection_title: 'Objects selection' }, - buttons: ['create', 'delete'], + // buttons: ['create', 'delete'], oWizardHelper: null }, - + // the constructor _create: function() { @@ -53,15 +54,14 @@ $(function() this.element .addClass('itop-directlinks'); - this.datatable = this.element.find('table.listResults'); - var aButtonsTypes = ['delete', 'remove', 'modify', 'add', 'create']; - this.oButtons = {}; - for(k in aButtonsTypes) - { - this.oButtons[aButtonsTypes[k]] = $(''); - } + // var aButtonsTypes = ['delete', 'remove', 'modify', 'add', 'create']; + // this.oButtons = {}; + // for(k in aButtonsTypes) + // { + // this.oButtons[aButtonsTypes[k]] = $(''); + // } this.indicator = $(''); this.inputToBeCreated = $(''); this.toBeCreated = {}; @@ -78,33 +78,33 @@ $(function() .after(this.inputToBeDeleted) .after(this.inputToBeAdded) .after(this.inputToBeRemoved) - .after('      ') .after(this.indicator); - for (k in this.options.buttons) { - this.element.after(this.oButtons[this.options.buttons[k]]).after('   '); - } + + // for (k in this.options.buttons) { + // this.element.after(this.oButtons[this.options.buttons[k]]).after('   '); + // } this.element.find('.selectList'+this.id).bind('change', function () { me._updateButtons(); }); - this.oButtons['delete'].on('click', function () { - $('.selectList'+me.id+':checked', me.element).each(function () { - me._deleteRow($(this)); - }); - }); - this.oButtons['create'].on('click', function () { - me._createRow(); - }); - this.oButtons['remove'].on('click', function () { - $('.selectList'+me.id+':checked', me.element).each(function () { - me._removeRow($(this)); - }); - }); - this.oButtons['add'].on('click', function () { - me._selectToAdd(); - }); + // this.oButtons['delete'].on('click', function () { + // me._deleteSelection(); + // }); + // this.oButtons['create'].on('click', function () { + // me._createRow(); + // }); + // this.oButtons['remove'].on('click', function () { + // $('.selectList'+me.id+':checked', me.element).each(function () { + // me._removeRow($(this)); + // }); + // }); + // this.oButtons['add'].on('click', function () { + // me._selectToAdd(); + // }); this._updateButtons(); + + me._updateTableInformation(); }, // called when created, and later when changing options @@ -135,24 +135,43 @@ $(function() var oChecked = $('.selectList'+this.id+':checked', this.element); switch (oChecked.length) { case 0: - this.oButtons['delete'].prop('disabled', true); - this.oButtons['remove'].prop('disabled', true); - this.oButtons['modify'].prop('disabled', true); + // this.oButtons['delete'].prop('disabled', true); + // this.oButtons['remove'].prop('disabled', true); + // this.oButtons['modify'].prop('disabled', true); break; case 1: - this.oButtons['delete'].prop('disabled', false); - this.oButtons['remove'].prop('disabled', false); - this.oButtons['modify'].prop('disabled', false); + // this.oButtons['delete'].prop('disabled', false); + // this.oButtons['remove'].prop('disabled', false); + // this.oButtons['modify'].prop('disabled', false); break; default: - this.oButtons['delete'].prop('disabled', false); - this.oButtons['remove'].prop('disabled', false); - this.oButtons['modify'].prop('disabled', true); + // this.oButtons['delete'].prop('disabled', false); + // this.oButtons['remove'].prop('disabled', false); + // this.oButtons['modify'].prop('disabled', true); break; } }, + _updateTableInformation: function(){ + + let nbChecked = $('tbody tr input:checked', this.element).length; + let count = $('tbody tr input', this.element).length; + + $('#linkedset_'+this.id+'_alert_information').toggleClass('ibo-is-information', nbChecked > 0); + + if(nbChecked > 0){ + $('#'+this.id+'_btnRemove').prop('disabled', false); + $('#linkedset_'+this.id+'_alert_information span[data-role="ibo-datatable-selection-value"]').text(nbChecked + ' / ' + count + ' éléments sélectionnés'); + } + else{ + $('#'+this.id+'_btnRemove').prop('disabled', true); + $('#linkedset_'+this.id+'_alert_information span[data-role="ibo-datatable-selection-value"]').text(count + ' éléments'); + } + }, + _onSelectChange: function () { + this._updateTableInformation(); + }, _updateTable: function () { var me = this; /* @@ -167,7 +186,7 @@ $(function() this.oDlg.dialog('option', {position: {my: "center", at: "center", of: window}}); }, _createRow: function () { - this.oButtons['create'].prop('disabled', true); + // this.oButtons['create'].prop('disabled', true); this.indicator.html(''); oParams = this.options.submit_parameters; oParams.operation = 'createObject'; @@ -206,14 +225,14 @@ $(function() } }); me.indicator.html(''); - me.oButtons['create'].prop('disabled', false); + // me.oButtons['create'].prop('disabled', false); me._updateDlgPosition(); }); }, _selectToAdd: function() { - this.oButtons['add'].prop('disabled', true); + // this.oButtons['add'].prop('disabled', true); this.indicator.html(''); oParams = this.options.submit_parameters; oParams.operation = 'selectObjectsToAdd'; @@ -279,7 +298,7 @@ $(function() }); me.indicator.html(''); - me.oButtons['add'].prop('disabled', false); + // me.oButtons['add'].prop('disabled', false); if (me.options.do_search) { me._onSearchToAdd(); @@ -425,7 +444,9 @@ $(function() var me = this; $.post(this.options.submit_to, oParams, function(data) { + var oInserted = $(data); + oInserted.find('input:checkbox').each(function() { var iKey = parseInt($(this).val(), 10); // Number in base 10 me.toBeAdded.push(iKey); @@ -436,12 +457,20 @@ $(function() me.inputToBeRemoved.val(JSON.stringify(me.toBeRemoved)); me.inputToBeDeleted.val(JSON.stringify(me.toBeDeleted)); - //me.datatable.find('tbody').append(data); $('#datatable_'+me.id+' .dataTables_empty').hide(); - me.datatable.find('tbody').append(data); + + // add actions on each row... + oInserted.each(function(){ + $('td:last-child',$(this)).after('' + $(`#datatable_${oParams.iInputId}_actions_buttons_template`).html() +''); + me.datatable.find('tbody').append(this.outerHTML); + }); + + me._updateTable(); me.indicator.html(''); - me.oButtons['add'].prop('disabled', false); + // me.oButtons['add'].prop('disabled', false); + + me._updateTableInformation(); }); }, subclassSelected: function() @@ -506,7 +535,7 @@ $(function() oParams.tempId = nextIdx; var me = this; - this.oButtons['create'].prop('disabled', true); + // this.oButtons['create'].prop('disabled', true); this.indicator.html(''); $.post(this.options.submit_to, oParams, function (data) { @@ -516,7 +545,7 @@ $(function() me._updateTable(); me.indicator.html(''); - me.oButtons['create'].prop('disabled', false); + // me.oButtons['create'].prop('disabled', false); }); } }, @@ -530,6 +559,12 @@ $(function() $('.wizContainer', this.oDlg).height(dlgHeight-20); $('#SearchResultsToAdd_'+this.id).height(dlgHeight-50-searchHeight); }, + _deleteSelection: function(){ + var me = this; + $('.selectList'+me.id+':checked', me.element).each(function () { + me._deleteRow($(this)); + }); + }, _deleteRow: function (oCheckbox) { var iObjKey = parseInt(oCheckbox.val(), 10); // Number in base 10 @@ -555,6 +590,13 @@ $(function() oRow.remove(); this._updateButtons(); this._updateTable(); + this._updateTableInformation(); + }, + _removeSelection: function(){ + var me = this; + $('.selectList'+me.id+':checked', me.element).each(function () { + me._removeRow($(this)); + }); }, _removeRow: function(oCheckbox) { diff --git a/js/linkswidget.js b/js/linkswidget.js index ae6334b84..792eb8562 100644 --- a/js/linkswidget.js +++ b/js/linkswidget.js @@ -15,6 +15,7 @@ * @param sExtKeyToRemote * @param bDoSearch * @param iMaxAddedId + * @param aRemoved * @constructor * * @since 3.0.0 Add iMaxAddedId parameter @@ -56,13 +57,47 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizH oInput.closest('form').on('submit', function () { return me.OnFormSubmit(); }); + + this.UpdateTableInformation(); }; + this.UpdateTableInformation = function(){ + + let nbChecked = $('#linkedset_'+me.id+' .selection:checked').length; + let count = $('#linkedset_'+this.id+' tbody tr').length; + + $('#linkedset_'+me.iInputId+'_alert_information').toggleClass('ibo-is-information', nbChecked > 0); + + if(nbChecked > 0){ + $('#'+me.id+'_btnRemove').prop('disabled', false); + $('#linkedset_'+me.iInputId+'_alert_information span[data-role="ibo-datatable-selection-value"]').text(nbChecked + ' / ' + count + ' éléments sélectionnés'); + } + else{ + $('#'+me.id+'_btnRemove').prop('disabled', true); + $('#linkedset_'+me.iInputId+'_alert_information span[data-role="ibo-datatable-selection-value"]').text(count + ' éléments'); + } + } + this.RemoveSelected = function () { let my_id = '#'+me.id; $('#linkedset_'+me.id+' .selection:checked').closest('tr').each(function () { - $('#datatable_'+me.id).DataTable().row($(this)).remove().draw(); - var oCheckbox = $(this).find('.selection'); + me.Remove($(this)); + }); + // Disable the button since all the selected items have been removed + $(my_id+'_btnRemove').prop('disabled', true); + + if ($('#linkedset_'+this.id+' .selection').length == 0) + { + // All items were removed: add a dummy hidden input to make sure that the linkset will be updated (emptied) when posted + $('#'+me.id+'_empty_row').show(); + } + + this.UpdateTableInformation(); + }; + + this.Remove = function(oRowElement){ + $('#datatable_'+me.id).DataTable().row($(oRowElement)).remove().draw(); + var oCheckbox = $(oRowElement).find('.selection'); let iLink = $(oCheckbox).attr('data-link-id'); if (iLink > 0) { me.aRemoved.push(iLink); @@ -79,32 +114,16 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizH } me.aAdded[iUniqueId] = null; } - }); - // Disable the button since all the selected items have been removed - $(my_id+'_btnRemove').prop('disabled', true); - if ($('#linkedset_'+this.id+' .selection').length == 0) - { - // All items were removed: add a dummy hidden input to make sure that the linkset will be updated (emptied) when posted - $('#'+me.id+'_empty_row').show(); + this.UpdateTableInformation(); } - }; this.OnSelectChange = function () { - let nbChecked = $('#linkedset_'+me.id+' .selection:checked').length; - if (nbChecked > 0) - { - $('#'+me.id+'_btnRemove').prop('disabled', false); - } - else - { - $('#'+me.id+'_btnRemove').prop('disabled', true); - } + this.UpdateTableInformation(); }; this.AddObjects = function () { let me = this; - $('#'+me.id+'_indicatorAdd').html(' '); me.oWizardHelper.UpdateWizard(); let sPromiseId = 'ajax_promise_'+me.id; @@ -179,6 +198,7 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizH }; this.DoAddObjects = function () { + let theMap = { sAttCode: me.sAttCode, iInputId: me.iInputId, diff --git a/lib/composer/InstalledVersions.php b/lib/composer/InstalledVersions.php index 17663926c..d50e0c9fc 100644 --- a/lib/composer/InstalledVersions.php +++ b/lib/composer/InstalledVersions.php @@ -29,6 +29,10 @@ class InstalledVersions * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array}|array{}|null */ private static $installed; + + /** + * @var bool|null + */ private static $canGetVendors; /** diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index e9fba7d2c..5f47496fb 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -229,6 +229,8 @@ return array( 'Combodo\\iTop\\Application\\UI\\Base\\Component\\DataTable\\StaticTable\\FormTable\\FormTable' => $baseDir . '/sources/Application/UI/Base/Component/DataTable/StaticTable/FormTable/FormTable.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\DataTable\\StaticTable\\StaticTable' => $baseDir . '/sources/Application/UI/Base/Component/DataTable/StaticTable/StaticTable.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\DataTable\\tTableRowActions' => $baseDir . '/sources/Application/UI/Base/Component/DataTable/tTableRowActions.php', + 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Dialog\\Dialog' => $baseDir . '/sources/Application/UI/Base/Component/Dialog/Dialog.php', + 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Dialog\\DialogUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/Dialog/DialogUIBlockFactory.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\FieldBadge\\FieldBadge' => $baseDir . '/sources/Application/UI/Base/Component/FieldBadge/FieldBadge.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\FieldBadge\\FieldBadgeUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/FieldBadge/FieldBadgeUIBlockFactory.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\FieldSet\\FieldSet' => $baseDir . '/sources/Application/UI/Base/Component/FieldSet/FieldSet.php', @@ -342,8 +344,12 @@ return array( 'Combodo\\iTop\\Application\\UI\\DisplayBlock\\BlockCsv\\BlockCsv' => $baseDir . '/sources/Application/UI/DisplayBlock/BlockCsv/BlockCsv.php', 'Combodo\\iTop\\Application\\UI\\DisplayBlock\\BlockList\\BlockList' => $baseDir . '/sources/Application/UI/DisplayBlock/BlockList/BlockList.php', 'Combodo\\iTop\\Application\\UI\\Helper\\UIHelper' => $baseDir . '/sources/Application/UI/Helper/UIHelper.php', - 'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockIndirectLinksEdit\\BlockIndirectLinksEdit' => $baseDir . '/sources/Application/UI/Links/Indirect/BlockIndirectLinksEdit/BlockIndirectLinksEdit.php', - 'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockObjectPickerDialog\\BlockObjectPickerDialog' => $baseDir . '/sources/Application/UI/Links/Indirect/BlockObjectPickerDialog/BlockObjectPickerDialog.php', + 'Combodo\\iTop\\Application\\UI\\Links\\AbstractBlockLinksViewTable' => $baseDir . '/sources/Application/UI/Links/AbstractBlockLinksViewTable.php', + 'Combodo\\iTop\\Application\\UI\\Links\\Direct\\BlockDirectLinksEditTable' => $baseDir . '/sources/Application/UI/Links/Direct/BlockDirectLinksEditTable.php', + 'Combodo\\iTop\\Application\\UI\\Links\\Direct\\BlockDirectLinksViewTable' => $baseDir . '/sources/Application/UI/Links/Direct/BlockDirectLinksViewTable.php', + 'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockIndirectLinksEditTable' => $baseDir . '/sources/Application/UI/Links/Indirect/BlockIndirectLinksEditTable.php', + 'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockIndirectLinksViewTable' => $baseDir . '/sources/Application/UI/Links/Indirect/BlockIndirectLinksViewTable.php', + 'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockObjectPickerDialog' => $baseDir . '/sources/Application/UI/Links/Indirect/BlockObjectPickerDialog.php', 'Combodo\\iTop\\Application\\UI\\Preferences\\BlockShortcuts\\BlockShortcuts' => $baseDir . '/sources/Application/UI/Preferences/BlockShortcuts/BlockShortcuts.php', 'Combodo\\iTop\\Application\\UI\\Printable\\BlockPrintHeader\\BlockPrintHeader' => $baseDir . '/sources/Application/UI/Printable/BlockPrintHeader/BlockPrintHeader.php', 'Combodo\\iTop\\Composer\\iTopComposer' => $baseDir . '/sources/Composer/iTopComposer.php', @@ -351,6 +357,7 @@ return array( 'Combodo\\iTop\\Controller\\AjaxRenderController' => $baseDir . '/sources/Controller/AjaxRenderController.php', 'Combodo\\iTop\\Controller\\Base\\Layout\\ActivityPanelController' => $baseDir . '/sources/Controller/Base/Layout/ActivityPanelController.php', 'Combodo\\iTop\\Controller\\Base\\Layout\\ObjectController' => $baseDir . '/sources/Controller/Base/Layout/ObjectController.php', + 'Combodo\\iTop\\Controller\\Links\\LinkSetController' => $baseDir . '/sources/Controller/Links/LinksetController.php', 'Combodo\\iTop\\Controller\\OAuth\\OAuthLandingController' => $baseDir . '/sources/Controller/OAuth/OAuthLandingController.php', 'Combodo\\iTop\\Controller\\PreferencesController' => $baseDir . '/sources/Controller/PreferencesController.php', 'Combodo\\iTop\\Controller\\iController' => $baseDir . '/sources/Controller/iController.php', @@ -2927,6 +2934,7 @@ return array( 'iTopStandardURLMaker' => $baseDir . '/application/applicationcontext.class.inc.php', 'iTopWebPage' => $baseDir . '/sources/Application/WebPage/iTopWebPage.php', 'iTopWizardWebPage' => $baseDir . '/sources/Application/WebPage/iTopWizardWebPage.php', + 'iTopXmlException' => $baseDir . '/application/exceptions/iTopXmlException.php', 'iWorkingTimeComputer' => $baseDir . '/core/computing.inc.php', 'lnkTriggerAction' => $baseDir . '/core/trigger.class.inc.php', 'ormCaseLog' => $baseDir . '/core/ormcaselog.class.inc.php', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 4255e16cd..f3e5238ac 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -594,6 +594,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Combodo\\iTop\\Application\\UI\\Base\\Component\\DataTable\\StaticTable\\FormTable\\FormTable' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/DataTable/StaticTable/FormTable/FormTable.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\DataTable\\StaticTable\\StaticTable' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/DataTable/StaticTable/StaticTable.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\DataTable\\tTableRowActions' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/DataTable/tTableRowActions.php', + 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Dialog\\Dialog' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Dialog/Dialog.php', + 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Dialog\\DialogUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Dialog/DialogUIBlockFactory.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\FieldBadge\\FieldBadge' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/FieldBadge/FieldBadge.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\FieldBadge\\FieldBadgeUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/FieldBadge/FieldBadgeUIBlockFactory.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\FieldSet\\FieldSet' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/FieldSet/FieldSet.php', @@ -707,8 +709,12 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Combodo\\iTop\\Application\\UI\\DisplayBlock\\BlockCsv\\BlockCsv' => __DIR__ . '/../..' . '/sources/Application/UI/DisplayBlock/BlockCsv/BlockCsv.php', 'Combodo\\iTop\\Application\\UI\\DisplayBlock\\BlockList\\BlockList' => __DIR__ . '/../..' . '/sources/Application/UI/DisplayBlock/BlockList/BlockList.php', 'Combodo\\iTop\\Application\\UI\\Helper\\UIHelper' => __DIR__ . '/../..' . '/sources/Application/UI/Helper/UIHelper.php', - 'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockIndirectLinksEdit\\BlockIndirectLinksEdit' => __DIR__ . '/../..' . '/sources/Application/UI/Links/Indirect/BlockIndirectLinksEdit/BlockIndirectLinksEdit.php', - 'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockObjectPickerDialog\\BlockObjectPickerDialog' => __DIR__ . '/../..' . '/sources/Application/UI/Links/Indirect/BlockObjectPickerDialog/BlockObjectPickerDialog.php', + 'Combodo\\iTop\\Application\\UI\\Links\\AbstractBlockLinksViewTable' => __DIR__ . '/../..' . '/sources/Application/UI/Links/AbstractBlockLinksViewTable.php', + 'Combodo\\iTop\\Application\\UI\\Links\\Direct\\BlockDirectLinksEditTable' => __DIR__ . '/../..' . '/sources/Application/UI/Links/Direct/BlockDirectLinksEditTable.php', + 'Combodo\\iTop\\Application\\UI\\Links\\Direct\\BlockDirectLinksViewTable' => __DIR__ . '/../..' . '/sources/Application/UI/Links/Direct/BlockDirectLinksViewTable.php', + 'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockIndirectLinksEditTable' => __DIR__ . '/../..' . '/sources/Application/UI/Links/Indirect/BlockIndirectLinksEditTable.php', + 'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockIndirectLinksViewTable' => __DIR__ . '/../..' . '/sources/Application/UI/Links/Indirect/BlockIndirectLinksViewTable.php', + 'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockObjectPickerDialog' => __DIR__ . '/../..' . '/sources/Application/UI/Links/Indirect/BlockObjectPickerDialog.php', 'Combodo\\iTop\\Application\\UI\\Preferences\\BlockShortcuts\\BlockShortcuts' => __DIR__ . '/../..' . '/sources/Application/UI/Preferences/BlockShortcuts/BlockShortcuts.php', 'Combodo\\iTop\\Application\\UI\\Printable\\BlockPrintHeader\\BlockPrintHeader' => __DIR__ . '/../..' . '/sources/Application/UI/Printable/BlockPrintHeader/BlockPrintHeader.php', 'Combodo\\iTop\\Composer\\iTopComposer' => __DIR__ . '/../..' . '/sources/Composer/iTopComposer.php', @@ -716,6 +722,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Combodo\\iTop\\Controller\\AjaxRenderController' => __DIR__ . '/../..' . '/sources/Controller/AjaxRenderController.php', 'Combodo\\iTop\\Controller\\Base\\Layout\\ActivityPanelController' => __DIR__ . '/../..' . '/sources/Controller/Base/Layout/ActivityPanelController.php', 'Combodo\\iTop\\Controller\\Base\\Layout\\ObjectController' => __DIR__ . '/../..' . '/sources/Controller/Base/Layout/ObjectController.php', + 'Combodo\\iTop\\Controller\\Links\\LinkSetController' => __DIR__ . '/../..' . '/sources/Controller/Links/LinksetController.php', 'Combodo\\iTop\\Controller\\OAuth\\OAuthLandingController' => __DIR__ . '/../..' . '/sources/Controller/OAuth/OAuthLandingController.php', 'Combodo\\iTop\\Controller\\PreferencesController' => __DIR__ . '/../..' . '/sources/Controller/PreferencesController.php', 'Combodo\\iTop\\Controller\\iController' => __DIR__ . '/../..' . '/sources/Controller/iController.php', @@ -3292,6 +3299,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'iTopStandardURLMaker' => __DIR__ . '/../..' . '/application/applicationcontext.class.inc.php', 'iTopWebPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/iTopWebPage.php', 'iTopWizardWebPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/iTopWizardWebPage.php', + 'iTopXmlException' => __DIR__ . '/../..' . '/application/exceptions/iTopXmlException.php', 'iWorkingTimeComputer' => __DIR__ . '/../..' . '/core/computing.inc.php', 'lnkTriggerAction' => __DIR__ . '/../..' . '/core/trigger.class.inc.php', 'ormCaseLog' => __DIR__ . '/../..' . '/core/ormcaselog.class.inc.php', diff --git a/lib/composer/installed.php b/lib/composer/installed.php index f74f47b1d..21e2f87ab 100644 --- a/lib/composer/installed.php +++ b/lib/composer/installed.php @@ -5,7 +5,7 @@ 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => '8a3e07dd80c8316d68ad44a892c51f4ed5de572c', + 'reference' => '0520e72b9d1190cab294aebc5ab7542a18d2bc51', 'name' => 'combodo/itop', 'dev' => true, ), @@ -25,7 +25,7 @@ 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => '8a3e07dd80c8316d68ad44a892c51f4ed5de572c', + 'reference' => '0520e72b9d1190cab294aebc5ab7542a18d2bc51', 'dev_requirement' => false, ), 'combodo/tcpdf' => array( diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 2e9177ec5..97d2a623b 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -1056,7 +1056,7 @@ EOF $aParams = utils::ReadParam('params', '', false, 'raw_data'); $sDashletClass = $aParams['attr_dashlet_class']; $sDashletType = $aParams['attr_dashlet_type']; - $sDashletId = utils::HtmlEntities($aParams['attr_dashlet_id']); + $sDashletId = utils::HtmlEntities($aParams['attr_dashlet_id']); $aUpdatedProperties = $aParams['updated']; // Code of the changed properties as an array: 'attr_xxx', 'attr_xxy', etc... $aPreviousValues = $aParams['previous_values']; // hash array: 'attr_xxx' => 'old_value' if (is_subclass_of($sDashletClass, 'Dashlet')) { diff --git a/sources/Application/UI/Base/Component/DataTable/DataTable.php b/sources/Application/UI/Base/Component/DataTable/DataTable.php index b7acb0eee..a43fb7248 100644 --- a/sources/Application/UI/Base/Component/DataTable/DataTable.php +++ b/sources/Application/UI/Base/Component/DataTable/DataTable.php @@ -40,6 +40,7 @@ class DataTable extends UIContentBlock 'js/dataTables.main.js', 'js/dataTables.settings.js', 'js/dataTables.pipeline.js', + 'js/dataTables.row-actions.js', ]; protected $aOptions;//list of specific options for display datatable @@ -52,6 +53,8 @@ class DataTable extends UIContentBlock */ protected $aInitDisplayData; + public const DEFAULT_ACTION_ROW_CONFIRMATION = true; + /** * Panel constructor. diff --git a/sources/Application/UI/Base/Component/DataTable/DataTableUIBlockFactory.php b/sources/Application/UI/Base/Component/DataTable/DataTableUIBlockFactory.php index ebabc69f9..289e8711b 100644 --- a/sources/Application/UI/Base/Component/DataTable/DataTableUIBlockFactory.php +++ b/sources/Application/UI/Base/Component/DataTable/DataTableUIBlockFactory.php @@ -211,7 +211,7 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory foreach ($oTable->GetRowActions() as $iKey => $aAction) { $oButton = ButtonUIBlockFactory::MakeIconAction( array_key_exists('icon_classes', $aAction) ? $aAction['icon_classes'] : 'fas fa-question', - array_key_exists('tooltip', $aAction) ? $aAction['tooltip'] : '', + array_key_exists('tooltip', $aAction) ? Dict::S($aAction['tooltip']) : '', array_key_exists('name', $aAction) ? $aAction['name'] : 'undefined' ); $oButton->SetDataAttributes(['action-id' => $iKey]); @@ -522,7 +522,6 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory $oDataTable->SetRowActions($aExtraParams['row_actions']); } - return $oDataTable; } diff --git a/sources/Application/UI/Base/Component/DataTable/StaticTable/StaticTable.php b/sources/Application/UI/Base/Component/DataTable/StaticTable/StaticTable.php index 8ccc845de..2bdfd4865 100644 --- a/sources/Application/UI/Base/Component/DataTable/StaticTable/StaticTable.php +++ b/sources/Application/UI/Base/Component/DataTable/StaticTable/StaticTable.php @@ -36,6 +36,7 @@ class StaticTable extends UIContentBlock 'js/dataTables.main.js', 'js/dataTables.settings.js', 'js/dataTables.pipeline.js', + 'js/dataTables.row-actions.js', ]; /** diff --git a/sources/Application/UI/Base/Component/DataTable/tTableRowActions.php b/sources/Application/UI/Base/Component/DataTable/tTableRowActions.php index e69fb1a32..9fef83ae2 100644 --- a/sources/Application/UI/Base/Component/DataTable/tTableRowActions.php +++ b/sources/Application/UI/Base/Component/DataTable/tTableRowActions.php @@ -6,6 +6,10 @@ namespace Combodo\iTop\Application\UI\Base\Component\DataTable; +use Combodo\iTop\Application\UI\Base\Component\Dialog\DialogUIBlockFactory; +use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory; +use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory; + /** * Trait tTableRowActions * @@ -17,12 +21,20 @@ namespace Combodo\iTop\Application\UI\Base\Component\DataTable; */ trait tTableRowActions { + /** @var bool static dialog initialized flag to avoid multiple html markups */ + static public bool $bDialogInitialized = false; + /** * @var $aRowActions array array of row actions * action => { * tooltip: string, * icon_classes: string, - * js_row_action: string + * js_row_action: string, + * confirmation => { + * message: string, + * message_row_data: string, + * remember_choice_pref_key: string + * } * } */ protected $aRowActions; @@ -70,4 +82,31 @@ trait tTableRowActions { return DataTableUIBlockFactory::MakeActionRowToolbarTemplate($this); } + + /** + * GetRowActionsConfirmDialog. + * + * @return \Combodo\iTop\Application\UI\Base\Component\Html\Html + */ + public function GetRowActionsConfirmDialog() + { + static::$bDialogInitialized = true; + + $oDialog = DialogUIBlockFactory::MakeNeutral('', '
', 'table-row-action-confirmation-dialog'); + + $oContent = UIContentBlockUIBlockFactory::MakeStandard(); + $oContent->AddCSSClass('ibo-row-action--confirmation--do-not-show-again'); + $checkBox = InputUIBlockFactory::MakeStandard('checkbox', 'do_not_show_again', false); + $checkBox->AddCSSClass('ibo-row-action--confirmation--do-not-show-again--checkbox'); + $checkBox->SetLabel(\Dict::S('UI:UserPref:DoNotShowAgain')); + $oContent->AddSubBlock($checkBox); + $oDialog->AddSubBlock($oContent); + + return $oDialog; + } + + public function GetRowActionsConfirmDialogInitializedFlag() + { + return static::$bDialogInitialized; + } } \ No newline at end of file diff --git a/sources/Application/UI/Base/Component/Dialog/Dialog.php b/sources/Application/UI/Base/Component/Dialog/Dialog.php new file mode 100644 index 000000000..722ba2902 --- /dev/null +++ b/sources/Application/UI/Base/Component/Dialog/Dialog.php @@ -0,0 +1,109 @@ +sContent = $sContent; + if (!empty($sContent)) { + $this->AddSubBlock(new Html($sContent)); + } + } + + /** + * @return string + */ + public function GetTitle(): string + { + return $this->sTitle; + } + + /** + * @param string $sTitle Title of the alert + * + * @return $this + */ + public function SetTitle(string $sTitle): Dialog + { + $this->sTitle = $sTitle; + + return $this; + } + + /** + * Return the raw HTML content, should be already sanitized. + * + * @return string + */ + public function GetContent(): string + { + return $this->sContent; + } + + /** + * Set the raw HTML content, must be already sanitized. + * + * @param string $sContent The raw HTML content, must be already sanitized + * + * @return $this + */ + public function SetContent(string $sContent): Dialog + { + $this->sContent = $sContent; + + return $this; + } + + + +} \ No newline at end of file diff --git a/sources/Application/UI/Base/Component/Dialog/DialogUIBlockFactory.php b/sources/Application/UI/Base/Component/Dialog/DialogUIBlockFactory.php new file mode 100644 index 000000000..0fa444794 --- /dev/null +++ b/sources/Application/UI/Base/Component/Dialog/DialogUIBlockFactory.php @@ -0,0 +1,54 @@ +oAttDef = $oAttDef; + $this->sAttCode = $sAttCode; + $this->sObjectClass = $sObjectClass; + $this->oDbObject = $oDbObject; + + // Initialization + $this->Init(); + + // UI Initialization + $this->InitUI($oPage); + } + + /** + * Init. + * + * @return void + * @throws \Exception + */ + private function Init() + { + $this->sTargetClass = $this->GetTargetClass(); + } + + /** + * Initialize UI. + * + * @return void + * @throws \CoreException + */ + private function InitUI(\WebPage $oPage) + { + // header + $this->InitHeader();; + + // Table + $this->InitTable($oPage); + } + + /** + * InitHeader. + * + * @return void + * @throws \CoreException + */ + private function InitHeader() + { + // MedallionIcon + $oClassIcon = new MedallionIcon(MetaModel::GetClassIcon($this->sTargetClass, false)); + $oClassIcon->SetDescription($this->oAttDef->GetDescription())->AddCSSClass('ibo-block-list--medallion'); + $this->AddSubBlock($oClassIcon); + } + + /** + * InitTable. + * + * @param \WebPage $oPage + * + * @return void + * @throws \ApplicationException + * @throws \ArchivedObjectException + * @throws \CoreException + * @throws \CoreWarning + * @throws \DictExceptionMissingString + * @throws \MySQLException + */ + private function InitTable(\WebPage $oPage) + { + // retrieve db object set + $oOrmLinkSet = $this->oDbObject->Get($this->sAttCode); + $oLinkSet = $oOrmLinkSet->ToDBObjectSet(\utils::ShowObsoleteData()); + + // add list block + $oBlock = new \DisplayBlock($oLinkSet->GetFilter(), 'list', false); + $this->AddSubBlock($oBlock->GetRenderContent($oPage, $this->GetExtraParam(), 'rel_'.$this->sAttCode)); + } + + /** + * GetExtraParam. + * + * Provide parameters for display block as list. + * + * @see \DisplayBlock::RenderList + * + * @return array + * @throws \ArchivedObjectException + * @throws \CoreException + */ + abstract function GetExtraParam(): array; + + /** + * Return row actions. + * + * Register row actions for table. + * + * @see \Combodo\iTop\Application\UI\Base\Component\DataTable\tTableRowActions + * + * @return \string[][] + */ + abstract function GetRowActions(): array; + + /** + * GetTargetClass. + * + * Return link set target class. + * + * @return string + * @throws \Exception + */ + abstract function GetTargetClass(): string; +} \ No newline at end of file diff --git a/sources/Application/UI/Links/Direct/BlockDirectLinksEditTable.php b/sources/Application/UI/Links/Direct/BlockDirectLinksEditTable.php new file mode 100644 index 000000000..164b44c64 --- /dev/null +++ b/sources/Application/UI/Links/Direct/BlockDirectLinksEditTable.php @@ -0,0 +1,249 @@ +oUILinksDirectWidget = $oUILinksDirectWidget; + + // compute + $this->aLabels = array( + 'delete' => Dict::S('UI:Button:Delete'), + 'creation_title' => Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->oUILinksDirectWidget->GetLinkedClass())), + 'create' => Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($this->oUILinksDirectWidget->GetLinkedClass())), + 'remove' => Dict::S('UI:Button:Remove'), + 'add' => Dict::Format('UI:AddAnExisting_Class', MetaModel::GetName($this->oUILinksDirectWidget->GetLinkedClass())), + 'selection_title' => Dict::Format('UI:SelectionOf_Class', MetaModel::GetName($this->oUILinksDirectWidget->GetLinkedClass())), + ); + $oContext = new \ApplicationContext(); + $this->sSubmitUrl = \utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?'.$oContext->GetForLink(); + + // Don't automatically launch the search if the table is huge + $bDoSearch = !\utils::IsHighCardinality($this->oUILinksDirectWidget->GetLinkedClass()); + $this->sJSDoSearch = $bDoSearch ? 'true' : 'false'; + + // Initialization + $this->Init(); + + // Initialize UI + $this->InitUI(); + } + + /** + * Initialisation. + * + * @return void + * @throws \Exception + */ + private function Init() + { + $this->oAttributeLinkedSet = MetaModel::GetAttributeDef($this->oUILinksDirectWidget->GetClass(), $this->oUILinksDirectWidget->GetAttCode()); + } + + /** + * Initialize UI. + * + * @return void + * @throws \CoreException + * @throws \Exception + */ + private function InitUI() + { + // MedallionIcon + $oClassIcon = new MedallionIcon(MetaModel::GetClassIcon($this->oUILinksDirectWidget->GetLinkedClass(), false)); + $oClassIcon->SetDescription($this->oAttributeLinkedSet->GetDescription()); + $oClassIcon->AddCSSClass('ibo-block-list--medallion'); + $this->AddSubBlock($oClassIcon); + } + + /** + * @param \WebPage $oPage + * @param \DBObjectSet $oValue + * @param string $sFormPrefix + * + * @return void + */ + public function InitTable(\WebPage $oPage, \DBObjectSet $oValue, string $sFormPrefix) + { + /** @todo fields initialization */ + $this->sInputName = $sFormPrefix.'attr_'.$this->oUILinksDirectWidget->GetAttCode(); + $this->sWizHelper = 'oWizardHelper'.$sFormPrefix; + + try { + $aAttribs = $this->oUILinksDirectWidget->GetTableConfig(); + $aRows = $this->GetTableRows($oPage, $oValue); + $aRowActions = $this->GetRowActions(); + $oDatatable = DataTableUIBlockFactory::MakeForForm($this->oUILinksDirectWidget->GetInputId(), $aAttribs, $aRows, '', $aRowActions); + $oDatatable->SetOptions(['select_mode' => 'custom', 'disable_hyperlinks' => true]); + $aTablePanel = PanelUIBlockFactory::MakeNeutral(''); + $aTablePanel->SetSubTitle(sprintf('Total: %d objects.', count($aRows))); + $oToolbar = ToolbarUIBlockFactory::MakeForButton(); + $oActionButtonUnlink = ButtonUIBlockFactory::MakeNeutral('Unlink'); + $oActionButtonUnlink->SetOnClickJsCode("$('#{$this->oUILinksDirectWidget->GetInputId()}').directlinks('instance')._removeSelection();"); + $oToolbar->AddSubBlock($oActionButtonUnlink); + $oActionButtonLink = ButtonUIBlockFactory::MakeNeutral('Link'); + $oActionButtonLink->SetOnClickJsCode("$('#{$this->oUILinksDirectWidget->GetInputId()}').directlinks('instance')._selectToAdd();"); + $oToolbar->AddSubBlock($oActionButtonLink); + $oActionButtonCreate = ButtonUIBlockFactory::MakeNeutral('Create'); + $oActionButtonCreate->SetOnClickJsCode("$('#{$this->oUILinksDirectWidget->GetInputId()}').directlinks('instance')._createRow();"); + $oToolbar->AddSubBlock($oActionButtonCreate); + $oActionButtonDelete = ButtonUIBlockFactory::MakeNeutral('Delete'); + $oActionButtonDelete->SetOnClickJsCode("$('#{$this->oUILinksDirectWidget->GetInputId()}').directlinks('instance')._deleteSelection();"); + $oToolbar->AddSubBlock($oActionButtonDelete); + $aTablePanel->AddToolbarBlock($oToolbar); + $aTablePanel->AddSubBlock($oDatatable); + $this->AddSubBlock($aTablePanel); + } + catch (\Exception $e) { + $oAlert = AlertUIBlockFactory::MakeForDanger('error', 'error while trying to load datatable'); + $oAlert->SetIsClosable(false); + $oAlert->SetIsCollapsible(false); + $this->AddSubBlock($oAlert); + } + } + + /** + * Return table rows. + * + * @param \DBObjectSet $oValue + * + * @return array + * @throws \ArchivedObjectException + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \DictExceptionMissingString + * @throws \MySQLException + * @throws \Exception + */ + private function GetTableRows(\WebPage $oPage, \DBObjectSet $oValue): array + { + // result data + $aRows = array(); + + // set pointer to start + $oValue->Rewind(); + + // create a row table for each value... + while ($oLinkObj = $oValue->Fetch()) { + $aRow = array(); + $aRow['form::select'] = ''; + foreach ($this->oUILinksDirectWidget->GetZList() as $sLinkedAttCode) { + $aRow[$sLinkedAttCode] = $oLinkObj->GetAsHTML($sLinkedAttCode); + + // tentative d'ajout des attributs en édition +// $sValue = $oLinkObj->Get($sLinkedAttCode); +// $sDisplayValue = $oLinkObj->GetEditValue($sLinkedAttCode); +// $oAttDef = MetaModel::GetAttributeDef($this->oUILinksDirectWidget->GetLinkedClass(), $sLinkedAttCode); +// +// $aRow[$sLinkedAttCode] = '
' +// .\cmdbAbstractObject::GetFormElementForField( +// $oPage, +// $this->oUILinksDirectWidget->GetLinkedClass(), +// $sLinkedAttCode, +// $oAttDef, +// $sValue, +// $sDisplayValue, +// $this->GetFieldId($oValue, $sLinkedAttCode), +// ']', +// 0, +// [] +// ) +// .'
'; + } + $aRows[] = $aRow; + } + + return $aRows; + } + + private function GetFieldId($iLnkId, $sFieldCode, $bSafe = true) + { + $sFieldId = $this->oUILinksDirectWidget->GetInputId().'_'.$sFieldCode.'['.$iLnkId.']'; + + return ($bSafe) ? \utils::GetSafeId($sFieldId) : $sFieldId; + } + + /** + * Return row actions. + * + * @return \string[][] + */ + private function GetRowActions(): array + { + $aRowActions = array(); + + if (!$this->oAttributeLinkedSet->GetReadOnly()) { + $aRowActions[] = array( + 'tooltip' => 'remove link', + 'icon_classes' => 'fas fa-minus', + 'js_row_action' => "$('#{$this->oUILinksDirectWidget->GetInputId()}').directlinks('instance')._deleteRow($(':checkbox', oTrElement));", + ); + } + + return $aRowActions; + } +} \ No newline at end of file diff --git a/sources/Application/UI/Links/Direct/BlockDirectLinksViewTable.php b/sources/Application/UI/Links/Direct/BlockDirectLinksViewTable.php new file mode 100644 index 000000000..aed230084 --- /dev/null +++ b/sources/Application/UI/Links/Direct/BlockDirectLinksViewTable.php @@ -0,0 +1,103 @@ +oAttDef->GetLinkedClass(); + } + + /** @inheritdoc * */ + public function GetExtraParam(): array + { + return array( + 'target_attr' => $this->oAttDef->GetExtKeyToMe(), + 'object_id' => $this->oDbObject->GetKey(), + 'menu' => MetaModel::GetConfig()->Get('allow_menu_on_linkset'), + 'default' => $this->GetDefault(), + 'table_id' => $this->sObjectClass.'_'.$this->sAttCode, + 'row_actions' => $this->GetRowActions(), + ); + } + + /** @inheritdoc * */ + public function GetRowActions(): array + { + $aRowActions = array(); + + if (!$this->oAttDef->GetReadOnly()) { + + switch ($this->oAttDef->GetRelationType()) { + + case LINKSET_RELATIONTYPE_LINK: + $aRowActions[] = array( + 'tooltip' => 'UI:Links:ActionRow:detach', + 'icon_classes' => 'fas fa-minus', + 'js_row_action' => "LinkSetWorker.DetachLinkedObject('{$this->sTargetClass}', aRowData['{$this->sTargetClass}/_key_/raw'], '{$this->oAttDef->GetExtKeyToMe()}');", + 'confirmation' => [ + 'message' => 'UI:Links:ActionRow:detach:confirmation', + 'message_row_data' => "{$this->sTargetClass}/hyperlink", + 'remember_choice_pref_key' => 'LinkSetWorker.DetachLinkedObject', + ], + ); + break; + + case LINKSET_RELATIONTYPE_PROPERTY: + $aRowActions[] = array( + 'tooltip' => 'UI:Links:ActionRow:delete', + 'icon_classes' => 'fas fa-trash', + 'js_row_action' => "LinkSetWorker.DeleteLinkedObject('{$this->oAttDef->GetLinkedClass()}', aRowData['{$this->oAttDef->GetLinkedClass()}/_key_/raw']);", + 'confirmation' => [ + 'message' => 'UI:Links:ActionRow:delete:confirmation', + 'message_row_data' => "{$this->sTargetClass}/hyperlink", + 'remember_choice_pref_key' => 'LinkSetWorker.DeleteLinkedObject', + ], + ); + break; + } + } + + return $aRowActions; + } + + /** + * GetDefault. + * + * @return array + * @throws \ArchivedObjectException + * @throws \CoreException + * @throws \Exception + */ + private function GetDefault(): array + { + $aDefaults = array($this->oAttDef->GetExtKeyToMe() => $this->oDbObject->GetKey()); + $oAppContext = new \ApplicationContext(); + foreach ($oAppContext->GetNames() as $sKey) { + if (MetaModel::IsValidAttCode($this->sObjectClass, $sKey)) { + $aDefaults[$sKey] = $this->oDbObject->Get($sKey); + } + } + + return $aDefaults; + } +} \ No newline at end of file diff --git a/sources/Application/UI/Links/Indirect/BlockIndirectLinksEdit/BlockIndirectLinksEdit.php b/sources/Application/UI/Links/Indirect/BlockIndirectLinksEdit/BlockIndirectLinksEdit.php deleted file mode 100644 index 81002fe49..000000000 --- a/sources/Application/UI/Links/Indirect/BlockIndirectLinksEdit/BlockIndirectLinksEdit.php +++ /dev/null @@ -1,75 +0,0 @@ -AddSubBlock(InputUIBlockFactory::MakeForHidden("{$this->sFormPrefix}{$this->iInputId}", '', "{$this->sFormPrefix}{$this->iInputId}")); - - $oToolbar = ToolbarUIBlockFactory::MakeStandard(null, ['ibo-datatable--selection-validation-buttons-toolbar']); - $this->AddSubBlock($oToolbar); - $oRemoveButton = ButtonUIBlockFactory::MakeForSecondaryAction(Dict::S('UI:RemoveLinkedObjectsOf_Class'), null, null, false, "{$this->sLinkedSetId}_btnRemove"); - $oRemoveButton->SetOnClickJsCode("oWidget{$this->iInputId}.RemoveSelected();"); - $oToolbar->AddSubBlock($oRemoveButton); - - $oAddButton = ButtonUIBlockFactory::MakeForSecondaryAction(Dict::Format('UI:AddLinkedObjectsOf_Class', MetaModel::GetName($this->sRemoteClass)), null, null, false, "{$this->sLinkedSetId}_btnAdd"); - $oAddButton->SetOnClickJsCode("oWidget{$this->iInputId}.AddObjects();"); - $oToolbar->AddSubBlock($oAddButton); - - // To prevent adding forms inside the main form - $oDeferredBlock = new UIContentBlock("dlg_{$this->sLinkedSetId}", ['ibo-block-indirect-links--edit--dialog']); - $this->AddDeferredBlock($oDeferredBlock); - } -} \ No newline at end of file diff --git a/sources/Application/UI/Links/Indirect/BlockIndirectLinksEditTable.php b/sources/Application/UI/Links/Indirect/BlockIndirectLinksEditTable.php new file mode 100644 index 000000000..6d740ba15 --- /dev/null +++ b/sources/Application/UI/Links/Indirect/BlockIndirectLinksEditTable.php @@ -0,0 +1,442 @@ +GetLinkedSetId()}", ["ibo-block-indirect-links--edit"]); + + // Retrieve parameters + $this->oUILinksWidget = $oUILinksWidget; + + // Compute + $this->sDuplicates = ($oUILinksWidget->IsDuplicatesAllowed()) ? 'true' : 'false'; + $this->sJSDoSearch = \utils::IsHighCardinality($oUILinksWidget->GetRemoteClass()) ? 'false' : 'true'; // Don't automatically launch the search if the table is huge + + // Initialization + $this->Init(); + + // Initialize UI + $this->InitUI(); + } + + /** + * Initialization. + * + * @return void + * @throws \Exception + */ + private function Init() + { + $this->oAttributeLinkedSetIndirect = MetaModel::GetAttributeDef($this->oUILinksWidget->GetClass(), $this->oUILinksWidget->GetAttCode()); + } + + /** + * Initialize UI. + * + * @return void + * @throws \CoreException + * @throws \Exception + */ + private function InitUI() + { + // MedallionIcon + $oClassIcon = new MedallionIcon(MetaModel::GetClassIcon($this->oUILinksWidget->GetRemoteClass(), false)); + $oClassIcon->SetDescription($this->oAttributeLinkedSetIndirect->GetDescription()); + $oClassIcon->AddCSSClass('ibo-block-list--medallion'); + $this->AddSubBlock($oClassIcon); + + // To prevent adding forms inside the main form + $oDeferredBlock = new UIContentBlock("dlg_{$this->oUILinksWidget->GetLinkedSetId()}", ['ibo-block-indirect-links--edit--dialog']); + $this->AddDeferredBlock($oDeferredBlock); + } + + /** + * CreateTableInformationAlert. + * + * @return iUIBlock + */ + private function CreateTableInformationAlert(): iUIBlock + { + // Selection alert + $oAlert = AlertUIBlockFactory::MakeNeutral('', '', "linkedset_{$this->oUILinksWidget->GetInputId()}_alert_information"); + $oAlert->AddCSSClasses([ + 'ibo-table--alert-information', + ]); + $oAlert->SetIsClosable(false); + $oAlert->SetIsCollapsible(false); + $oAlert->AddSubBlock(new Html('')); + + // Delete button + $oUIButton = ButtonUIBlockFactory::MakeForDestructiveAction("Détacher les {$this->oUILinksWidget->GetRemoteClass()}", 'table-selection'); + $oUIButton->SetOnClickJsCode("oWidget{$this->oUILinksWidget->GetInputId()}.RemoveSelected();"); + $oUIButton->AddCSSClass('ibo-table--alert-information--delete-button'); + $oAlert->AddSubBlock($oUIButton); + + // Add button + $oUIAddButton = ButtonUIBlockFactory::MakeForPrimaryAction("Attacher des {$this->oUILinksWidget->GetRemoteClass()}", 'table-selection'); + $oUIAddButton->AddCSSClass('ibo-table--alert-information--add-button'); + $oUIAddButton->SetOnClickJsCode("oWidget{$this->oUILinksWidget->GetInputId()}.AddObjects();"); + $oAlert->AddSubBlock($oUIAddButton); + + return $oAlert; + } + + /** + * @param \WebPage $oPage + * @param $oValue + * @param $aArgs + * @param $sFormPrefix + * @param $oCurrentObj + * @param $aTableConfig + * + * @return void + */ + public function InitTable(\WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj, $aTableConfig) + { + $this->AddSubBlock(InputUIBlockFactory::MakeForHidden("{$sFormPrefix}{$this->oUILinksWidget->GetInputId()}", '', "{$sFormPrefix}{$this->oUILinksWidget->GetInputId()}")); + $this->sWizHelper = 'oWizardHelper'.$sFormPrefix; + $oValue->Rewind(); + $aForm = array(); + $iMaxAddedId = 0; + $iAddedId = -1; // Unique id for new links + $this->aRemoved = json_decode(\utils::ReadPostedParam("attr_{$sFormPrefix}{$this->oUILinksWidget->GetAttCode()}_tbd", '[]', 'raw_data')); + while ($oCurrentLink = $oValue->Fetch()) { + // We try to retrieve the remote object as usual + if (!in_array($oCurrentLink->GetKey(), $this->aRemoved)) { + $oLinkedObj = MetaModel::GetObject($this->oUILinksWidget->GetRemoteClass(), $oCurrentLink->Get($this->oUILinksWidget->GetExternalKeyToRemote()), false /* Must not be found */); + // If successful, it means that we can edit its link + if ($oLinkedObj !== null) { + $bReadOnly = false; + } // Else we retrieve it without restrictions (silos) and will display its link as readonly + else { + $bReadOnly = true; + $oLinkedObj = MetaModel::GetObject($this->oUILinksWidget->GetRemoteClass(), $oCurrentLink->Get($this->oUILinksWidget->GetExternalKeyToRemote()), false /* Must not be found */, true); + } + + if ($oCurrentLink->IsNew()) { + $key = $iAddedId--; + } else { + $key = $oCurrentLink->GetKey(); + } + + $iMaxAddedId = max($iMaxAddedId, $key); + $aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj, $key, $bReadOnly); + } + } + $this->iMaxAddedId = (int)$iMaxAddedId; + + // Datatable + $aRowActions = $this->GetRowActions(); + $oDataTable = DataTableUIBlockFactory::MakeForForm("{$this->oUILinksWidget->GetAttCode()}{$this->oUILinksWidget->GetNameSuffix()}", $aTableConfig, $aForm, '', $aRowActions); + $oDataTable->SetOptions([ + 'select_mode' => 'custom', + 'disable_hyperlinks' => true, + ]); + $aTablePanel = PanelUIBlockFactory::MakeNeutral(''); + $aTablePanel->SetSubTitle(sprintf('Total: %d objects.', count($aForm))); + $oToolbar = ToolbarUIBlockFactory::MakeForButton(); + $oActionButtonUnlink = ButtonUIBlockFactory::MakeNeutral('Unlink'); + $oActionButtonUnlink->SetOnClickJsCode("oWidget{$this->oUILinksWidget->GetInputId()}.RemoveSelected();"); + $oToolbar->AddSubBlock($oActionButtonUnlink); + $oActionButtonLink = ButtonUIBlockFactory::MakeNeutral('Link'); + $oActionButtonLink->SetOnClickJsCode("oWidget{$this->oUILinksWidget->GetInputId()}.AddObjects();"); + $oToolbar->AddSubBlock($oActionButtonLink); + $aTablePanel->AddToolbarBlock($oToolbar); + $aTablePanel->AddSubBlock($oDataTable); + + $this->AddSubBlock($aTablePanel); + } + + /** + * A one-row form for editing a link record + * + * @param WebPage $oP Web page used for the ouput + * @param DBObject $oLinkedObj Remote object + * @param DBObject|int $linkObjOrId Either the lnk object or a unique number for new link records to add + * @param array $aArgs Extra context arguments + * @param DBObject $oCurrentObj The object to which all the elements of the linked set refer to + * @param int $iUniqueId A unique identifier of new links + * @param boolean $bReadOnly Display link as editable or read-only. Default is false (editable) + * + * @return array The HTML fragment of the one-row form + * @throws \ArchivedObjectException + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \Exception + */ + public function GetFormRow(\WebPage $oP, \DBObject $oLinkedObj, $linkObjOrId, $aArgs, $oCurrentObj, $iUniqueId, $bReadOnly = false) + { + $sPrefix = "{$this->oUILinksWidget->GetAttCode()}{$this->oUILinksWidget->GetNameSuffix()}"; + $aRow = array(); + $aFieldsMap = array(); + $iKey = 0; + + if (is_object($linkObjOrId) && (!$linkObjOrId->IsNew())) { + $iKey = $linkObjOrId->GetKey(); + $iRemoteObjKey = $linkObjOrId->Get($this->oUILinksWidget->GetExternalKeyToRemote()); + $sPrefix .= "[$iKey]["; + $sNameSuffix = "]"; // To make a tabular form + $aArgs['prefix'] = $sPrefix; + $aArgs['wizHelper'] = "oWizardHelper{$this->oUILinksWidget->GetInputId()}{$iKey}"; + $aArgs['this'] = $linkObjOrId; + + if ($bReadOnly) { + $aRow['form::checkbox'] = ""; + foreach ($this->m_aEditableFields as $sFieldCode) { + $sDisplayValue = $linkObjOrId->GetEditValue($sFieldCode); + $aRow[$sFieldCode] = $sDisplayValue; + } + } else { + $aRow['form::checkbox'] = "oUILinksWidget->GetInputId()}.OnSelectChange();\" value=\"$iKey\">"; + foreach ($this->oUILinksWidget->GetEditableFields() as $sFieldCode) { + $sSafeFieldId = $this->GetFieldId($linkObjOrId->GetKey(), $sFieldCode); + $this->AddRowForFieldCode($aRow, $sFieldCode, $aArgs, $linkObjOrId, $oP, $sNameSuffix, $sSafeFieldId); + $aFieldsMap[$sFieldCode] = $sSafeFieldId; + } + } + + $sState = $linkObjOrId->GetState(); + $sRemoteKeySafeFieldId = $this->GetFieldId($aArgs['this']->GetKey(), $this->oUILinksWidget->GetExternalKeyToRemote());; + } else { + // form for creating a new record + if (is_object($linkObjOrId)) { + // New link existing only in memory + $oNewLinkObj = $linkObjOrId; + $iRemoteObjKey = $oNewLinkObj->Get($this->oUILinksWidget->GetExternalKeyToRemote()); + $oNewLinkObj->Set($this->oUILinksWidget->GetExternalKeyToMe(), + $oCurrentObj); // Setting the extkey with the object also fills the related external fields + } else { + $iRemoteObjKey = $linkObjOrId; + $oNewLinkObj = MetaModel::NewObject($this->oUILinksWidget->GetLinkedClass()); + $oRemoteObj = MetaModel::GetObject($this->oUILinksWidget->GetRemoteClass(), $iRemoteObjKey); + $oNewLinkObj->Set($this->oUILinksWidget->GetExternalKeyToRemote(), + $oRemoteObj); // Setting the extkey with the object alsoo fills the related external fields + $oNewLinkObj->Set($this->oUILinksWidget->GetExternalKeyToMe(), + $oCurrentObj); // Setting the extkey with the object also fills the related external fields + } + $sPrefix .= "[-$iUniqueId]["; + $sNameSuffix = "]"; // To make a tabular form + $aArgs['prefix'] = $sPrefix; + $aArgs['wizHelper'] = "oWizardHelper{$this->oUILinksWidget->GetInputId()}_".($iUniqueId < 0 ? -$iUniqueId : $iUniqueId); + $aArgs['this'] = $oNewLinkObj; + $sInputValue = $iUniqueId > 0 ? "-$iUniqueId" : "$iUniqueId"; + $aRow['form::checkbox'] = "oUILinksWidget->GetInputId()}.OnSelectChange();\" value=\"$sInputValue\">"; + + if ($iUniqueId > 0) { + // Rows created with ajax call need OnLinkAdded call. + // + $oP->add_ready_script( + <<oUILinksWidget->GetInputId()}.OnLinkAdded($iUniqueId, $iRemoteObjKey); +EOF + ); + } else { + // Rows added before loading the form don't have to call OnLinkAdded. + // Listeners are already present and DOM is not recreated + $iPositiveUniqueId = -$iUniqueId; + $oP->add_ready_script(<<oUILinksWidget->GetInputId()}.AddLink($iPositiveUniqueId, $iRemoteObjKey); +EOF + ); + } + + foreach ($this->oUILinksWidget->GetEditableFields() as $sFieldCode) { + $sSafeFieldId = $this->GetFieldId($iUniqueId, $sFieldCode); + $this->AddRowForFieldCode($aRow, $sFieldCode, $aArgs, $oNewLinkObj, $oP, $sNameSuffix, $sSafeFieldId); + $aFieldsMap[$sFieldCode] = $sSafeFieldId; + + $sValue = $oNewLinkObj->Get($sFieldCode); + $oP->add_ready_script( + <<oUILinksWidget->GetInputId()}.OnValueChange($iKey, $iUniqueId, '$sFieldCode', '$sValue'); +JS + ); + } + + $sState = ''; + $sRemoteKeySafeFieldId = $this->GetFieldId($iUniqueId, $this->oUILinksWidget->GetExternalKeyToRemote()); + } + + if (!$bReadOnly) { + $sExtKeyToMeId = \utils::GetSafeId($sPrefix.$this->oUILinksWidget->GetExternalKeyToMe()); + $aFieldsMap[$this->oUILinksWidget->GetExternalKeyToMe()] = $sExtKeyToMeId; + $aRow['form::checkbox'] .= "GetKey()."\">"; + + $sExtKeyToRemoteId = \utils::GetSafeId($sPrefix.$this->oUILinksWidget->GetExternalKeyToRemote()); + $aFieldsMap[$this->oUILinksWidget->GetExternalKeyToRemote()] = $sExtKeyToRemoteId; + $aRow['form::checkbox'] .= ""; + } + + // Adding fields from remote class + // all fields are embedded in a span + added to $aFieldsMap array so that we can refresh them after extkey change + $aRemoteFieldsMap = []; + foreach (MetaModel::GetZListItems($this->oUILinksWidget->GetRemoteClass(), 'list') as $sFieldCode) { + $sSafeFieldId = $this->GetFieldId($aArgs['this']->GetKey(), $sFieldCode); + $aRow['static::'.$sFieldCode] = "".$oLinkedObj->GetAsHTML($sFieldCode).''; + $aRemoteFieldsMap[$sFieldCode] = $sSafeFieldId; + } + // id field is needed so that remote object could be load server side + $aRemoteFieldsMap['id'] = $sRemoteKeySafeFieldId; + + // Generate WizardHelper to update dependant fields + $this->AddWizardHelperInit($oP, $aArgs['wizHelper'], $this->oUILinksWidget->GetLinkedClass(), $sState, $aFieldsMap); + //instantiate specific WizarHelper instance for remote class fields refresh + $bHasExtKeyUpdatingRemoteClassFields = ( + array_key_exists('replaceDependenciesByRemoteClassFields', $aArgs) + && ($aArgs['replaceDependenciesByRemoteClassFields']) + ); + if ($bHasExtKeyUpdatingRemoteClassFields) { + $this->AddWizardHelperInit($oP, $aArgs['wizHelperRemote'], $this->oUILinksWidget->GetRemoteClass(), $sState, $aRemoteFieldsMap); + } + + return $aRow; + } + + private function AddRowForFieldCode(&$aRow, $sFieldCode, &$aArgs, $oLnk, $oP, $sNameSuffix, $sSafeFieldId): void + { + if (($sFieldCode === $this->oUILinksWidget->GetExternalKeyToRemote())) { + // current field is the lnk extkey to the remote class + $aArgs['replaceDependenciesByRemoteClassFields'] = true; + $sRowFieldCode = 'static::key'; + $aArgs['wizHelperRemote'] = $aArgs['wizHelper'].'_remote'; + $aRemoteAttDefs = MetaModel::GetZListAttDefsFilteredForIndirectRemoteClass($this->oUILinksWidget->GetRemoteClass()); + $aRemoteCodes = array_map( + function ($value) { + return $value->GetCode(); + }, + $aRemoteAttDefs + ); + $aArgs['remoteCodes'] = $aRemoteCodes; + } else { + $aArgs['replaceDependenciesByRemoteClassFields'] = false; + $sRowFieldCode = $sFieldCode; + } + $sValue = $oLnk->Get($sFieldCode); + $sDisplayValue = $oLnk->GetEditValue($sFieldCode); + $oAttDef = MetaModel::GetAttributeDef($this->oUILinksWidget->GetLinkedClass(), $sFieldCode); + + $aRow[$sRowFieldCode] = '
' + .\cmdbAbstractObject::GetFormElementForField( + $oP, + $this->oUILinksWidget->GetLinkedClass(), + $sFieldCode, + $oAttDef, + $sValue, + $sDisplayValue, + $sSafeFieldId, + $sNameSuffix, + 0, + $aArgs + ) + .'
'; + } + + private function GetFieldId($iLnkId, $sFieldCode, $bSafe = true) + { + $sFieldId = $this->oUILinksWidget->GetInputId().'_'.$sFieldCode.'['.$iLnkId.']'; + + return ($bSafe) ? \utils::GetSafeId($sFieldId) : $sFieldId; + } + + private function AddWizardHelperInit($oP, $sWizardHelperVarName, $sWizardHelperClass, $sState, $aFieldsMap): void + { + $iFieldsCount = count($aFieldsMap); + $sJsonFieldsMap = json_encode($aFieldsMap); + + $oP->add_script( + <<oAttributeLinkedSetIndirect->GetReadOnly()) { + $aRowActions[] = array( + 'tooltip' => 'remove link', + 'icon_classes' => 'fas fa-minus', + 'js_row_action' => "oWidget{$this->oUILinksWidget->GetInputId()}.Remove(oTrElement);", + ); + } + + return $aRowActions; + } + +} \ No newline at end of file diff --git a/sources/Application/UI/Links/Indirect/BlockIndirectLinksViewTable.php b/sources/Application/UI/Links/Indirect/BlockIndirectLinksViewTable.php new file mode 100644 index 000000000..db9114990 --- /dev/null +++ b/sources/Application/UI/Links/Indirect/BlockIndirectLinksViewTable.php @@ -0,0 +1,109 @@ +oAttDef->GetLinkedClass(), $this->oAttDef->GetExtKeyToRemote()); + + return $oLinkingAttDef->GetTargetClass(); + } + catch (Exception $e) { + return '?'; + } + } + + /** @inheritdoc */ + public function GetExtraParam(): array + { + return array( + 'link_attr' => $this->oAttDef->GetExtKeyToMe(), + 'object_id' => $this->oDbObject->GetKey(), + 'target_attr' => $this->oAttDef->GetExtKeyToRemote(), + 'view_link' => false, + 'menu' => false, + 'display_limit' => true, + 'table_id' => $this->sObjectClass.'_'.$this->sAttCode, + 'zlist' => false, + 'extra_fields' => $this->GetAttCodesToDisplay(), + 'row_actions' => $this->GetRowActions(), + ); + } + + /** @inheritdoc */ + public function GetRowActions(): array + { + $aRowActions = array(); + + if (!$this->oAttDef->GetReadOnly()) { + + $aRowActions[] = array( + 'tooltip' => 'UI:Links:ActionRow:detach', + 'icon_classes' => 'fas fa-minus', + 'js_row_action' => "LinkSetWorker.DeleteLinkedObject('{$this->oAttDef->GetLinkedClass()}', aRowData['Link/_key_/raw']);", + 'confirmation' => [ + 'message' => 'UI:Links:ActionRow:detach:confirmation', + 'message_row_data' => "Remote/hyperlink", + 'remember_choice_pref_key' => 'LinkSetWorker.DetachLinkedObject', + ], + ); + + } + + return $aRowActions; + } + + /** + * GetAttCodesToDisplay. + * + * @return string + * @throws \CoreException + */ + private function GetAttCodesToDisplay(): string + { + $oLinkingAttDef = MetaModel::GetAttributeDef($this->oAttDef->GetLinkedClass(), $this->oAttDef->GetExtKeyToRemote()); + $sLinkingAttCode = $oLinkingAttDef->GetCode(); + $sTargetClass = $oLinkingAttDef->GetTargetClass(); + + $aLnkAttDefsToDisplay = MetaModel::GetZListAttDefsFilteredForIndirectLinkClass($this->sObjectClass, $this->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); + + return implode(',', $aAttCodesToDisplay); + } +} \ No newline at end of file diff --git a/sources/Application/UI/Links/Indirect/BlockObjectPickerDialog/BlockObjectPickerDialog.php b/sources/Application/UI/Links/Indirect/BlockObjectPickerDialog.php similarity index 52% rename from sources/Application/UI/Links/Indirect/BlockObjectPickerDialog/BlockObjectPickerDialog.php rename to sources/Application/UI/Links/Indirect/BlockObjectPickerDialog.php index 5762d7adf..5d3350cd2 100644 --- a/sources/Application/UI/Links/Indirect/BlockObjectPickerDialog/BlockObjectPickerDialog.php +++ b/sources/Application/UI/Links/Indirect/BlockObjectPickerDialog.php @@ -4,31 +4,40 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ -namespace Combodo\iTop\Application\UI\Links\Indirect\BlockObjectPickerDialog; +namespace Combodo\iTop\Application\UI\Links\Indirect; - -use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Form\Form; use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory; -use Combodo\iTop\Application\UI\Base\Component\Toolbar\ToolbarUIBlockFactory; use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock; use Dict; /** * Class BlockObjectPickerDialog * - * @package Combodo\iTop\Application\UI\Links\Indirect\BlockObjectPickerDialog + * @internal + * @package Combodo\iTop\Application\UI\Links\Indirect */ class BlockObjectPickerDialog extends UIContentBlock { // Overloaded constants - public const BLOCK_CODE = 'ibo-block-object-picker-dialog'; + public const BLOCK_CODE = 'ibo-block-object-picker-dialog'; public const DEFAULT_JS_ON_READY_TEMPLATE_REL_PATH = 'application/links/indirect/block-object-picker-dialog/layout'; - public $sLinkedSetId; - public $iInputId; - public $sLinkedClassName; - public $sClassName; + /** @var \UILinksWidget */ + public \UILinksWidget $oUILinksWidget; + + /** + * Constructor. + * + * @param \UILinksWidget $oUILinksWidget + */ + public function __construct(\UILinksWidget $oUILinksWidget) + { + parent::__construct(); + + // Retrieve parameters + $this->oUILinksWidget = $oUILinksWidget; + } public function AddForm() { @@ -36,13 +45,13 @@ class BlockObjectPickerDialog extends UIContentBlock $sCancel = Dict::S('UI:Button:Cancel'); $sAdd = Dict::S('UI:Button:Add'); - $oForm = new Form("ObjectsAddForm_{$this->sLinkedSetId}"); + $oForm = new Form("ObjectsAddForm_{$this->oUILinksWidget->GetLinkedSetId()}"); $this->AddSubBlock($oForm); - $oBlock = new UIContentBlock("SearchResultsToAdd_{$this->sLinkedSetId}", ['ibo-block-object-picker-dialog--results']); + $oBlock = new UIContentBlock("SearchResultsToAdd_{$this->oUILinksWidget->GetLinkedSetId()}", ['ibo-block-object-picker-dialog--results']); $oForm->AddSubBlock($oBlock); $oBlock->AddHtml("

{$sEmptyList}

"); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("count_{$this->sLinkedSetId}", '0', "count_{$this->sLinkedSetId}")); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("count_{$this->oUILinksWidget->GetLinkedSetId()}", '0', "count_{$this->oUILinksWidget->GetLinkedSetId()}")); } } \ No newline at end of file diff --git a/sources/Controller/Links/LinksetController.php b/sources/Controller/Links/LinksetController.php new file mode 100644 index 000000000..90811a1fa --- /dev/null +++ b/sources/Controller/Links/LinksetController.php @@ -0,0 +1,103 @@ +DBDelete(); + $bOperationSuccess = (count($oDeletionPlan->GetIssues()) === 0); + if (!$bOperationSuccess) { + $sErrorMessage = json_encode($oDeletionPlan->GetIssues()); + } + } + catch (\Exception $e) { + $sErrorMessage = $e->getMessage(); + } + } else { + $sErrorMessage = 'invalid transaction id'; + } + $oPage->SetData([ + 'success' => $bOperationSuccess, + 'error_message' => $sErrorMessage, + ]); + + return $oPage; + } + + /** + * OperationDetachLinkedObject. + * + * @return \JsonPage + */ + public function OperationDetachLinkedObject(): \JsonPage + { + $oPage = new \JsonPage(); + $sErrorMessage = null; + $bOperationSuccess = false; + + // retrieve parameters + $sLinkedObjectClass = utils::ReadParam('linked_object_class', '', false, utils::ENUM_SANITIZATION_FILTER_CLASS); + $sLinkedObjectKey = utils::ReadParam('linked_object_key', 0, false, utils::ENUM_SANITIZATION_FILTER_STRING); + $sExternalKeyAttCode = utils::ReadParam('external_key_att_code', null, false, utils::ENUM_SANITIZATION_FILTER_STRING); + $sTransactionId = utils::ReadParam('transaction_id', null, false, utils::ENUM_SANITIZATION_FILTER_TRANSACTION_ID); + + // check transaction id + if (utils::IsTransactionValid($sTransactionId, false)) { + try { + $oLinkedObject = MetaModel::GetObject($sLinkedObjectClass, $sLinkedObjectKey); + $oLinkedObject->Set($sExternalKeyAttCode, null); + $oLinkedObject->DBWrite(); + $bOperationSuccess = true; + } + catch (\Exception $e) { + $sErrorMessage = $e->getMessage(); + } + } else { + $sErrorMessage = 'invalid transaction id'; + } + $oPage->SetData([ + 'success' => $bOperationSuccess, + 'error_message' => $sErrorMessage, + ]); + + return $oPage; + } + + +} \ No newline at end of file diff --git a/templates/application/links/direct/block-direct-links-edit-table/layout.js.twig b/templates/application/links/direct/block-direct-links-edit-table/layout.js.twig new file mode 100644 index 000000000..73340066b --- /dev/null +++ b/templates/application/links/direct/block-direct-links-edit-table/layout.js.twig @@ -0,0 +1,12 @@ +{# @copyright Copyright (C) 2010-2021 Combodo SARL #} +{# @license http://opensource.org/licenses/AGPL-3.0 #} +{% apply spaceless %} +oWidget{{ oUIBlock.oUILinksDirectWidget.GetInputId() }} = $('#{{ oUIBlock.oUILinksDirectWidget.GetInputId() }}').directlinks({ + class_name: '{{ oUIBlock.oUILinksDirectWidget.GetClass() }}', + att_code: '{{ oUIBlock.oUILinksDirectWidget.GetAttCode() }}', + input_name: '{{ oUIBlock.sInputName }}', + submit_to: '{{ oUIBlock.sSubmitUrl }}', + oWizardHelper: {{ oUIBlock.sWizHelper }}, + do_search: '{{ oUIBlock.sJSDoSearch }}' +}); +{% endapply %} \ No newline at end of file diff --git a/templates/application/links/indirect/block-indirect-links-edit-table/layout.js.twig b/templates/application/links/indirect/block-indirect-links-edit-table/layout.js.twig new file mode 100644 index 000000000..205769c60 --- /dev/null +++ b/templates/application/links/indirect/block-indirect-links-edit-table/layout.js.twig @@ -0,0 +1,18 @@ +{# @copyright Copyright (C) 2010-2021 Combodo SARL #} +{# @license http://opensource.org/licenses/AGPL-3.0 #} +{% apply spaceless %} +oWidget{{ oUIBlock.oUILinksWidget.GetInputId() }} = new LinksWidget( + '{{ oUIBlock.oUILinksWidget.GetLinkedSetId() }}', + '{{ oUIBlock.oUILinksWidget.GetClass() }}', + '{{ oUIBlock.oUILinksWidget.GetAttCode() }}', + '{{ oUIBlock.oUILinksWidget.GetInputId() }}', + '{{ oUIBlock.oUILinksWidget.GetNameSuffix() }}', + {{ oUIBlock.sDuplicates }}, + {{ oUIBlock.sWizHelper }}, + '{{ oUIBlock.oUILinksWidget.GetExternalKeyToRemote() }}', + {{ oUIBlock.sJSDoSearch }}, + {{ oUIBlock.iMaxAddedId }}, + {{ oUIBlock.aRemoved | json_encode | raw }} +); +oWidget{{ oUIBlock.oUILinksWidget.GetInputId() }}.Init(); +{% endapply %} \ No newline at end of file diff --git a/templates/application/links/indirect/block-indirect-links-edit/layout.js.twig b/templates/application/links/indirect/block-indirect-links-edit/layout.js.twig deleted file mode 100644 index d94ed6e31..000000000 --- a/templates/application/links/indirect/block-indirect-links-edit/layout.js.twig +++ /dev/null @@ -1,18 +0,0 @@ -{# @copyright Copyright (C) 2010-2021 Combodo SARL #} -{# @license http://opensource.org/licenses/AGPL-3.0 #} -{% apply spaceless %} -oWidget{{ oUIBlock.iInputId }} = new LinksWidget( - '{{ oUIBlock.sLinkedSetId }}', - '{{ oUIBlock.sClass }}', - '{{ oUIBlock.sAttCode }}', - '{{ oUIBlock.iInputId }}', - '{{ oUIBlock.sNameSuffix }}', - {{ oUIBlock.bDuplicates }}, - {{ oUIBlock.oWizHelper }}, - '{{ oUIBlock.sExtKeyToRemote }}', - {{ oUIBlock.bJSDoSearch }}, - {{ oUIBlock.iMaxAddedId }}, - {{ oUIBlock.aRemoved | json_encode | raw }} -); -oWidget{{ oUIBlock.iInputId }}.Init(); -{% endapply %} \ No newline at end of file diff --git a/templates/application/links/indirect/block-object-picker-dialog/layout.ready.js.twig b/templates/application/links/indirect/block-object-picker-dialog/layout.ready.js.twig index aa812c42d..88f4f5c71 100644 --- a/templates/application/links/indirect/block-object-picker-dialog/layout.ready.js.twig +++ b/templates/application/links/indirect/block-object-picker-dialog/layout.ready.js.twig @@ -2,13 +2,13 @@ {# @license http://opensource.org/licenses/AGPL-3.0 #} {% apply spaceless %} -$('#dlg_{{ oUIBlock.sLinkedSetId }}').dialog({ +$('#dlg_{{ oUIBlock.oUILinksWidget.GetLinkedSetId() }}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, - title:"{{ 'UI:AddObjectsOf_Class_LinkedWith_Class'|dict_format(oUIBlock.sLinkedClassName, oUIBlock.sClassName) }}" , + title:"{{ 'UI:AddObjectsOf_Class_LinkedWith_Class'|dict_format(oUIBlock.oUILinksWidget.GetLinkedClass(), oUIBlock.oUILinksWidget.GetClass()) }}" , autoOpen: false, modal: true, - resizeStop: oWidget{{ oUIBlock.iInputId }}.UpdateSizes, + resizeStop: oWidget{{ oUIBlock.oUILinksWidget.GetInputId() }}.UpdateSizes, buttons: [ { text: "{{ 'UI:Button:Cancel'| dict_s }}", @@ -20,16 +20,16 @@ $('#dlg_{{ oUIBlock.sLinkedSetId }}').dialog({ { text: "{{ 'UI:Button:Add'| dict_s }}", class: "ibo-is-regular ibo-is-primary", - id: "btn_ok_{{ oUIBlock.sLinkedSetId }}", + id: "btn_ok_{{ oUIBlock.oUILinksWidget.GetLinkedSetId() }}", click: function() { - return oWidget{{ oUIBlock.iInputId }}.DoAddObjects(); + return oWidget{{ oUIBlock.oUILinksWidget.GetInputId() }}.DoAddObjects(); } }, ], }); -$('#SearchFormToAdd_{{ oUIBlock.sLinkedSetId }} form').bind('submit.uilinksWizard', oWidget{{ oUIBlock.iInputId }}.SearchObjectsToAdd); -$('#SearchFormToAdd_{{ oUIBlock.sLinkedSetId }}').resize(oWidget{{ oUIBlock.iInputId }}.UpdateSizes); +$('#SearchFormToAdd_{{ oUIBlock.oUILinksWidget.GetLinkedSetId() }} form').bind('submit.uilinksWizard', oWidget{{ oUIBlock.oUILinksWidget.GetInputId() }}.SearchObjectsToAdd); +$('#SearchFormToAdd_{{ oUIBlock.oUILinksWidget.GetLinkedSetId() }}').resize(oWidget{{ oUIBlock.oUILinksWidget.GetInputId() }}.UpdateSizes); {% endapply %} \ No newline at end of file diff --git a/templates/base/components/datatable/layout.html.twig b/templates/base/components/datatable/layout.html.twig index 9ec38c93a..bc72bca3c 100644 --- a/templates/base/components/datatable/layout.html.twig +++ b/templates/base/components/datatable/layout.html.twig @@ -21,11 +21,14 @@ {{ aColumn["attribute_label"] }} {% endfor %} {% if oUIBlock.HasRowActions() %} - {{ 'UI:Datatables:Column:RowActions:Label'|dict_s }} + {{ 'UI:Datatables:Column:RowActions:Label'|dict_s }} {% endif %} {% if oUIBlock.HasRowActions() %} {{ render_block(oUIBlock.GetRowActionsTemplate()) }} + {% if not oUIBlock.GetRowActionsConfirmDialogInitializedFlag() %} + {{ render_block(oUIBlock.GetRowActionsConfirmDialog()) }} + {% endif %} {% endif %} \ No newline at end of file diff --git a/templates/base/components/datatable/layout.ready.js.twig b/templates/base/components/datatable/layout.ready.js.twig index 64198fa85..887370c4d 100644 --- a/templates/base/components/datatable/layout.ready.js.twig +++ b/templates/base/components/datatable/layout.ready.js.twig @@ -423,4 +423,6 @@ if(window.ResizeObserver){ oTable{{ sListIDForVarSuffix }}Resize.observe($('#{{ oUIBlock.GetId() }}')[0]); } -{% include 'base/components/datatable/row-actions/handler.js.twig' %} \ No newline at end of file +{% if oUIBlock.HasRowActions() %} + {% include 'base/components/datatable/row-actions/handler.js.twig' %} +{% endif %} \ No newline at end of file diff --git a/templates/base/components/datatable/row-actions/handler.js.twig b/templates/base/components/datatable/row-actions/handler.js.twig index 1bf8a0453..f1cfa1bf6 100644 --- a/templates/base/components/datatable/row-actions/handler.js.twig +++ b/templates/base/components/datatable/row-actions/handler.js.twig @@ -1,15 +1,51 @@ {# @copyright Copyright (C) 2010-2022 Combodo SARL #} {# @license http://opensource.org/licenses/AGPL-3.0 #} -// for each row action -{% if oUIBlock.HasRowActions() %} - {% for aAction in oUIBlock.GetRowActions() %} - $('#{{ oUIBlock.GetId() }} tbody').on('click', 'button[data-action-id="{{ loop.index0 }}"]', function() { - let iActionId = $(this).data('action-id'); - let oDatatable = $('#{{ oUIBlock.GetId() }}').DataTable(); - let oTrElement = $(this).closest('tr'); - let aData = oDatatable.row(oTrElement).data(); - {{ aAction.js_row_action|raw }}; - }); - {% endfor %} -{% endif %} \ No newline at end of file +// for each row action... +{% for aAction in oUIBlock.GetRowActions() %} + + // register action buttons click + $('#{{ oUIBlock.GetId() }} tbody').on('click', 'button[data-action-id="{{ loop.index0 }}"]', function() { + + // variables accessible from action row js + let oDatatable = $('#{{ oUIBlock.GetId() }}').DataTable(); + let oTrElement = $(this).closest('tr'); + let iActionId = $(this).data('action-id'); + let aRowData = oDatatable.row(oTrElement).data(); + + {% if aAction.confirmation is defined %} + + // Handle action row with confirmation + let sTitle = '{{ 'UI:Datatables:RowActions:ConfirmationDialog'|dict_s }}'; + let sMessage = '{{ 'UI:Datatables:RowActions:ConfirmationMessage'|dict_s }}'; + {% if aAction.confirmation.message is defined %} + sMessage = '{{ aAction.confirmation.message|dict_s|raw }}'; + sMessage = sMessage.replaceAll('{item}', aRowData['{{ aAction.confirmation.message_row_data }}']); + {% endif %} + let sPrefKey = null; + {% if aAction.confirmation.remember_choice_pref_key is defined %} + sPrefKey = '{{ aAction.confirmation.remember_choice_pref_key }}'; + {% endif %} + HandleActionRowConfirmation (sTitle, sMessage, sPrefKey, ActionRowFunction{{ oUIBlock.GetId() }}{{ loop.index0 }}, { + action_id: iActionId, + datatable: oDatatable, + tr_element: oTrElement, + row_data: aRowData + }); + + {% else %} + + // Handle action row without confirmation + ActionRowFunction{{ oUIBlock.GetId() }}{{ loop.index0 }}(oDatatable, oTrElement, iActionId, aRowData); + + {% endif %} + + }); + + // action row function declaration + function ActionRowFunction{{ oUIBlock.GetId() }}{{ loop.index0 }}(oDatatable, oTrElement, iActionId, aRowData){ + {{ aAction.js_row_action|raw }}; + return true; + } + +{% endfor %} diff --git a/templates/base/components/datatable/static/formtable/layout.html.twig b/templates/base/components/datatable/static/formtable/layout.html.twig index 3da718895..11a807f62 100644 --- a/templates/base/components/datatable/static/formtable/layout.html.twig +++ b/templates/base/components/datatable/static/formtable/layout.html.twig @@ -21,4 +21,7 @@ {% if oUIBlock.HasRowActions() %} {{ render_block(oUIBlock.GetRowActionsTemplate()) }} + {% if not oUIBlock.GetRowActionsConfirmDialogInitializedFlag() %} + {{ render_block(oUIBlock.GetRowActionsConfirmDialog()) }} + {% endif %} {% endif %} \ No newline at end of file diff --git a/templates/base/components/datatable/static/layout.html.twig b/templates/base/components/datatable/static/layout.html.twig index a7b9748b8..5e8d9030f 100644 --- a/templates/base/components/datatable/static/layout.html.twig +++ b/templates/base/components/datatable/static/layout.html.twig @@ -48,4 +48,7 @@ {% if oUIBlock.HasRowActions() %} {{ render_block(oUIBlock.GetRowActionsTemplate()) }} + {% if not oUIBlock.GetRowActionsConfirmDialogInitializedFlag() %} + {{ render_block(oUIBlock.GetRowActionsConfirmDialog()) }} + {% endif %} {% endif %} \ No newline at end of file diff --git a/templates/base/components/dialog/layout.html.twig b/templates/base/components/dialog/layout.html.twig new file mode 100644 index 000000000..a86a86f21 --- /dev/null +++ b/templates/base/components/dialog/layout.html.twig @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/templates/base/components/dialog/layout.ready.js.twig b/templates/base/components/dialog/layout.ready.js.twig new file mode 100644 index 000000000..d37e9c573 --- /dev/null +++ b/templates/base/components/dialog/layout.ready.js.twig @@ -0,0 +1,4 @@ +$('#{{ oUIBlock.GetId() }}').alert({ + bOpenedByDefault: {{ oUIBlock.IsOpenedByDefault()|var_export }} + {% if oUIBlock.IsSaveCollapsibleStateEnabled() %}, collapsibleStateStorageKey: '{{ oUIBlock.GetSessionCollapsibleStateStorageKey() }}'{% endif %} +}); \ No newline at end of file