diff --git a/application/applicationextension.inc.php b/application/applicationextension.inc.php
index a007a3ab3..4576e4fa0 100644
--- a/application/applicationextension.inc.php
+++ b/application/applicationextension.inc.php
@@ -2246,3 +2246,25 @@ interface iModuleExtension
*/
public function __construct();
}
+
+/**
+ * Interface to manipulate ormCaseLog objects after initialization/edition
+ *
+ * @api
+ * @private
+ * @since 3.1.0 N°6275
+ */
+interface iOrmCaseLogExtension
+{
+ public function __construct();
+
+ /**
+ * Rebuild API to be able manipulate ormCaseLog after its initialization/modifications
+ * Examples of use: fix ormcase log broken index/shrink huge histories/....
+ * @param string $sLog: ormcaselog description
+ * @param array|null $aIndex: ormcaselog index
+ *
+ * @return bool: indicate whether current ormCaseLog fields were touched
+ */
+ public function Rebuild(&$sLog, &$aIndex) : bool;
+}
diff --git a/core/ormcaselog.class.inc.php b/core/ormcaselog.class.inc.php
index 52e1629d9..5646faf13 100644
--- a/core/ormcaselog.class.inc.php
+++ b/core/ormcaselog.class.inc.php
@@ -3,7 +3,7 @@
//
// This file is part of iTop.
//
-// iTop is free software; you can redistribute it and/or modify
+// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
@@ -25,10 +25,11 @@ use Combodo\iTop\Renderer\BlockRenderer;
define('CASELOG_VISIBLE_ITEMS', 2);
define('CASELOG_SEPARATOR', "\n".'========== %1$s : %2$s (%3$d) ============'."\n\n");
+require_once('ormcaselogservice.inc.php');
/**
* Class to store a "case log" in a structured way, keeping track of its successive entries
- *
+ *
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -47,19 +48,22 @@ class ormCaseLog {
protected $m_sLog;
protected $m_aIndex;
protected $m_bModified;
-
+ protected \ormCaseLogService $oOrmCaseLogService;
+
/**
* Initializes the log with the first (initial) entry
* @param $sLog string The text of the whole case log
* @param $aIndex array The case log index
*/
- public function __construct($sLog = '', $aIndex = array())
+ public function __construct($sLog = '', $aIndex = [], \ormCaseLogService $oOrmCaseLogService=null)
{
$this->m_sLog = $sLog;
$this->m_aIndex = $aIndex;
$this->m_bModified = false;
+ $this->oOrmCaseLogService = (is_null($oOrmCaseLogService)) ? new \ormCaseLogService() : $oOrmCaseLogService;
+ $this->RebuildIndex();
}
-
+
public function GetText($bConvertToPlainText = false)
{
if ($bConvertToPlainText)
@@ -72,7 +76,7 @@ class ormCaseLog {
return $this->m_sLog;
}
}
-
+
public static function FromJSON($oJson)
{
if (!isset($oJson->items))
@@ -88,8 +92,8 @@ class ormCaseLog {
}
/**
- * Return a value that will be further JSON encoded
- */
+ * Return a value that will be further JSON encoded
+ */
public function GetForJSON()
{
// Order by ascending date
@@ -199,9 +203,9 @@ class ormCaseLog {
$sSeparator = sprintf(CASELOG_SEPARATOR, $aData['date'], $aData['user_login'], $aData['user_id']);
$sPlainText .= $sSeparator.$aData['message'];
}
- return $sPlainText;
+ return $sPlainText;
}
-
+
public function GetIndex()
{
return $this->m_aIndex;
@@ -227,7 +231,7 @@ class ormCaseLog {
{
return count($this->m_aIndex);
}
-
+
public function ClearModifiedFlag()
{
$this->m_bModified = false;
@@ -235,7 +239,7 @@ class ormCaseLog {
/**
* Produces an HTML representation, aimed at being used within an email
- */
+ */
public function GetAsEmailHtml()
{
$sStyleCaseLogHeader = '';
@@ -312,10 +316,10 @@ class ormCaseLog {
$sHtml .= '';
return $sHtml;
}
-
+
/**
* Produces an HTML representation, aimed at being used to produce a PDF with TCPDF (no table)
- */
+ */
public function GetAsSimpleHtml($aTransfoHandler = null)
{
$sStyleCaseLogEntry = '';
@@ -338,7 +342,7 @@ class ormCaseLog {
$sTextEntry = call_user_func($aTransfoHandler, $sTextEntry, true /* wiki "links" only */);
}
$sTextEntry = InlineImage::FixUrls($sTextEntry);
- }
+ }
$iPos += $aIndex[$index]['text_length'];
$sEntry = '
';
@@ -396,7 +400,7 @@ class ormCaseLog {
/**
* Produces an HTML representation, aimed at being used within the iTop framework
- */
+ */
public function GetAsHTML(WebPage $oP = null, $bEditMode = false, $aTransfoHandler = null)
{
$bPrintableVersion = (utils::ReadParam('printable', '0') == '1');
@@ -519,7 +523,7 @@ class ormCaseLog {
}
}
}
-
+
return $sHtml;
}
@@ -534,7 +538,7 @@ class ormCaseLog {
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \OQLException
- *
+ *
* @since 3.0.0 New $iOnBehalfOfId parameter
* @since 3.0.0 May throw \ArchivedObjectException exception
*/
@@ -585,6 +589,7 @@ class ormCaseLog {
'separator_length' => $iSepLength,
'format' => static::ENUM_FORMAT_HTML,
);
+ $this->RebuildIndex();
$this->m_bModified = true;
}
@@ -620,7 +625,7 @@ class ormCaseLog {
$iUserId = UserRights::GetUserId();
$sOnBehalfOf = UserRights::GetUserFriendlyName();
}
-
+
if (isset($oJson->date))
{
$oDate = new DateTime($oJson->date);
@@ -653,14 +658,14 @@ class ormCaseLog {
$iTextlength = strlen($sText);
$this->m_sLog = $sSeparator.$sText.$this->m_sLog; // Latest entry printed first
$this->m_aIndex[] = array(
- 'user_name' => $sOnBehalfOf,
- 'user_id' => $iUserId,
- 'date' => $iDate,
- 'text_length' => $iTextlength,
+ 'user_name' => $sOnBehalfOf,
+ 'user_id' => $iUserId,
+ 'date' => $iDate,
+ 'text_length' => $iTextlength,
'separator_length' => $iSepLength,
'format' => $sFormat,
);
-
+ $this->RebuildIndex();
$this->m_bModified = true;
}
@@ -716,7 +721,7 @@ class ormCaseLog {
$iLast = end($aKeys); // Strict standards: the parameter passed to 'end' must be a variable since it is passed by reference
return $iLast;
}
-
+
/**
* Get the text string corresponding to the given entry in the log (zero based index, older entries first)
* @param integer $iIndex
@@ -736,4 +741,16 @@ class ormCaseLog {
$sText = substr($this->m_sLog, $iPos, $this->m_aIndex[$index]['text_length']);
return InlineImage::FixUrls($sText);
}
+
+ /**
+ * @since 3.1.0 N°6275
+ */
+ public function RebuildIndex(): void
+ {
+ $oNewOrmCaseLog = $this->oOrmCaseLogService->Rebuild($this->m_sLog, $this->m_aIndex);
+ if (! is_null($oNewOrmCaseLog)) {
+ $this->m_aIndex = $oNewOrmCaseLog->m_aIndex;
+ $this->m_sLog = $oNewOrmCaseLog->m_sLog;
+ }
+ }
}
diff --git a/core/ormcaselogservice.inc.php b/core/ormcaselogservice.inc.php
new file mode 100644
index 000000000..d1e933e3c
--- /dev/null
+++ b/core/ormcaselogservice.inc.php
@@ -0,0 +1,58 @@
+aOrmCaseLogExtension !== null) return;
+
+ $aOrmCaseLogExtension = [];
+ $aProviderClasses = \utils::GetClassesForInterface(iOrmCaseLogExtension::class, '', array('[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]', '[\\\\/]tests[\\\\/]'));
+ foreach($aProviderClasses as $sProviderClass) {
+ $aOrmCaseLogExtension[] = new $sProviderClass();
+ }
+ $this->aOrmCaseLogExtension = $aOrmCaseLogExtension;
+ }
+
+ /**
+ * @param string|null $sLog
+ * @param array|null $aIndex
+ *
+ * @return \ormCaseLog|null: returns rebuilt ormCaseLog. null if not touched
+ */
+ public function Rebuild($sLog, $aIndex) : ?\ormCaseLog
+ {
+ $this->LoadCaseLogExtensions();
+
+ $bTouched = false;
+ foreach ($this->aOrmCaseLogExtension as $oOrmCaseLogExtension){
+ /** var iOrmCaseLogExtension $oOrmCaseLogExtension */
+ $bTouched = $bTouched || $oOrmCaseLogExtension->Rebuild($sLog, $aIndex);
+ }
+
+ if ($bTouched){
+ return new \ormCaseLog($sLog, $aIndex);
+ }
+
+ return null;
+ }
+}
diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php
index b85485524..b15367dce 100644
--- a/lib/composer/autoload_classmap.php
+++ b/lib/composer/autoload_classmap.php
@@ -2970,6 +2970,7 @@ return array(
'lnkAuditCategoryToAuditDomain' => $baseDir . '/application/audit.domain.class.inc.php',
'lnkTriggerAction' => $baseDir . '/core/trigger.class.inc.php',
'ormCaseLog' => $baseDir . '/core/ormcaselog.class.inc.php',
+ 'ormCaseLogService' => $baseDir . '/core/ormcaselogservice.inc.php',
'ormCustomFieldsValue' => $baseDir . '/core/ormcustomfieldsvalue.class.inc.php',
'ormDocument' => $baseDir . '/core/ormdocument.class.inc.php',
'ormLinkSet' => $baseDir . '/core/ormlinkset.class.inc.php',
diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php
index c10b2e716..15e466bb4 100644
--- a/lib/composer/autoload_static.php
+++ b/lib/composer/autoload_static.php
@@ -3335,6 +3335,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'lnkAuditCategoryToAuditDomain' => __DIR__ . '/../..' . '/application/audit.domain.class.inc.php',
'lnkTriggerAction' => __DIR__ . '/../..' . '/core/trigger.class.inc.php',
'ormCaseLog' => __DIR__ . '/../..' . '/core/ormcaselog.class.inc.php',
+ 'ormCaseLogService' => __DIR__ . '/../..' . '/core/ormcaselogservice.inc.php',
'ormCustomFieldsValue' => __DIR__ . '/../..' . '/core/ormcustomfieldsvalue.class.inc.php',
'ormDocument' => __DIR__ . '/../..' . '/core/ormdocument.class.inc.php',
'ormLinkSet' => __DIR__ . '/../..' . '/core/ormlinkset.class.inc.php',
diff --git a/tests/php-unit-tests/unitary-tests/core/ormCaseLogTest.php b/tests/php-unit-tests/unitary-tests/core/ormCaseLogTest.php
index 484be7951..18e3e3c6a 100644
--- a/tests/php-unit-tests/unitary-tests/core/ormCaseLogTest.php
+++ b/tests/php-unit-tests/unitary-tests/core/ormCaseLogTest.php
@@ -22,6 +22,32 @@ use ormCaseLog;
*/
class ormCaseLogTest extends ItopDataTestCase
{
+ const USE_TRANSACTION = false;
+ private $sLogin;
+ private $sPassword = "Iuytrez9876543ç_è-(";
+
+ public function setUp() :void{
+ parent::setUp(); // TODO: Change the autogenerated stub
+ //require_once APPROOT . "core/CaseLogService.php";
+
+ $oAdminProfile = \MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => 'Administrator'), true);
+
+ if (is_object($oAdminProfile)) {
+ $this->sLogin = "admin-".date('dmYHis');
+
+ $this->CreateTestOrganization();
+
+ /** @var \Person $oPerson */
+ $oPerson = $this->createObject('Person', array(
+ 'name' => $this->sLogin,
+ 'first_name' => 'Test',
+ 'org_id' => $this->getTestOrgId(),
+ ));
+
+ $this->CreateUser($this->sLogin, $oAdminProfile->GetKey(), $this->sPassword, $oPerson->GetKey());
+ }
+ }
+
/**
* @covers \ormCaseLog::GetEntryCount()
* @throws \ArchivedObjectException
@@ -38,4 +64,141 @@ class ormCaseLogTest extends ItopDataTestCase
$oLog->AddLogEntry('First entry');
$this->assertEquals($oLog->GetEntryCount(), 1, 'Should be 1 entry, returned '.$oLog->GetEntryCount());
}
+
+ public function testConstructorWithoutRebuild(){
+ $oOrmCaseLogService = $this->createMock(\ormCaseLogService::class);
+ $sLog = "aaaaa";
+ $aInitialIndex = ['a' => 'b'];
+
+ $oOrmCaseLogService->expects($this->exactly(1))
+ ->method('Rebuild')
+ ->with($sLog, $aInitialIndex)
+ ->willReturn(null);
+
+ $oLog = new ormCaseLog($sLog, $aInitialIndex, $oOrmCaseLogService);
+ $this->assertEquals($aInitialIndex, $oLog->GetIndex());
+ $this->assertEquals($sLog, $oLog->GetText());
+ }
+
+ public function testConstructorWithRebuild(){
+ $oOrmCaseLogService = $this->createMock(\ormCaseLogService::class);
+ $sLog = "aaaaa";
+ $sRebuiltLog = "bbbb";
+ $aInitialIndex = ['a' => 'b'];
+ $aRebuiltIndex = ['c' => 'd'];
+
+ $oOrmCaseLogService->expects($this->exactly(1))
+ ->method('Rebuild')
+ ->with($sLog, $aInitialIndex)
+ ->willReturn(new ormCaseLog($sRebuiltLog, $aRebuiltIndex));
+
+ $oLog = new ormCaseLog($sLog, $aInitialIndex, $oOrmCaseLogService);
+ $this->assertEquals($aRebuiltIndex, $oLog->GetIndex());
+ $this->assertEquals($sRebuiltLog, $oLog->GetText());
+ }
+
+ public function AddLogEntryProvider(){
+ return [
+ 'AddLogEntry' => [
+ 'bTestAddLogEntry' => 'true'
+ ],
+ 'AddLogEntryFromJSON' => [
+ 'bTestAddLogEntry' => 'false'
+ ]
+ ];
+ }
+
+
+ /**
+ * @dataProvider AddLogEntryProvider
+ */
+ public function testAddLogEntryNoRebuild($bTestAddLogEntry=true){
+ $_SESSION = array();
+ $this->assertTrue(\UserRights::Login($this->sLogin));
+
+ $sLog = "aaaaa";
+ $sDate = date(\AttributeDateTime::GetInternalFormat());
+ $iUserId = \UserRights::GetUserId();
+ $sOnBehalfOf = \UserRights::GetUserFriendlyName();
+ $sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
+ $sExpectedLog = $sSeparator."$sLog
";
+
+ $oOrmCaseLogService = $this->createMock(\ormCaseLogService::class);
+ $aExpectedIndex = [
+ [
+ 'user_name' => $sOnBehalfOf,
+ 'user_id' => $iUserId,
+ 'date' => time(),
+ 'text_length' => 12,
+ 'separator_length' => strlen($sSeparator),
+ 'format' => 'html',
+ ]
+ ];
+
+ $oOrmCaseLogService->expects($this->exactly(2))
+ ->method('Rebuild')
+ ->withConsecutive(['', []], [$sExpectedLog, $aExpectedIndex])
+ ->willReturnOnConsecutiveCalls(null, null);
+
+ $oLog = new ormCaseLog('', [], $oOrmCaseLogService);
+ if ($bTestAddLogEntry){
+ $oLog->AddLogEntry($sLog);
+ } else {
+ $sJson = json_encode(
+ [
+ 'user_login' => 'gabuzmeu',
+ 'message' => $sLog,
+ ]
+ );
+ $oJson = json_decode($sJson);
+ $oLog->AddLogEntryFromJSON($oJson, false);
+ }
+
+ $this->assertEquals($sExpectedLog, $oLog->GetText());
+ $this->assertEquals($aExpectedIndex, $oLog->GetIndex());
+ }
+
+ /**
+ * @dataProvider AddLogEntryProvider
+ */
+ public function testAddLogEntry($bTestAddLogEntry=true){
+ $_SESSION = array();
+ $this->assertTrue(\UserRights::Login($this->sLogin));
+
+ $sLog = "aaaaa";
+ $sDate = date(\AttributeDateTime::GetInternalFormat());
+ $iUserId = \UserRights::GetUserId();
+ $sOnBehalfOf = \UserRights::GetUserFriendlyName();
+ $sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
+ $sExpectedLog = $sSeparator."$sLog
";
+
+ $aRebuiltIndex = ['c' => 'd'];
+ $sRebuiltLog = "bbbb";
+
+ $oOrmCaseLogService = $this->createMock(\ormCaseLogService::class);
+
+ $aExpectedIndex = [
+ [
+ 'user_name' => $sOnBehalfOf,
+ 'user_id' => $iUserId,
+ 'date' => time(),
+ 'text_length' => 12,
+ 'separator_length' => strlen($sSeparator),
+ 'format' => 'html',
+ ]
+ ];
+ $oOrmCaseLogService->expects($this->exactly(2))
+ ->method('Rebuild')
+ ->withConsecutive(['', []], [$sExpectedLog, $aExpectedIndex])
+ ->willReturnOnConsecutiveCalls(
+ null,
+ new ormCaseLog($sRebuiltLog, $aRebuiltIndex)
+ );
+
+ $oLog = new ormCaseLog('', [], $oOrmCaseLogService);
+ $oLog->AddLogEntry($sLog);
+
+ $this->assertEquals($sRebuiltLog, $oLog->GetText());
+ $this->assertEquals($aRebuiltIndex, $oLog->GetIndex());
+ }
}