diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php
index 30c09fdc3..b2b63dc9c 100644
--- a/application/cmdbabstract.class.inc.php
+++ b/application/cmdbabstract.class.inc.php
@@ -2974,7 +2974,12 @@ EOF
$oObjectDetails = ObjectFactory::MakeDetails($this, $this->GetDisplayMode());
$oToolbarButtons->AddCSSClass('ibo-toolbar-top');
$oObjectDetails->AddToolbarBlock($oToolbarButtons);
+ // Allow form title customization
+ if (array_key_exists('form_title', $aExtraParams)) {
+ $oObjectDetails->SetTitle($aExtraParams['form_title']);
+ }
}
+
$oForm->AddSubBlock($oObjectDetails);
if (isset($aExtraParams['nbBulkObj'])) {
// if bulk modify buttons must be after object display
diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php
index d55f7da06..2298eab30 100644
--- a/application/displayblock.class.inc.php
+++ b/application/displayblock.class.inc.php
@@ -1844,7 +1844,12 @@ class MenuBlock extends DisplayBlock
$iSetCount = $oSet->Count();
/** @var string $sRefreshAction JS snippet to run when clicking on the refresh button of the menu */
$sRefreshAction = $aExtraParams['refresh_action'] ?? '';
- $bIsCreationInModalAllowed = isset($aExtraParams['creation_in_modal_is_allowed']) && $aExtraParams['creation_in_modal_is_allowed'] === true;
+ $bIsCreationInModal = isset($aExtraParams['creation_in_modal']) && $aExtraParams['creation_in_modal'] === true;
+ $bIsCreationDisallowed = isset($aExtraParams['creation_disallowed']) && $aExtraParams['creation_disallowed'] === true;
+
+ // Check rights
+ $oReflectionClass = new ReflectionClass($sClass);
+ $bIsCreationAllowed = (!$bIsCreationDisallowed && UserRights::IsActionAllowed($sClass, UR_ACTION_CREATE) === UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
/** @var array $aRegularActions Any action other than a transition */
$aRegularActions = [];
@@ -1860,7 +1865,7 @@ class MenuBlock extends DisplayBlock
$sContext = '&'.$sContext;
}
- $oReflectionClass = new ReflectionClass($sClass);
+
$sFilter = $this->GetFilter()->serialize();
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($sClass);
$sRootUrl = utils::GetAbsoluteUrlAppRoot();
@@ -1880,10 +1885,6 @@ class MenuBlock extends DisplayBlock
}
}
- // Check rights
- $bIsCreationAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_CREATE) === UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
- $bIsModifyAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) === UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
-
// Check concurrent lock (can only be lock if we are handling a single object
$bLocked = false;
if ($iSetCount === 1) {
@@ -1905,7 +1906,7 @@ class MenuBlock extends DisplayBlock
//--------------------------------------------
// Create in new tab
- if ($bIsCreationAllowed && !$bIsCreationInModalAllowed) {
+ if ($bIsCreationAllowed && !$bIsCreationInModal) {
$this->AddNewObjectMenuAction($aRegularActions, $sClass, $sDefaultValuesAsUrlParams);
}
@@ -2308,10 +2309,13 @@ class MenuBlock extends DisplayBlock
}
// - Creation in modal
- if ($bIsCreationInModalAllowed === true) {
+ if ($bIsCreationAllowed && $bIsCreationInModal) {
$oAddLinkActionButton = ButtonUIBlockFactory::MakeIconAction(
'fas fa-plus',
- Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($sClass)),
+ // Allow button tooltip customization
+ array_key_exists('creation_in_modal_tooltip', $aExtraParams) ?
+ $aExtraParams['creation_in_modal_tooltip'] :
+ Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($sClass)),
'UI:Links:New',
'',
false
diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php
index 50e6e791e..962ba98c5 100644
--- a/core/attributedef.class.inc.php
+++ b/core/attributedef.class.inc.php
@@ -2381,69 +2381,97 @@ class AttributeLinkedSet extends AttributeDefinition
{
if ($oFormField === null)
{
- $sFormFieldClass = static::GetFormFieldClass();
- $oFormField = new $sFormFieldClass($this->GetCode());
- }
+ $sFormFieldClass = static::GetFormFieldClass();
+ $oFormField = new $sFormFieldClass($this->GetCode());
+ }
- // Setting target class
- if (!$this->IsIndirect())
- {
- $sTargetClass = $this->GetLinkedClass();
- }
- else
- {
- /** @var \AttributeExternalKey $oRemoteAttDef */
- /** @var \AttributeLinkedSetIndirect $this */
- $oRemoteAttDef = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToRemote());
- $sTargetClass = $oRemoteAttDef->GetTargetClass();
+ // Setting target class
+ if (!$this->IsIndirect()) {
+ $sTargetClass = $this->GetLinkedClass();
+ } else {
+ /** @var \AttributeExternalKey $oRemoteAttDef */
+ /** @var \AttributeLinkedSetIndirect $this */
+ $oRemoteAttDef = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToRemote());
+ $sTargetClass = $oRemoteAttDef->GetTargetClass();
- /** @var \AttributeLinkedSetIndirect $this */
- $oFormField->SetExtKeyToRemote($this->GetExtKeyToRemote());
- }
- $oFormField->SetTargetClass($sTargetClass);
- $oFormField->SetIndirect($this->IsIndirect());
- // Setting attcodes to display
- $aAttCodesToDisplay = MetaModel::FlattenZList(MetaModel::GetZListItems($sTargetClass, 'list'));
- // - Adding friendlyname attribute to the list is not already in it
- $sTitleAttCode = MetaModel::GetFriendlyNameAttributeCode($sTargetClass);
- if (($sTitleAttCode !== null) && !in_array($sTitleAttCode, $aAttCodesToDisplay))
- {
- $aAttCodesToDisplay = array_merge(array($sTitleAttCode), $aAttCodesToDisplay);
- }
- // - Adding attribute labels
- $aAttributesToDisplay = array();
- foreach($aAttCodesToDisplay as $sAttCodeToDisplay)
- {
- $oAttDefToDisplay = MetaModel::GetAttributeDef($sTargetClass, $sAttCodeToDisplay);
- $aAttributesToDisplay[$sAttCodeToDisplay] = $oAttDefToDisplay->GetLabel();
- }
- $oFormField->SetAttributesToDisplay($aAttributesToDisplay);
+ /** @var \AttributeLinkedSetIndirect $this */
+ $oFormField->SetExtKeyToRemote($this->GetExtKeyToRemote());
+ }
+ $oFormField->SetTargetClass($sTargetClass);
+ $oFormField->SetIndirect($this->IsIndirect());
+ // Setting attcodes to display
+ $aAttCodesToDisplay = MetaModel::FlattenZList(MetaModel::GetZListItems($sTargetClass, 'list'));
+ // - Adding friendlyname attribute to the list is not already in it
+ $sTitleAttCode = MetaModel::GetFriendlyNameAttributeCode($sTargetClass);
+ if (($sTitleAttCode !== null) && !in_array($sTitleAttCode, $aAttCodesToDisplay)) {
+ $aAttCodesToDisplay = array_merge(array($sTitleAttCode), $aAttCodesToDisplay);
+ }
+ // - Adding attribute labels
+ $aAttributesToDisplay = array();
+ foreach ($aAttCodesToDisplay as $sAttCodeToDisplay) {
+ $oAttDefToDisplay = MetaModel::GetAttributeDef($sTargetClass, $sAttCodeToDisplay);
+ $aAttributesToDisplay[$sAttCodeToDisplay] = $oAttDefToDisplay->GetLabel();
+ }
+ $oFormField->SetAttributesToDisplay($aAttributesToDisplay);
- parent::MakeFormField($oObject, $oFormField);
+ parent::MakeFormField($oObject, $oFormField);
- return $oFormField;
- }
+ return $oFormField;
+ }
- public function IsPartOfFingerprint()
- {
- return false;
- }
+ public function IsPartOfFingerprint()
+ {
+ return false;
+ }
- /**
- * @inheritDoc
- * @param \ormLinkSet $proposedValue
- */
- public function HasAValue($proposedValue): bool
- {
- // Protection against wrong value type
- if (false === ($proposedValue instanceof ormLinkSet))
- {
- return parent::HasAValue($proposedValue);
- }
+ /**
+ * @inheritDoc
+ * @param \ormLinkSet $proposedValue
+ */
+ public function HasAValue($proposedValue): bool
+ {
+ // Protection against wrong value type
+ if (false === ($proposedValue instanceof ormLinkSet)) {
+ return parent::HasAValue($proposedValue);
+ }
- // We test if there is at least 1 item in the linkset (new or existing), not if an item is being added to it.
- return $proposedValue->Count() > 0;
- }
+ // We test if there is at least 1 item in the linkset (new or existing), not if an item is being added to it.
+ return $proposedValue->Count() > 0;
+ }
+
+ /**
+ * SearchSpecificLabel.
+ *
+ * @param string $sDictEntrySuffix
+ * @param string $sDefault
+ * @param bool $bUserLanguageOnly
+ * @param ...$aArgs
+ * @return string
+ * @since 3.1.0
+ */
+ public function SearchSpecificLabel(string $sDictEntrySuffix, string $sDefault, bool $bUserLanguageOnly, ...$aArgs): string
+ {
+ try {
+ $sNextClass = $this->m_sHostClass;
+
+ do {
+ $sKey = "class:{$sNextClass}/Attribute:{$this->m_sCode}/{$sDictEntrySuffix}";
+ if (Dict::S($sKey, null, $bUserLanguageOnly) !== $sKey) {
+ return Dict::Format($sKey, ...$aArgs);
+ }
+ $sNextClass = MetaModel::GetParentClass($sNextClass);
+ } while ($sNextClass !== null);
+
+ if (Dict::S($sDictEntrySuffix, null, $bUserLanguageOnly) !== $sKey) {
+ return Dict::Format($sDictEntrySuffix, ...$aArgs);
+ } else {
+ return $sDefault;
+ }
+ } catch (Exception $e) {
+ ExceptionLog::LogException($e);
+ return $sDefault;
+ }
+ }
}
/**
diff --git a/dictionaries/ui/application/links/en.dictionary.itop.links.php b/dictionaries/ui/application/links/en.dictionary.itop.links.php
index 0c7127c6c..6522f884d 100644
--- a/dictionaries/ui/application/links/en.dictionary.itop.links.php
+++ b/dictionaries/ui/application/links/en.dictionary.itop.links.php
@@ -19,19 +19,46 @@
Dict::Add('EN US', 'English', 'English', array(
- // Action row
- 'UI:Links:ActionRow:Detach' => 'Remove',
- 'UI:Links:ActionRow:Detach+' => 'Remove this object',
- 'UI:Links:ActionRow:Detach:Confirmation' => 'Do you really want to remove {item} from current object ?',
- 'UI:Links:ActionRow:Delete' => 'Delete',
- 'UI:Links:ActionRow:Delete+' => 'Delete this object',
- 'UI:Links:ActionRow:Delete:Confirmation' => 'Do you really want to delete {item} from current object ?',
- 'UI:Links:ActionRow:Modify' => 'Modify',
- 'UI:Links:ActionRow:Modify+' => 'Modify this object',
- 'UI:Links:ActionRow:Modify:Modal:Title' => 'Modify an object',
+ // Placeholders
+ // $%1s : host object class name
+ // $%2s : host object firendlyname
+ // $%3s : current tab name
+ // $%4s : remote object class name
+ // $%5s : remote object friendlyname
- // Action
- 'UI:Links:New:Modal:Title' => 'Create an object',
+ 'UI:Links:Object:New:Modal:Title' => 'Create an object',
+
+ // Create
+ 'UI:Links:Create:Button' => 'Create',
+ 'UI:Links:Create:Button+' => 'Create a %4$s',
+ 'UI:Links:Create:Modal:Title' => 'Create a %4$s in %2$s',
+
+ // Add
+ 'UI:Links:Add:Button' => 'Add',
+ 'UI:Links:Add:Button+' => 'Add a %4$s',
+ 'UI:Links:Add:Modal:Title' => 'Add a %4$s to %2$s',
+
+ // Modify link
+ 'UI:Links:ModifyLink:Button' => 'Modify',
+ 'UI:Links:ModifyLink:Button+' => 'Modify this link',
+ 'UI:Links:ModifyLink:Modal:Title' => 'Modify the link between %2$s and %5$s',
+
+ // Modify object
+ 'UI:Links:ModifyObject:Button' => 'Modify',
+ 'UI:Links:ModifyObject:Button+' => 'Modify this object',
+ 'UI:Links:ModifyObject:Modal:Title' => '%5$s',
+
+ // Remove
+ 'UI:Links:Remove:Button' => 'Remove',
+ 'UI:Links:Remove:Button+' => 'Remove this %4$s',
+ 'UI:Links:Remove:Modal:Title' => 'Remove a %4$s',
+ 'UI:Links:Remove:Modal:Message' => 'Do you really want to remove %5$s from %2$s?',
+
+ // Delete
+ 'UI:Links:Delete:Button' => 'Delete',
+ 'UI:Links:Delete:Button+' => 'Delete this %4$s',
+ 'UI:Links:Delete:Modal:Title' => 'Delete a %4$s',
+ 'UI:Links:Delete:Modal:Message' => 'Do you really want to delete %5$s?',
// Bulk
'UI:Links:Bulk:LinkWillBeCreatedForAllObjects' => 'Add to all objects',
diff --git a/dictionaries/ui/application/links/fr.dictionary.itop.links.php b/dictionaries/ui/application/links/fr.dictionary.itop.links.php
index 64d3d66a3..5a6dc96e6 100644
--- a/dictionaries/ui/application/links/fr.dictionary.itop.links.php
+++ b/dictionaries/ui/application/links/fr.dictionary.itop.links.php
@@ -19,19 +19,47 @@
Dict::Add('FR FR', 'French', 'Français', array(
- // Action row
- 'UI:Links:ActionRow:Detach' => 'Enlever',
- 'UI:Links:ActionRow:Detach+' => 'Enlever cet objet',
- 'UI:Links:ActionRow:Detach:Confirmation' => 'Voulez-vous Enlever {item} de l\'objet courant ?',
- 'UI:Links:ActionRow:Delete' => 'Supprimer',
- 'UI:Links:ActionRow:Delete+' => 'Supprimer cet objet',
- 'UI:Links:ActionRow:Delete:Confirmation' => 'Voulez-vous supprimer {item} de l\'objet courant ?',
- 'UI:Links:ActionRow:Modify' => 'Modifier',
- 'UI:Links:ActionRow:Modify+' => 'Modifier cet objet',
- 'UI:Links:ActionRow:Modify:Modal:Title' => 'Modifier un objet',
- // Action
- 'UI:Links:New:Modal:Title' => 'Créer un objet',
+ // Placeholders
+ // $%1s : host object class name
+ // $%2s : host object firendlyname
+ // $%3s : current tab name
+ // $%4s : remote object class name
+ // $%5s : remote object friendlyname
+
+ 'UI:Links:Object:New:Modal:Title' => 'Créer un objet',
+
+ // Create
+ 'UI:Links:Create:Button' => 'Créer',
+ 'UI:Links:Create:Button+' => 'Créer un(e) %4$s',
+ 'UI:Links:Create:Modal:Title' => 'Ajouter un(e) %4$s à %2$s',
+
+ // Add
+ 'UI:Links:Add:Button' => 'Ajouter',
+ 'UI:Links:Add:Button+' => 'Ajouter un(e) %4$s',
+ 'UI:Links:Add:Modal:Title' => 'Ajouter un(e) %4$s à %2$s',
+
+ // Modify link
+ 'UI:Links:ModifyLink:Button' => 'Modifier',
+ 'UI:Links:ModifyLink:Button+' => 'Modifier cette relation',
+ 'UI:Links:ModifyLink:Modal:Title' => 'Modifier la relation entre %2$s et %5$s',
+
+ // Modify object
+ 'UI:Links:ModifyObject:Button' => 'Modifier',
+ 'UI:Links:ModifyObject:Button+' => 'Modifier cet objet',
+ 'UI:Links:ModifyObject:Modal:Title' => '%5$s',
+
+ // Remove
+ 'UI:Links:Remove:Button' => 'Retirer',
+ 'UI:Links:Remove:Button+' => 'Retirer cet(te) %4$s',
+ 'UI:Links:Remove:Modal:Title' => 'Retirer un(e) %4$s de %1$s',
+ 'UI:Links:Remove:Modal:Message' => 'Voulez-vous vraiment retirer %5$s de %2$s ?',
+
+ // Delete
+ 'UI:Links:Delete:Button' => 'Supprimer',
+ 'UI:Links:Delete:Button+' => 'Supprimer cet(te) %4$s',
+ 'UI:Links:Delete:Modal:Title' => 'Supprimer un(e) %4$s',
+ 'UI:Links:Delete:Modal:Message' => 'Voulez-vous vraiment supprimer %5$s ?',
// Bulk
'UI:Links:Bulk:LinkWillBeCreatedForAllObjects' => 'Ajouter à tous les objets',
diff --git a/js/links/links_view_table_widget.js b/js/links/links_view_table_widget.js
index feb21d8fc..362be11af 100644
--- a/js/links/links_view_table_widget.js
+++ b/js/links/links_view_table_widget.js
@@ -75,6 +75,10 @@ $(function()
// retrieve table
const $Table = $('table', this.element);
+ // retrieve new button
+ const $NewButton = $('[name="UI:Links:New"]', this.element);
+ const sButtonTooltipContent = $NewButton.attr('data-tooltip-content');
+
// retrieve context parameters
const sClass = $Table.closest('[data-role="ibo-block-links-table"]').attr('data-link-class');
const sAttCode = $Table.closest('[data-role="ibo-block-links-table"]').attr('data-link-attcode');
@@ -82,7 +86,7 @@ $(function()
const sHostObjectId = $Table.closest('[data-role="ibo-object-details"]').attr('data-object-id');
// link object creation
- iTopLinkSetWorker.CreateLinkedObject(sClass, sAttCode, sHostObjectClass, sHostObjectId, function(){
+ iTopLinkSetWorker.CreateLinkedObject(sButtonTooltipContent, sClass, sAttCode, sHostObjectClass, sHostObjectId, function(){
$(this).find("form").remove();
$(this).dialog('destroy');
},function (event, data) {
@@ -96,20 +100,29 @@ $(function()
* ModifyLinkedObject.
*
* @param {string} sLinkedObjectKey
+ * @param {Element} $TRElement
+ * @param {string} sRemoteFriendlyname
*/
- ModifyLinkedObject: function (sLinkedObjectKey) {
+ ModifyLinkedObject: function (sLinkedObjectKey, $TRElement, sRemoteFriendlyname) {
const me = this;
-
+
+ // retrieve modify button and extract modal title
+ const $ModifyButton = $('[name="ModifyButton"]', $TRElement);
+ const sButtonTooltipContent = $ModifyButton.attr('data-tooltip-content');
+ let sButtonTitleContent = $ModifyButton.attr('data-modal-title');
+ sButtonTitleContent = sButtonTitleContent.replaceAll('{item}', sRemoteFriendlyname);
+
// Specify that external key to host object will be readonly
let aReadOnlyParams = {
'readonly': {
}
}
aReadOnlyParams['readonly'][this.options.external_key_to_me] = 1;
-
+ aReadOnlyParams['form_title'] = sButtonTitleContent;
+
// link object modification
- iTopObjectWorker.ModifyObject(this.options.link_class, sLinkedObjectKey, function () {
+ iTopObjectWorker.ModifyObject(sButtonTooltipContent, this.options.link_class, sLinkedObjectKey, function () {
$(this).find("form").remove();
$(this).dialog('destroy');
}, function(event, data){
diff --git a/js/links/linkset-worker.js b/js/links/linkset-worker.js
index 940fad976..ef4ddc9c2 100644
--- a/js/links/linkset-worker.js
+++ b/js/links/linkset-worker.js
@@ -46,6 +46,7 @@ const iTopLinkSetWorker = new function(){
/**
* CallAjaxCreateLinkedObject.
*
+ * @param {string} sModalTitle
* @param {string} sClass
* @param {string} sAttCode
* @param {string} sHostObjectClass
@@ -53,10 +54,10 @@ const iTopLinkSetWorker = new function(){
* @param oOnModalCloseCallback
* @param oOnFormSubmittedCallback
*/
- const CallAjaxCreateLinkedObject = function(sClass, sAttCode, sHostObjectClass, sHostObjectId, oOnModalCloseCallback = null, oOnFormSubmittedCallback = null){
+ const CallAjaxCreateLinkedObject = function(sModalTitle, sClass, sAttCode, sHostObjectClass, sHostObjectId, oOnModalCloseCallback = null, oOnFormSubmittedCallback = null){
let oOptions = {
- title: Dict.S('UI:Links:New:Modal:Title'),
+ title: sModalTitle,
content: {
endpoint: `${ROUTER_BASE_URL}?route=${ROUTE_LINK_SET_CREATE_OBJECT}`,
data: {
diff --git a/js/links/linkset.js b/js/links/linkset.js
index 2e8b11a76..85cdc93a6 100644
--- a/js/links/linkset.js
+++ b/js/links/linkset.js
@@ -15,7 +15,7 @@ const iTopLinkSet = new function () {
const CallCreateLinkedObject = function(sLinkedClass, oWidget)
{
// Create link object
- iTopObjectWorker.CreateObject(sLinkedClass, function(){
+ iTopObjectWorker.CreateObject(Dict.S('UI:Links:Object:New:Modal:Title'), sLinkedClass, function(){
$(this).find("form").remove();
$(this).dialog('destroy');
},
diff --git a/js/object/object-worker.js b/js/object/object-worker.js
index 86f6fe2af..0c0e522ed 100644
--- a/js/object/object-worker.js
+++ b/js/object/object-worker.js
@@ -5,17 +5,18 @@ const iTopObjectWorker = new function(){
const ROUTE_CREATE_OBJECT = 'object.new';
const ROUTE_MODIFY_OBJECT = 'object.modify';
const ROUTE_GET_OBJECT = 'object.get';
-
+
/**
* CallAjaxCreateObject.
*
+ * @param {string} sTitle
* @param {string} sClass
* @param oOnModalCloseCallback
* @param oOnFormSubmittedCallback
* @param {Object} aAdditionalData
* @constructor
*/
- const CallAjaxCreateObject = function(sClass, oOnModalCloseCallback = null, oOnFormSubmittedCallback = null, aAdditionalData = []){
+ const CallAjaxCreateObject = function(sTitle, sClass, oOnModalCloseCallback = null, oOnFormSubmittedCallback = null, aAdditionalData = []){
let aData = $.extend(
{
class: sClass,
@@ -24,7 +25,7 @@ const iTopObjectWorker = new function(){
);
let oOptions = {
- title: Dict.S('UI:Object:Modal:Title'),
+ title: sTitle,
content: {
endpoint: `${ROUTER_BASE_URL}?route=${ROUTE_CREATE_OBJECT}`,
data: aData
@@ -43,6 +44,7 @@ const iTopObjectWorker = new function(){
/**
* CallAjaxModifyObject.
*
+ * @param {string} sTitle
* @param {string} sObjectClass
* @param {string} sObjectKey
* @param oOnModalCloseCallback
@@ -50,7 +52,7 @@ const iTopObjectWorker = new function(){
* @param {Object} aAdditionalData
* @constructor
*/
- const CallAjaxModifyObject = function(sObjectClass, sObjectKey, oOnModalCloseCallback, oOnFormSubmittedCallback, aAdditionalData = []){
+ const CallAjaxModifyObject = function(sTitle, sObjectClass, sObjectKey, oOnModalCloseCallback, oOnFormSubmittedCallback, aAdditionalData = []){
let aData = $.extend(
{
class: sObjectClass,
@@ -58,9 +60,9 @@ const iTopObjectWorker = new function(){
},
aAdditionalData
);
-
+
let oOptions = {
- title: Dict.S('UI:Links:ActionRow:Modify:Modal:Title'),
+ title: sTitle,
content: {
endpoint: `${ROUTER_BASE_URL}?route=${ROUTE_MODIFY_OBJECT}`,
data: aData,
@@ -93,7 +95,7 @@ const iTopObjectWorker = new function(){
},
aAdditionalData
)
-
+
$.post(`${ROUTER_BASE_URL}?route=${ROUTE_GET_OBJECT}`, aData, oOnResponseCallback);
};
diff --git a/js/pages/backoffice/toolbox.js b/js/pages/backoffice/toolbox.js
index 70b086e4a..ae29f3ed0 100644
--- a/js/pages/backoffice/toolbox.js
+++ b/js/pages/backoffice/toolbox.js
@@ -325,10 +325,12 @@ CombodoModal._ConvertButtonDefinition = function (aButtonsDefinitions) {
if(aButtonsDefinitions === null) {
return aConverted
}
- aButtonsDefinitions.forEach(element => {
+ Object.keys(aButtonsDefinitions).forEach(key => {
+ const element = aButtonsDefinitions[key];
+ console.log(element);
const aButton = {
text: element.text,
- class: element.class,
+ class: typeof(element.classes) !== 'undefined' ? element.classes.join(' ') : '',
click: element.callback_on_click
}
aConverted.push(aButton);
@@ -366,43 +368,40 @@ CombodoModal.OpenConfirmationModal = function(oOptions, aData) {
}
}
// Merge external options with confirmation modal default options
- oOptions = $.extend({
+ oOptions = $.extend(true, {
title: Dict.S('UI:Modal:DefaultConfirmationTitle'),
content: '',
- confirm_button_label: null,
do_not_show_again_pref_key: null,
- callback_on_confirm: null,
- callback_on_cancel: null,
extra_options: {
callback_on_modal_close: function () {
$(this).dialog( "destroy" ); // destroy dialog object
}
},
- buttons: [
- {
+ buttons: {
+ cancel: {
text: Dict.S('UI:Button:Cancel'),
- class: 'ibo-is-alternative',
+ classes: ['ibo-is-alternative'],
callback_on_click: function () {
// call confirm handler and close dialog
let bCanClose = true;
- if(oOptions.callback_on_cancel != null){
+ if (oOptions.callback_on_cancel != null) {
bCanClose = oOptions.callback_on_cancel(...aData) !== false;
}
- if(bCanClose){
+ if (bCanClose) {
$(this).dialog('close'); // close dialog
}
}
},
- {
- text: oOptions.confirm_button_label ?? Dict.S('UI:Button:Confirm'),
- class: 'ibo-is-primary',
+ confirm: {
+ text: Dict.S('UI:Button:Confirm'),
+ classes: ['ibo-is-primary'],
callback_on_click: function () {
// Call confirm handler and close dialog
let bCanClose = true;
- if(oOptions.callback_on_confirm != null){
+ if (oOptions.callback_on_confirm != null) {
bCanClose = oOptions.callback_on_confirm(...aData) !== false;
}
- if(bCanClose){
+ if (bCanClose) {
$(this).dialog('close'); // close dialog
// Handle "do not show again" user preference
let bDoNotShowAgain = oOptions.do_not_show_again_pref_key !== null ?
@@ -414,7 +413,7 @@ CombodoModal.OpenConfirmationModal = function(oOptions, aData) {
}
}
}
- ],
+ },
callback_on_content_loaded: function(oModalContentElement){
// Add option do not show again from template
if(oOptions.do_not_show_again_pref_key !== null) {
@@ -444,15 +443,15 @@ CombodoModal.OpenInformativeModal = function(sMessage, sSeverity, oOptions) {
$(this).dialog( "destroy" );
}
},
- buttons: [
- {
+ buttons: {
+ ok: {
text: Dict.S('UI:Button:Ok'),
- class: 'ibo-is-regular ibo-is-neutral',
+ // classes: ['ibo-is-regular', 'ibo-is-neutral'],
callback_on_click: function () {
- $(this).dialog('close');
+ $(this).dialog('close');
}
},
- ],
+ },
}, oOptions);
// Open modal
diff --git a/lib/autoload.php b/lib/autoload.php
index db10dc867..460e67535 100644
--- a/lib/autoload.php
+++ b/lib/autoload.php
@@ -2,24 +2,6 @@
// autoload.php @generated by Composer
-if (PHP_VERSION_ID < 50600) {
- if (!headers_sent()) {
- header('HTTP/1.1 500 Internal Server Error');
- }
- $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
- if (!ini_get('display_errors')) {
- if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
- fwrite(STDERR, $err);
- } elseif (!headers_sent()) {
- echo $err;
- }
- }
- trigger_error(
- $err,
- E_USER_ERROR
- );
-}
-
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit7f81b4a2a468a061c306af5e447a9a9f::getLoader();
diff --git a/lib/composer/ClassLoader.php b/lib/composer/ClassLoader.php
index a72151c77..0cd6055d1 100644
--- a/lib/composer/ClassLoader.php
+++ b/lib/composer/ClassLoader.php
@@ -42,9 +42,6 @@ namespace Composer\Autoload;
*/
class ClassLoader
{
- /** @var \Closure(string):void */
- private static $includeFile;
-
/** @var ?string */
private $vendorDir;
@@ -109,7 +106,6 @@ class ClassLoader
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
- self::initializeIncludeClosure();
}
/**
@@ -153,7 +149,7 @@ class ClassLoader
/**
* @return string[] Array of classname => path
- * @psalm-return array
+ * @psalm-var array
*/
public function getClassMap()
{
@@ -429,8 +425,7 @@ class ClassLoader
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
- $includeFile = self::$includeFile;
- $includeFile($file);
+ includeFile($file);
return true;
}
@@ -560,26 +555,18 @@ class ClassLoader
return false;
}
-
- /**
- * @return void
- */
- private static function initializeIncludeClosure()
- {
- if (self::$includeFile !== null) {
- return;
- }
-
- /**
- * Scope isolated include.
- *
- * Prevents access to $this/self from included files.
- *
- * @param string $file
- * @return void
- */
- self::$includeFile = \Closure::bind(static function($file) {
- include $file;
- }, null, null);
- }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ *
+ * @param string $file
+ * @return void
+ * @private
+ */
+function includeFile($file)
+{
+ include $file;
}
diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php
index a7f82e11e..b85485524 100644
--- a/lib/composer/autoload_classmap.php
+++ b/lib/composer/autoload_classmap.php
@@ -2,7 +2,7 @@
// autoload_classmap.php @generated by Composer
-$vendorDir = dirname(__DIR__);
+$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
@@ -449,6 +449,7 @@ return array(
'Combodo\\iTop\\Service\\Events\\EventServiceLog' => $baseDir . '/sources/Service/Events/EventServiceLog.php',
'Combodo\\iTop\\Service\\Events\\iEventServiceSetup' => $baseDir . '/sources/Service/Events/iEventServiceSetup.php',
'Combodo\\iTop\\Service\\Links\\LinkSetDataTransformer' => $baseDir . '/sources/Service/Links/LinkSetDataTransformer.php',
+ 'Combodo\\iTop\\Service\\Links\\LinkSetHelper' => $baseDir . '/sources/Service/Links/LinkSetHelper.php',
'Combodo\\iTop\\Service\\Links\\LinkSetModel' => $baseDir . '/sources/Service/Links/LinkSetModel.php',
'Combodo\\iTop\\Service\\Links\\LinkSetRepository' => $baseDir . '/sources/Service/Links/LinkSetRepository.php',
'Combodo\\iTop\\Service\\Links\\LinksBulkDataPostProcessor' => $baseDir . '/sources/Service/Links/LinksBulkDataPostProcessor.php',
diff --git a/lib/composer/autoload_files.php b/lib/composer/autoload_files.php
index cbe23f179..9ca87aae9 100644
--- a/lib/composer/autoload_files.php
+++ b/lib/composer/autoload_files.php
@@ -2,24 +2,24 @@
// autoload_files.php @generated by Composer
-$vendorDir = dirname(__DIR__);
+$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
- 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
+ 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
- '23c18046f52bef3eea034657bafda50f' => $vendorDir . '/symfony/polyfill-php81/bootstrap.php',
'0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php',
+ '23c18046f52bef3eea034657bafda50f' => $vendorDir . '/symfony/polyfill-php81/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
- '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
- 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
- '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
'c9d07b32a2e02bc0fc582d4f0c1b56cc' => $vendorDir . '/laminas/laminas-servicemanager/src/autoload.php',
+ '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
'8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
+ 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
+ 'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
+ '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',
- 'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
);
diff --git a/lib/composer/autoload_namespaces.php b/lib/composer/autoload_namespaces.php
index 6629b7e09..1db5bf646 100644
--- a/lib/composer/autoload_namespaces.php
+++ b/lib/composer/autoload_namespaces.php
@@ -2,7 +2,7 @@
// autoload_namespaces.php @generated by Composer
-$vendorDir = dirname(__DIR__);
+$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
diff --git a/lib/composer/autoload_psr4.php b/lib/composer/autoload_psr4.php
index 3b30be01d..eb2c95ace 100644
--- a/lib/composer/autoload_psr4.php
+++ b/lib/composer/autoload_psr4.php
@@ -2,7 +2,7 @@
// autoload_psr4.php @generated by Composer
-$vendorDir = dirname(__DIR__);
+$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
diff --git a/lib/composer/autoload_real.php b/lib/composer/autoload_real.php
index 671821bc0..cc554d8d1 100644
--- a/lib/composer/autoload_real.php
+++ b/lib/composer/autoload_real.php
@@ -25,31 +25,46 @@ class ComposerAutoloaderInit7f81b4a2a468a061c306af5e447a9a9f
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit7f81b4a2a468a061c306af5e447a9a9f', 'loadClassLoader'), true, true);
- self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInit7f81b4a2a468a061c306af5e447a9a9f', 'loadClassLoader'));
$includePaths = require __DIR__ . '/include_paths.php';
$includePaths[] = get_include_path();
set_include_path(implode(PATH_SEPARATOR, $includePaths));
- require __DIR__ . '/autoload_static.php';
- call_user_func(\Composer\Autoload\ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f::getInitializer($loader));
+ $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+ if ($useStaticLoader) {
+ require __DIR__ . '/autoload_static.php';
+
+ call_user_func(\Composer\Autoload\ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f::getInitializer($loader));
+ } else {
+ $classMap = require __DIR__ . '/autoload_classmap.php';
+ if ($classMap) {
+ $loader->addClassMap($classMap);
+ }
+ }
$loader->setClassMapAuthoritative(true);
$loader->register(true);
- $filesToLoad = \Composer\Autoload\ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f::$files;
- $requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
- if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
- $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
-
- require $file;
- }
- }, null, null);
- foreach ($filesToLoad as $fileIdentifier => $file) {
- $requireFile($fileIdentifier, $file);
+ if ($useStaticLoader) {
+ $includeFiles = Composer\Autoload\ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f::$files;
+ } else {
+ $includeFiles = require __DIR__ . '/autoload_files.php';
+ }
+ foreach ($includeFiles as $fileIdentifier => $file) {
+ composerRequire7f81b4a2a468a061c306af5e447a9a9f($fileIdentifier, $file);
}
return $loader;
}
}
+
+function composerRequire7f81b4a2a468a061c306af5e447a9a9f($fileIdentifier, $file)
+{
+ if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+ require $file;
+
+ $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+ }
+}
diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php
index a578890db..c10b2e716 100644
--- a/lib/composer/autoload_static.php
+++ b/lib/composer/autoload_static.php
@@ -7,22 +7,22 @@ namespace Composer\Autoload;
class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
{
public static $files = array (
- 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
+ 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
- '23c18046f52bef3eea034657bafda50f' => __DIR__ . '/..' . '/symfony/polyfill-php81/bootstrap.php',
'0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php',
+ '23c18046f52bef3eea034657bafda50f' => __DIR__ . '/..' . '/symfony/polyfill-php81/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
- '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
- 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
- '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
'c9d07b32a2e02bc0fc582d4f0c1b56cc' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/autoload.php',
+ '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
'8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php',
+ 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
+ 'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
+ '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
'25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php',
- 'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
);
public static $prefixLengthsPsr4 = array (
@@ -814,6 +814,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Service\\Events\\EventServiceLog' => __DIR__ . '/../..' . '/sources/Service/Events/EventServiceLog.php',
'Combodo\\iTop\\Service\\Events\\iEventServiceSetup' => __DIR__ . '/../..' . '/sources/Service/Events/iEventServiceSetup.php',
'Combodo\\iTop\\Service\\Links\\LinkSetDataTransformer' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetDataTransformer.php',
+ 'Combodo\\iTop\\Service\\Links\\LinkSetHelper' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetHelper.php',
'Combodo\\iTop\\Service\\Links\\LinkSetModel' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetModel.php',
'Combodo\\iTop\\Service\\Links\\LinkSetRepository' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetRepository.php',
'Combodo\\iTop\\Service\\Links\\LinksBulkDataPostProcessor' => __DIR__ . '/../..' . '/sources/Service/Links/LinksBulkDataPostProcessor.php',
diff --git a/lib/composer/include_paths.php b/lib/composer/include_paths.php
index af33c1491..d4fb96718 100644
--- a/lib/composer/include_paths.php
+++ b/lib/composer/include_paths.php
@@ -2,7 +2,7 @@
// include_paths.php @generated by Composer
-$vendorDir = dirname(__DIR__);
+$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
diff --git a/lib/composer/installed.php b/lib/composer/installed.php
index 2c8578068..02a962cb6 100644
--- a/lib/composer/installed.php
+++ b/lib/composer/installed.php
@@ -5,7 +5,7 @@
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
- 'reference' => 'e645e9b00625680af30db024d4f2ef652179407c',
+ 'reference' => 'd0e6572fd0f88f05aa45e840a0311c2a0595d51a',
'name' => 'combodo/itop',
'dev' => true,
),
@@ -25,7 +25,7 @@
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
- 'reference' => 'e645e9b00625680af30db024d4f2ef652179407c',
+ 'reference' => 'd0e6572fd0f88f05aa45e840a0311c2a0595d51a',
'dev_requirement' => false,
),
'combodo/tcpdf' => array(
diff --git a/sources/Application/UI/Base/Component/DataTable/DataTableUIBlockFactory.php b/sources/Application/UI/Base/Component/DataTable/DataTableUIBlockFactory.php
index 80237bcab..de8737d43 100644
--- a/sources/Application/UI/Base/Component/DataTable/DataTableUIBlockFactory.php
+++ b/sources/Application/UI/Base/Component/DataTable/DataTableUIBlockFactory.php
@@ -249,6 +249,15 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
$oButton->SetColor($aAction['color']);
}
$oButton->SetDataAttributes(['label' => Dict::S($aAction['label']), 'action-id' => $iKey, 'table-id' => $oTable->GetId()]);
+ if (array_key_exists('metadata', $aAction)) {
+ $aMetadata = $aAction['metadata'];
+ if (is_array($aMetadata)) {
+ foreach ($aMetadata as $key => $value) {
+ $oButton->AddDataAttribute($key, $value);
+ }
+ }
+ }
+
$oToolbar->AddSubBlock($oButton);
}
@@ -922,10 +931,14 @@ JS;
/**give definition of id for select checkbox*/
'row_actions',
/** array of blocks displayed on every row */
- 'creation_in_modal_is_allowed',
- /** bool to allow a creation of a new object of this type in a modal */
+ 'creation_in_modal',
+ /** bool to perform a creation of a new object of this type in a modal */
+ 'creation_in_modal_tooltip',
+ /** creation in modal button tooltip */
'creation_in_modal_js_handler',
/** Handler to call when trying to create a new object in modal */
+ 'creation_disallowed',
+ /** Don't provide the standard object creation feature */
];
}
}
\ No newline at end of file
diff --git a/sources/Application/UI/Links/AbstractBlockLinkSetViewTable.php b/sources/Application/UI/Links/AbstractBlockLinkSetViewTable.php
index 13c45640a..98dc73c51 100644
--- a/sources/Application/UI/Links/AbstractBlockLinkSetViewTable.php
+++ b/sources/Application/UI/Links/AbstractBlockLinkSetViewTable.php
@@ -9,7 +9,6 @@ namespace Combodo\iTop\Application\UI\Links;
use ApplicationException;
use ArchivedObjectException;
use AttributeLinkedSet;
-use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
use CoreException;
use CoreWarning;
@@ -17,7 +16,6 @@ use DBObject;
use DictExceptionMissingString;
use DisplayBlock;
use Exception;
-use MetaModel;
use MySQLException;
use Utils;
use WebPage;
@@ -41,6 +39,20 @@ abstract class AbstractBlockLinkSetViewTable extends UIContentBlock
'js/wizardhelper.js',
];
+ // Dictionnary entries
+ public const BUTTON_TOOLTIP = 'UI:Links:Add:Button+';
+ public const DICT_CREATE_BUTTON_TOOLTIP = 'UI:Links:Create:Button+';
+ public const DICT_MODIFY_LINK_BUTTON_TOOLTIP = 'UI:Links:ModifyLink:Button+';
+ public const DICT_MODIFY_LINK_MODAL_TITLE = 'UI:Links:ModifyLink:Modal:Title';
+ public const DICT_MODIFY_OBJECT_BUTTON_TOOLTIP = 'UI:Links:ModifyObject:Button+';
+ public const DICT_MODIFY_OBJECT_MODAL_TITLE = 'UI:Links:ModifyObject:Modal:Title';
+ public const DICT_REMOVE_BUTTON_TOOLTIP = 'UI:Links:Remove:Button+';
+ public const DICT_REMOVE_MODAL_TITLE = 'UI:Links:Remove:Modal:Title';
+ public const DICT_REMOVE_MODAL_MESSAGE = 'UI:Links:Remove:Modal:Message';
+ public const DICT_DELETE_BUTTON_TOOLTIP = 'UI:Links:Delete:Button+';
+ public const DICT_DELETE_MODAL_TITLE = 'UI:Links:Delete:Modal:Title';
+ public const DICT_DELETE_MODAL_MESSAGE = 'UI:Links:Delete:Modal:Message';
+
/** @var DBObject $oDbObject db object witch link set belongs to */
protected DBObject $oDbObject;
@@ -99,6 +111,25 @@ abstract class AbstractBlockLinkSetViewTable extends UIContentBlock
$this->sTargetClass = $this->GetTargetClass();
}
+
+ /**
+ * @param string $sKey
+ * @param \DBObject|null $oDBObject
+ *
+ * @return string
+ * @throws \ArchivedObjectException
+ * @throws \CoreException
+ */
+ public function GetDictionaryEntry(string $sKey, DBObject $oDBObject = null)
+ {
+ return $this->oAttDef->SearchSpecificLabel($sKey, '', true,
+ $this->sObjectClass,
+ $this->oDbObject->Get('friendlyname'),
+ $this->oAttDef->GetLabel(),
+ $this->sTargetClass,
+ $oDBObject !== null ? $oDBObject->Get('friendlyname') : '{item}');
+ }
+
/**
* Initialize UI.
*
diff --git a/sources/Application/UI/Links/Direct/BlockDirectLinkSetViewTable.php b/sources/Application/UI/Links/Direct/BlockDirectLinkSetViewTable.php
index 251c50162..dbfee84dd 100644
--- a/sources/Application/UI/Links/Direct/BlockDirectLinkSetViewTable.php
+++ b/sources/Application/UI/Links/Direct/BlockDirectLinkSetViewTable.php
@@ -49,9 +49,12 @@ class BlockDirectLinkSetViewTable extends AbstractBlockLinkSetViewTable
}
// Add creation in modal if the linkset is not readonly
- if ($this->oAttDef->GetEditMode() != LINKSET_EDITMODE_NONE) {
- $aExtraParams['creation_in_modal_is_allowed'] = true;
+ if (!$this->oAttDef->GetReadOnly() && $this->oAttDef->GetEditMode() != LINKSET_EDITMODE_NONE) {
+ $aExtraParams['creation_in_modal'] = true;
+ $aExtraParams['creation_in_modal_tooltip'] = $this->GetDictionaryEntry(static::DICT_CREATE_BUTTON_TOOLTIP);
$aExtraParams['creation_in_modal_js_handler'] = "{$this->GetWidgetName()}.links_view_table('CreateLinkedObject');";
+ } else {
+ $aExtraParams['creation_disallowed'] = true;
}
return $aExtraParams;
@@ -69,30 +72,42 @@ class BlockDirectLinkSetViewTable extends AbstractBlockLinkSetViewTable
case LINKSET_EDITMODE_ADDONLY: // The only possible action is to open (in a new window) the form to create a new object
$aRowActions[] = array(
- 'label' => 'UI:Links:ActionRow:Modify',
- 'tooltip' => 'UI:Links:ActionRow:Modify+',
+ 'label' => 'UI:Links:ModifyObject:Button',
+ 'name' => 'ModifyButton',
+ 'tooltip' => $this->GetDictionaryEntry(static::DICT_MODIFY_OBJECT_BUTTON_TOOLTIP),
'icon_classes' => 'fas fa-pen',
- 'js_row_action' => "{$this->GetWidgetName()}.links_view_table('ModifyLinkedObject', aRowData['{$this->oAttDef->GetLinkedClass()}/_key_/raw']);",
+ 'js_row_action' => "{$this->GetWidgetName()}.links_view_table('ModifyLinkedObject', aRowData['{$this->oAttDef->GetLinkedClass()}/_key_/raw'], oTrElement, aRowData['{$this->sTargetClass}/friendlyname']);",
+ 'metadata' => [
+ 'modal-title' => $this->GetDictionaryEntry(static::DICT_MODIFY_OBJECT_MODAL_TITLE),
+ ],
);
break;
case LINKSET_EDITMODE_INPLACE: // The whole linkset can be edited 'in-place'
case LINKSET_EDITMODE_ACTIONS: // Show the usual 'Actions' popup menu
$aRowActions[] = array(
- 'label' => 'UI:Links:ActionRow:Modify',
- 'tooltip' => 'UI:Links:ActionRow:Modify+',
+ 'label' => 'UI:Links:ModifyObject:Button',
+ 'name' => 'ModifyButton',
+ 'tooltip' => $this->GetDictionaryEntry(static::DICT_MODIFY_OBJECT_BUTTON_TOOLTIP),
'icon_classes' => 'fas fa-pen',
- 'js_row_action' => "{$this->GetWidgetName()}.links_view_table('ModifyLinkedObject', aRowData['{$this->oAttDef->GetLinkedClass()}/_key_/raw']);",
+ 'js_row_action' => "{$this->GetWidgetName()}.links_view_table('ModifyLinkedObject', aRowData['{$this->oAttDef->GetLinkedClass()}/_key_/raw'], oTrElement, aRowData['{$this->sTargetClass}/friendlyname']);",
+ 'metadata' => [
+ 'modal-title' => $this->GetDictionaryEntry(static::DICT_MODIFY_OBJECT_MODAL_TITLE),
+ ],
);
+
$aRowActions[] = array(
- 'label' => 'UI:Links:ActionRow:Delete',
- 'tooltip' => 'UI:Links:ActionRow:Delete+',
+ 'label' => 'UI:Links:Delete:Button',
+ 'name' => 'DeleteButton',
+ 'tooltip' => $this->GetDictionaryEntry(static::DICT_DELETE_BUTTON_TOOLTIP),
'icon_classes' => 'fas fa-trash',
'color' => Button::ENUM_COLOR_SCHEME_DESTRUCTIVE,
'js_row_action' => "{$this->GetWidgetName()}.links_view_table('DeleteLinkedObject', aRowData['{$this->oAttDef->GetLinkedClass()}/_key_/raw'], oTrElement);",
'confirmation' => [
- 'message' => 'UI:Links:ActionRow:Delete:Confirmation',
- 'message_row_data' => "{$this->sTargetClass}/hyperlink",
+ 'title' => $this->GetDictionaryEntry(static::DICT_DELETE_MODAL_TITLE),
+ 'message' => $this->GetDictionaryEntry(static::DICT_DELETE_MODAL_MESSAGE),
+ 'confirm_button_class' => 'ibo-is-danger',
+ 'row_data' => "{$this->sTargetClass}/hyperlink",
'do_not_show_again_pref_key' => $this->GetDoNotShowAgainPreferenceKey(),
],
);
@@ -100,19 +115,25 @@ class BlockDirectLinkSetViewTable extends AbstractBlockLinkSetViewTable
case LINKSET_EDITMODE_ADDREMOVE: // The whole linkset can be edited 'in-place'
$aRowActions[] = array(
- 'label' => 'UI:Links:ActionRow:Modify',
- 'tooltip' => 'UI:Links:ActionRow:Modify+',
+ 'label' => 'UI:Links:ModifyObject:Button',
+ 'name' => 'ModifyButton',
+ 'tooltip' => $this->GetDictionaryEntry(static::DICT_MODIFY_OBJECT_BUTTON_TOOLTIP),
'icon_classes' => 'fas fa-pen',
- 'js_row_action' => "{$this->GetWidgetName()}.links_view_table('ModifyLinkedObject', aRowData['{$this->oAttDef->GetLinkedClass()}/_key_/raw']);",
+ 'js_row_action' => "{$this->GetWidgetName()}.links_view_table('ModifyLinkedObject', aRowData['{$this->oAttDef->GetLinkedClass()}/_key_/raw'], oTrElement, aRowData['{$this->sTargetClass}/friendlyname']);",
+ 'metadata' => [
+ 'modal-title' => $this->GetDictionaryEntry(static::DICT_MODIFY_OBJECT_MODAL_TITLE),
+ ],
);
$aRowActions[] = array(
- 'label' => 'UI:Links:ActionRow:Detach',
- 'tooltip' => 'UI:Links:ActionRow:Detach+',
+ 'label' => 'UI:Links:Remove:Button',
+ 'name' => 'RemoveButton',
+ 'tooltip' => $this->GetDictionaryEntry(static::DICT_REMOVE_BUTTON_TOOLTIP),
'icon_classes' => 'fas fa-minus',
- 'js_row_action' => "{$this->GetWidgetName()}.links_view_table('DetachLinkedObject', aRowData['{$this->sTargetClass}/_key_/raw'], oTrElement);",
+ 'js_row_action' => "{$this->GetWidgetName()}.links_view_table('DetachLinkedObject', aRowData['{$this->sTargetClass}/_key_/raw'], oTrElement, oTrElement);",
'confirmation' => [
- 'message' => 'UI:Links:ActionRow:Detach:Confirmation',
- 'message_row_data' => "{$this->sTargetClass}/hyperlink",
+ 'title' => $this->GetDictionaryEntry(static::DICT_REMOVE_MODAL_TITLE),
+ 'message' => $this->GetDictionaryEntry(static::DICT_REMOVE_MODAL_MESSAGE),
+ 'row_data' => "{$this->sTargetClass}/hyperlink",
'do_not_show_again_pref_key' => $this->GetDoNotShowAgainPreferenceKey(),
],
);
diff --git a/sources/Application/UI/Links/Indirect/BlockIndirectLinkSetViewTable.php b/sources/Application/UI/Links/Indirect/BlockIndirectLinkSetViewTable.php
index 595c1e1e0..1e6a1c0e3 100644
--- a/sources/Application/UI/Links/Indirect/BlockIndirectLinkSetViewTable.php
+++ b/sources/Application/UI/Links/Indirect/BlockIndirectLinkSetViewTable.php
@@ -7,7 +7,6 @@
namespace Combodo\iTop\Application\UI\Links\Indirect;
use Combodo\iTop\Application\UI\Links\AbstractBlockLinkSetViewTable;
-use Combodo\iTop\Service\Links\LinkSetModel;
use MetaModel;
use PHPUnit\Exception;
@@ -50,9 +49,9 @@ class BlockIndirectLinkSetViewTable extends AbstractBlockLinkSetViewTable
'zlist' => false,
'extra_fields' => $this->GetAttCodesToDisplay(),
'row_actions' => $this->GetRowActions(),
- 'currentId' => $this->GetTableId(),
- 'panel_title' => $this->oAttDef->GetLabel(),
- 'panel_icon' => MetaModel::GetClassIcon($this->GetTargetClass(), false),
+ 'currentId' => $this->GetTableId(),
+ 'panel_title' => $this->oAttDef->GetLabel(),
+ 'panel_icon' => MetaModel::GetClassIcon($this->GetTargetClass(), false),
);
// Description
@@ -62,9 +61,11 @@ class BlockIndirectLinkSetViewTable extends AbstractBlockLinkSetViewTable
// Add creation in modal if the linkset is not readonly
if (!$this->oAttDef->GetReadOnly()) {
- $sHostClass = get_class($this->oDbObject);
- $aExtraParams['creation_in_modal_is_allowed'] = true;
+ $aExtraParams['creation_in_modal'] = true;
+ $aExtraParams['creation_in_modal_tooltip'] = $this->GetDictionaryEntry(static::BUTTON_TOOLTIP);
$aExtraParams['creation_in_modal_js_handler'] = "{$this->GetWidgetName()}.links_view_table('CreateLinkedObject');";
+ } else {
+ $aExtraParams['creation_disallowed'] = true;
}
return $aExtraParams;
@@ -76,20 +77,26 @@ class BlockIndirectLinkSetViewTable extends AbstractBlockLinkSetViewTable
$aRowActions = array();
$aRowActions[] = array(
- 'label' => 'UI:Links:ActionRow:Modify',
- 'tooltip' => 'UI:Links:ActionRow:Modify+',
+ 'label' => 'UI:Links:ModifyLink:Button',
+ 'name' => 'ModifyButton',
+ 'tooltip' => $this->GetDictionaryEntry(static::DICT_MODIFY_LINK_BUTTON_TOOLTIP),
'icon_classes' => 'fas fa-pen',
- 'js_row_action' => "{$this->GetWidgetName()}.links_view_table('ModifyLinkedObject', aRowData['Link/_key_/raw']);",
+ 'js_row_action' => "{$this->GetWidgetName()}.links_view_table('ModifyLinkedObject', aRowData['Link/_key_/raw'], oTrElement, aRowData['Remote/friendlyname']);",
+ 'metadata' => [
+ 'modal-title' => $this->GetDictionaryEntry(static::DICT_MODIFY_LINK_MODAL_TITLE),
+ ],
);
$aRowActions[] = array(
- 'label' => 'UI:Links:ActionRow:Detach',
- 'tooltip' => 'UI:Links:ActionRow:Detach+',
+ 'label' => 'UI:Links:Remove:Button',
+ 'name' => 'RemoveButton',
+ 'tooltip' => $this->GetDictionaryEntry(static::DICT_REMOVE_BUTTON_TOOLTIP),
'icon_classes' => 'fas fa-minus',
'js_row_action' => "{$this->GetWidgetName()}.links_view_table('DeleteLinkedObject', aRowData['Link/_key_/raw'], oTrElement);",
'confirmation' => [
- 'message' => 'UI:Links:ActionRow:Detach:Confirmation',
- 'message_row_data' => "Remote/hyperlink",
+ 'title' => $this->GetDictionaryEntry(static::DICT_REMOVE_MODAL_TITLE),
+ 'message' => $this->GetDictionaryEntry(static::DICT_REMOVE_MODAL_MESSAGE),
+ 'row_data' => "Remote/hyperlink",
'do_not_show_again_pref_key' => $this->GetDoNotShowAgainPreferenceKey(),
],
);
diff --git a/sources/Controller/AjaxRenderController.php b/sources/Controller/AjaxRenderController.php
index 982656e97..1744cee63 100644
--- a/sources/Controller/AjaxRenderController.php
+++ b/sources/Controller/AjaxRenderController.php
@@ -81,6 +81,7 @@ class AjaxRenderController
$aObj[$sAlias."/_key_"] = $aObject[$sAlias]->GetKey();
$aObj[$sAlias."/_key_/raw"] = $aObject[$sAlias]->GetKey();
$aObj[$sAlias."/hyperlink"] = $aObject[$sAlias]->GetHyperlink();
+ $aObj[$sAlias."/friendlyname"] = $aObject[$sAlias]->Get('friendlyname');
// N°5943 Protection against $aColumnsLoad having less class aliases than $aClassAliases, this is in case the method's consumer isn't passing data correctly
if (false === array_key_exists($sAlias, $aColumnsLoad)) {
diff --git a/sources/Controller/Base/Layout/ObjectController.php b/sources/Controller/Base/Layout/ObjectController.php
index 4cf4886fb..7479f8a15 100644
--- a/sources/Controller/Base/Layout/ObjectController.php
+++ b/sources/Controller/Base/Layout/ObjectController.php
@@ -219,6 +219,7 @@ JS
$bPrintable = utils::ReadParam('printable', '0') === '1';
$sClass = utils::ReadParam('class', '', false, 'class');
$sId = utils::ReadParam('id', '');
+ $sFormTitle = utils::ReadPostedParam('form_title', null, utils::ENUM_SANITIZATION_FILTER_STRING);
// Check parameters
if (utils::IsNullOrEmptyString($sClass) || utils::IsNullOrEmptyString($sId))
@@ -243,6 +244,11 @@ JS
$aFormExtraParams = array('wizard_container' => 1);
FormHelper::UpdateFlagsFromContext($oObj, $aFormExtraParams);
+ // Allow form title customization
+ if (!utils::IsNullOrEmptyString($sFormTitle)) {
+ $aFormExtraParams['form_title'] = $sFormTitle;
+ }
+
if ($this->IsHandlingXmlHttpRequest()) {
$oPage = new AjaxPage('');
$aFormExtraParams['js_handlers'] = [];
diff --git a/sources/Controller/Links/LinkSetController.php b/sources/Controller/Links/LinkSetController.php
index ec5ac169c..1373b52d9 100644
--- a/sources/Controller/Links/LinkSetController.php
+++ b/sources/Controller/Links/LinkSetController.php
@@ -11,6 +11,7 @@ use cmdbAbstractObject;
use Combodo\iTop\Application\Helper\FormHelper;
use Combodo\iTop\Application\UI\Base\Component\Form\FormUIBlockFactory;
use Combodo\iTop\Controller\AbstractController;
+use Combodo\iTop\Service\Links\LinkSetModel;
use Combodo\iTop\Service\Router\Router;
use Combodo\iTop\Service\Base\ObjectRepository;
use Exception;
@@ -198,17 +199,30 @@ class LinkSetController extends AbstractController
}
});
}
-JS
- ;
+JS;
+
+
+ // Form title
+ /** @var \AttributeLinkedSet $oLinksetDef */
+ $oLinksetDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
+ $oHostObj = MetaModel::GetObject($sClass, $sId);
+ $sFormTitle = $oLinksetDef->SearchSpecificLabel('UI:Links:Add:Modal:Title', '', true,
+ $sClass,
+ $oHostObj->Get('friendlyname'),
+ $oLinksetDef->GetLabel(),
+ LinkSetModel::GetTargetClass($oLinksetDef));
+
$aExtraParams = [
- 'noRelations' => true,
- 'hide_transitions' => true,
- 'formPrefix' => $sAttCode,
- 'fieldsFlags' => $aFieldFlags,
+ 'noRelations' => true,
+ 'hide_transitions' => true,
+ 'formPrefix' => $sAttCode,
+ 'fieldsFlags' => $aFieldFlags,
'forceFieldsSubmission' => [
- $sExtKeyToMe
+ $sExtKeyToMe,
],
- 'js_handlers' => [
+ 'form_title' => $sFormTitle,
+ 'custom_button' => \Dict::S('UI:Button:Add'),
+ 'js_handlers' => [
'form_on_submit' => $sFormOnSubmitJsCode,
'cancel_button_on_click' =>
<<