diff --git a/application/ui.htmleditorwidget.class.inc.php b/application/ui.htmleditorwidget.class.inc.php
index 440124cc2e..3bc22b6163 100644
--- a/application/ui.htmleditorwidget.class.inc.php
+++ b/application/ui.htmleditorwidget.class.inc.php
@@ -72,15 +72,16 @@ public function Display(WebPage $oPage, $aArgs = array())
// To change the default settings of the editor,
// a) edit the file /js/ckeditor/config.js
// b) or override some of the configuration settings, using the second parameter of ckeditor()
+ $sJSDefineWidth = '';
$aConfig = utils::GetCkeditorPref();
$sWidthSpec = addslashes(trim($this->m_oAttDef->GetWidth()));
- if ($sWidthSpec != '')
- {
- $aConfig['width'] = $sWidthSpec;
+ if ($sWidthSpec != '') {
+ /*N°6543 - the function min allow to keep text inside the column when width is defined*/
+ $aConfig['width'] = "min($sWidthSpec,100%)";
+ $sJSDefineWidth = '$("#cke_'.$iId.' iframe").contents().find("body").css("width", "'.$sWidthSpec.'")';
}
$sHeightSpec = addslashes(trim($this->m_oAttDef->GetHeight()));
- if ($sHeightSpec != '')
- {
+ if ($sHeightSpec != '') {
$aConfig['height'] = $sHeightSpec;
}
$sConfigJS = json_encode($aConfig);
@@ -111,6 +112,7 @@ public function Display(WebPage $oPage, $aArgs = array())
else
{
oMe.data('ckeditorInstance').setReadOnly(oMe.prop('disabled'));
+ $sJSDefineWidth
}
};
setTimeout(delayedSetReadOnly, 50);
diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php
index 8416be1d6e..bc4b29336a 100644
--- a/core/attributedef.class.inc.php
+++ b/core/attributedef.class.inc.php
@@ -4535,7 +4535,6 @@ public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
$sStyle = '';
if (count($aStyles) > 0)
{
- $aStyles[] = 'overflow:auto';
$sStyle = 'style="'.implode(';', $aStyles).'"';
}
diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php
index c9ccb436f9..ca0413b5b3 100644
--- a/core/cmdbobject.class.inc.php
+++ b/core/cmdbobject.class.inc.php
@@ -335,7 +335,7 @@ protected function RecordObjDeletion($objkey)
$oMyChangeOp->Set("objclass", MetaModel::GetRootClass(get_class($this)));
$oMyChangeOp->Set("objkey", $objkey);
$oMyChangeOp->Set("fclass", get_class($this));
- $oMyChangeOp->Set("fname", substr($this->GetRawName(), 0, 255)); // Protect against very long friendly names
+ $oMyChangeOp->SetTrim("fname", $this->GetRawName()); // Protect against very long friendly names
$iId = $oMyChangeOp->DBInsertNoReload();
}
diff --git a/css/backoffice/components/_field.scss b/css/backoffice/components/_field.scss
index 642715bd2a..6b20c1e518 100644
--- a/css/backoffice/components/_field.scss
+++ b/css/backoffice/components/_field.scss
@@ -80,16 +80,29 @@ $ibo-field--enable-bulk--checkbox--margin-left: $ibo-spacing-300 !default;
}
}
}
+
+ /*N°6543 - We need the rule to keep text inside the column when width is defined*/
+ &[data-attribute-type="AttributeHtml"],
+ &[data-attribute-type="AttributeText"] {
+ &[data-attribute-flag-read-only="true"] {
+ display: grid;
+
+ > .ibo-field--value {
+ max-width: 100%;
+ overflow: auto;
+ }
+ }
+ }
}
-/* Large field = Label on top, value below */
-.ibo-field-large {
- display: block;
+ /* Large field = Label on top, value below */
+ .ibo-field-large {
+ display: block;
- .ibo-field--label {
- position: relative; /* Necessary for fullscreen toggler */
- display: flex;
- align-items: center;
+ .ibo-field--label {
+ position: relative; /* Necessary for fullscreen toggler */
+ display: flex;
+ align-items: center;
max-width: initial;
width: 100%;
}
diff --git a/css/backoffice/components/datatable/_datatable.scss b/css/backoffice/components/datatable/_datatable.scss
index 95797d908a..c8147baa67 100644
--- a/css/backoffice/components/datatable/_datatable.scss
+++ b/css/backoffice/components/datatable/_datatable.scss
@@ -123,6 +123,13 @@ $ibo-fieldsorter--selected--background-color: $ibo-color-blue-200 !default;
.ibo-datatable--row-actions-toolbar{
justify-content: end;
}
+ /* N°6543 - We need the rule to keep text inside the column when width is defined */
+ > [data-attribute-type="AttributeHtml"],
+ > [data-attribute-type="AttributeText"] {
+ max-width: 100%;
+ overflow: auto;
+ }
+
}
}
diff --git a/datamodels/2.x/itop-attachments/datamodel.itop-attachments.xml b/datamodels/2.x/itop-attachments/datamodel.itop-attachments.xml
index 50271092f6..6e0acee097 100755
--- a/datamodels/2.x/itop-attachments/datamodel.itop-attachments.xml
+++ b/datamodels/2.x/itop-attachments/datamodel.itop-attachments.xml
@@ -269,16 +269,15 @@
An attachment has been added to an object
Attachment::AfterUpdate
-
- The attachment updated
+ The object where the attachment is added
DBObject
-
- The object to which the attachment is linked
+
+ The attachment added to the objet
DBObject
@@ -291,16 +290,15 @@
An attachment has been removed from an object
Attachment::AfterUpdate
-
- The attachment updated
+ The object where the attachment is removed
DBObject
-
- The object to which the attachment is linked
+
+ The attachment removed
DBObject
diff --git a/datamodels/2.x/itop-attachments/main.itop-attachments.php b/datamodels/2.x/itop-attachments/main.itop-attachments.php
index 0f70f34b89..94a1e72699 100644
--- a/datamodels/2.x/itop-attachments/main.itop-attachments.php
+++ b/datamodels/2.x/itop-attachments/main.itop-attachments.php
@@ -264,7 +264,6 @@ public function DisplayAttachments(DBObject $oObject, WebPage $oPage, $bEditMode
else
{
$oAttachmentsRenderer->RenderViewAttachmentsList();
-
}
}
@@ -293,8 +292,8 @@ protected static function UpdateAttachments($oObject, $oChange = null)
// Remove attachments that are no longer attached to the current object
if (in_array($oAttachment->GetKey(), $aRemovedAttachmentIds))
{
- $aData = ['target_object' => $oObject];
- $oAttachment->FireEvent(EVENT_REMOVE_ATTACHMENT_FROM_OBJECT, $aData);
+ $aData = ['attachment' => $oAttachment];
+ $oObject->FireEvent(EVENT_REMOVE_ATTACHMENT_FROM_OBJECT, $aData);
$oAttachment->DBDelete();
$aActions[] = self::GetActionChangeOp($oAttachment, false /* false => deletion */);
}
@@ -322,8 +321,8 @@ protected static function UpdateAttachments($oObject, $oChange = null)
$oAttachment->DBUpdate();
// temporary attachment confirmed, list it in the history
$aActions[] = self::GetActionChangeOp($oAttachment, true /* true => creation */);
- $aData = ['target_object' => $oObject];
- $oAttachment->FireEvent(EVENT_ADD_ATTACHMENT_TO_OBJECT, $aData);
+ $aData = ['attachment' => $oAttachment];
+ $oObject->FireEvent(EVENT_ADD_ATTACHMENT_TO_OBJECT, $aData);
}
}
if (count($aActions) > 0)
diff --git a/templates/base/components/datatable/row-actions/handler.js.twig b/templates/base/components/datatable/row-actions/handler.js.twig
index 38c699f8c0..56c0811f96 100644
--- a/templates/base/components/datatable/row-actions/handler.js.twig
+++ b/templates/base/components/datatable/row-actions/handler.js.twig
@@ -23,16 +23,16 @@
{% if aAction.confirmation is defined %}
// Prepare confirmation title
- let sTitle = '{{ 'UI:Datatables:RowActions:ConfirmationDialog'|dict_s }}';
+ let sTitle = `{{ 'UI:Datatables:RowActions:ConfirmationDialog'|dict_s|escape('js') }}`;
{% if aAction.confirmation.title is defined %}
- sTitle = '{{ aAction.confirmation.title|dict_s }}';
+ sTitle = `{{ aAction.confirmation.title|dict_s|escape('js') }}`;
{% endif %}
sTitle = sTitle.replaceAll('{item}', aRowData['{{ aAction.confirmation.row_data }}']);
// Prepare confirmation message
- let sMessage = '{{ 'UI:Datatables:RowActions:ConfirmationMessage'|dict_s }}';
+ let sMessage = `{{ 'UI:Datatables:RowActions:ConfirmationMessage'|dict_s|escape('js') }}`;
{% if aAction.confirmation.message is defined %}
- sMessage = '{{ aAction.confirmation.message|dict_s }}';
+ sMessage = `{{ aAction.confirmation.message|dict_s|escape('js') }}`;
{% endif %}
sMessage = sMessage.replaceAll('{item}', aRowData['{{ aAction.confirmation.row_data }}']);
diff --git a/tests/php-unit-tests/unitary-tests/core/CMDBObjectTest.php b/tests/php-unit-tests/unitary-tests/core/CMDBObjectTest.php
index 4d2e2ad9fc..8188ec08f6 100644
--- a/tests/php-unit-tests/unitary-tests/core/CMDBObjectTest.php
+++ b/tests/php-unit-tests/unitary-tests/core/CMDBObjectTest.php
@@ -5,9 +5,11 @@
use CMDBObject;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
+use CoreException;
use Exception;
use MetaModel;
+
/**
* @since 2.7.7 3.0.2 3.1.0 N°3717 tests history objects creation
*
@@ -168,6 +170,53 @@ public function testCurrentChangeUnderImpersonation($sTrackInfo=null, $sExpected
CMDBObject::SetTrackInfo($sInitialTrackInfo);
}
+ /**
+ * Data provider for test deletion
+ * N°5547 - Object deletion fails if friendlyname too long
+ *
+ * @return array data
+ */
+ public function RecordObjDeletionProvider()
+ {
+ return [
+ 'friendlyname longer than 255 characters which will be truncated on a multi-bytes characters' => [
+ str_repeat('e', 250),
+ '😁😂🤣😃😄😅😆😗🥰😘😍😎😋😊😉😙😚',
+ ],
+ 'friendlyname longer than 255 characters which will be truncated after a single byte characters' => [
+ '😁😂🤣😃😄😅😆😗🥰😘😍😎😋😊😉😙😚',
+ str_repeat('e', 250),
+ ],
+ ];
+ }
+
+ /**
+ * N°5547 - Object deletion fails if friendlyname too long
+ *
+ * @dataProvider RecordObjDeletionProvider
+ *
+ */
+ public function testRecordObjDeletion( string $sFirstName, string $sName)
+ {
+ $oPerson = MetaModel::NewObject('Person', [
+ 'first_name' => $sFirstName,
+ 'name' => $sName,
+ 'org_id' => 1,
+ ]);
+ $oPerson->DBWrite();
+
+ $bDeletionOK = true;
+ try {
+ $oDeletionPlan = $this->InvokeNonPublicMethod(CMDBObject::class, 'RecordObjDeletion', $oPerson, [$oPerson->GetKey()]);
+ }
+ catch (CoreException $e) {
+ $bDeletionOK = false;
+ }
+ // We don't need to test the result (truncated string), it's already done in \DBObject::SetTrim() with N°3448
+ $this->assertTrue($bDeletionOK);
+ }
+
+
private function ReplaceByFriendlyNames($sMessage, $oAdminUser, $oImpersonatedUser) : string {
$sNewMessage = str_replace('AdminSurName AdminName', $oAdminUser->GetFriendlyName(), $sMessage);
$sNewMessage = str_replace('ImpersonatedSurName ImpersonatedName', $oImpersonatedUser->GetFriendlyName(), $sNewMessage);
diff --git a/tests/php-unit-tests/unitary-tests/core/DBObjectTest.php b/tests/php-unit-tests/unitary-tests/core/DBObjectTest.php
index 433032aca3..321610d78b 100644
--- a/tests/php-unit-tests/unitary-tests/core/DBObjectTest.php
+++ b/tests/php-unit-tests/unitary-tests/core/DBObjectTest.php
@@ -1133,6 +1133,54 @@ private function CreatePersonInstance()
return $oPerson;
}
+ /**
+ * Data provider for test deletion
+ * N°5547 - Object deletion fails if friendlyname too long
+ *
+ * @return array data
+ */
+ public function getDeletionLongValueProvider()
+ {
+ return [
+ 'friendlyname longer than 255 chracters with smiley' => [
+ '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789-0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopq',
+ '😁😂🤣😃😄😅😆😗🥰😘😍😎😋😊😉😙😚',
+ ],
+ 'the same friendlyname in other order with error before fix 5547 ' => [
+ '😁😂🤣😃😄😅😆😗🥰😘😍😎😋😊😉😙😚',
+ '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789-0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopq',
+ ],
+ ];
+ }
+
+ /**
+ * N°5547 - Object deletion fails if friendlyname too long
+ *
+ * @covers DBObject::DBIncrement
+ *
+ * @dataProvider getDeletionLongValueProvider
+ *
+ */
+ public function testDeletionLongValue(string $sName, string $sFirstName)
+ {
+ // Create a UserRequest with 2 contacts
+ $oPerson = MetaModel::NewObject('Person', [
+ 'name' => $sName,
+ 'first_name' => $sFirstName,
+ 'org_id' => 1,
+ ]);
+ $oPerson->DBWrite();
+
+ $bDeletionOK = true;
+ try {
+ $oDeletionPlan = $oPerson->DBDelete();
+ }
+ catch (CoreException $e) {
+ $bDeletionOK = false;
+ }
+ $this->assertTrue($bDeletionOK);
+ }
+
public function ResetReloadCount()
{
$this->aReloadCount = [];
@@ -1211,8 +1259,14 @@ public function testConstructorMemoryFootprint():void
$fTotalDuration = microtime(true) - $fStart;
echo 'Total duration: '.sprintf('%.3f s', $fTotalDuration)."\n\n";
}
-
- public function CheckLongValueInAttributeProvider() {
+ /**
+ * Data provider for test deletion
+ * N°5547 - Object deletion fails if friendlyname too long
+ *
+ * @return array data
+ */
+ public function DeletionLongValueProvider()
+ {
return [
// UserRequest.title is an AttributeString (maxsize = 255)
'title 250 chars' => ['title', 250],
@@ -1234,11 +1288,9 @@ public function CheckLongValueInAttributeProvider() {
/**
* Test check long field with non ascii characters
*
- * @covers DBObject::Set
- * @covers DBObject::CheckToWrite
- * @covers DBObject::SetTrim
+ * @covers DBObject::DBDelete
*
- * @dataProvider CheckLongValueInAttributeProvider
+ * @dataProvider DeletionLongValueProvider
*
* @since 3.1.2 N°3448 - Framework field size check not correctly implemented for multi-bytes languages/strings
*/
diff --git a/tests/php-unit-tests/unitary-tests/datamodels/2.x/itop-attachments/TestAttachment.php b/tests/php-unit-tests/unitary-tests/datamodels/2.x/itop-attachments/TestAttachment.php
new file mode 100644
index 0000000000..8485c956f0
--- /dev/null
+++ b/tests/php-unit-tests/unitary-tests/datamodels/2.x/itop-attachments/TestAttachment.php
@@ -0,0 +1,72 @@
+sAddAttachmentName = '';
+ $this->sRemoveAttachmentName = '';
+
+ $_REQUEST['transaction_id'] = 'test_transaction';
+ $_REQUEST['attachment_plugin'] = 'in_form';
+
+ $oDocument = new \ormDocument('Test', 'text/plain', 'test.txt');
+
+ $this->EventService_RegisterListener(EVENT_ADD_ATTACHMENT_TO_OBJECT, [$this, 'OnAddAttachment']);
+ $this->EventService_RegisterListener(EVENT_REMOVE_ATTACHMENT_FROM_OBJECT, [$this, 'OnRemoveAttachment']);
+
+ $oAttachment = MetaModel::NewObject('Attachment', [
+ 'item_class' => 'UserRequest',
+ 'temp_id' => 'test_transaction',
+ 'contents' => $oDocument,
+ ]);
+
+ $oAttachment->DBInsert();
+ $oTicket = $this->CreateTicket(1);
+
+ $_REQUEST['removed_attachments'] = [$oAttachment->GetKey()];
+ $this->InvokeNonPublicStaticMethod(\AttachmentPlugIn::class, 'UpdateAttachments', [$oTicket]);
+
+ $this->assertEquals('test.txt', $this->sAddAttachmentName);
+ $this->assertEquals('test.txt', $this->sRemoveAttachmentName);
+ }
+
+ public function OnAddAttachment(EventData $oData)
+ {
+ $this->debug('OnAddAttachment');
+ $this->assertEquals('UserRequest', get_class($oData->Get('object')));
+ $oAttachment = $oData->Get('attachment');
+ /** @var \ormDocument $oDocument */
+ $oDocument = $oAttachment->Get('contents');
+ $this->sAddAttachmentName = $oDocument->GetFileName();
+ }
+
+ public function OnRemoveAttachment(EventData $oData)
+ {
+ $this->debug('OnRemoveAttachment');
+ $this->assertEquals('UserRequest', get_class($oData->Get('object')));
+ $oAttachment = $oData->Get('attachment');
+ /** @var \ormDocument $oDocument */
+ $oDocument = $oAttachment->Get('contents');
+ $this->sRemoveAttachmentName = $oDocument->GetFileName();
+ }
+}