Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

N°6275 - Propose CaseLog extensibility API to manage history #493

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
33 changes: 33 additions & 0 deletions application/applicationextension.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -2246,3 +2246,36 @@ interface iModuleExtension
*/
public function __construct();
}

/**
* Interface to manipulate ormCaseLog objects after initialization/edition
*
* @api
* @since 3.1.0 N°6275
*/
interface iOrmCaseLogExtension
{
/**
* 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;
}

/**
* Inherit from iOrmCaseLogExtension to manipulate ormCaseLog after its initialization/modifications
*
* @api
* @since 3.1.0 N°6275
*/
abstract class AbstractOrmCaseLogExtension implements iOrmCaseLogExtension
{
public function Rebuild(&$sLog, &$aIndex) : bool
{
return false;
}
}
8 changes: 8 additions & 0 deletions core/config.class.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -1523,6 +1523,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'ormcaselog_extension_classes' => [
'type' => 'array',
'description' => 'Sorted list of enabled iOrmCaseLogExtension implementation classes',
'default' => [],
'value' => [],
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'regenerate_session_id_enabled' => [
'type' => 'bool',
'description' => 'If true then session id will be regenerated on each login, to prevent session fixation.',
Expand Down
2 changes: 1 addition & 1 deletion core/dbobject.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -1983,7 +1983,7 @@ public function CheckValue($sAttCode, $value = null)
/** @var \AttributeExternalKey $oAtt */
$sTargetClass = $oAtt->GetTargetClass();
if (false === MetaModel::IsObjectInDB($sTargetClass, $toCheck)) {
return "Target object not found (".$sTargetClass.".::".$toCheck.")";
return "Target object not found ({$sTargetClass}::{$toCheck})";
}
}
if ($oAtt->IsHierarchicalKey())
Expand Down
80 changes: 52 additions & 28 deletions core/ormcaselog.class.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -25,10 +25,11 @@
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
*/
Expand All @@ -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)
Expand All @@ -72,7 +76,7 @@ public function GetText($bConvertToPlainText = false)
return $this->m_sLog;
}
}

public static function FromJSON($oJson)
{
if (!isset($oJson->items))
Expand All @@ -88,8 +92,8 @@ public static function FromJSON($oJson)
}

/**
* 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
Expand Down Expand Up @@ -199,9 +203,9 @@ public function GetAsPlainText()
$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;
Expand All @@ -227,15 +231,15 @@ public function GetEntryCount(): int
{
return count($this->m_aIndex);
}

public function ClearModifiedFlag()
{
$this->m_bModified = false;
}

/**
* Produces an HTML representation, aimed at being used within an email
*/
*/
public function GetAsEmailHtml()
{
$sStyleCaseLogHeader = '';
Expand Down Expand Up @@ -312,10 +316,10 @@ public function GetAsEmailHtml()
$sHtml .= '</td></tr></table>';
return $sHtml;
}

/**
* Produces an HTML representation, aimed at being used to produce a PDF with TCPDF (no table)
*/
*/
public function GetAsSimpleHtml($aTransfoHandler = null)
{
$sStyleCaseLogEntry = '';
Expand All @@ -338,7 +342,7 @@ public function GetAsSimpleHtml($aTransfoHandler = null)
$sTextEntry = call_user_func($aTransfoHandler, $sTextEntry, true /* wiki "links" only */);
}
$sTextEntry = InlineImage::FixUrls($sTextEntry);
}
}
$iPos += $aIndex[$index]['text_length'];

$sEntry = '<li>';
Expand Down Expand Up @@ -396,7 +400,7 @@ public function GetAsSimpleHtml($aTransfoHandler = null)

/**
* 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');
Expand Down Expand Up @@ -519,7 +523,7 @@ public function GetAsHTML(WebPage $oP = null, $bEditMode = false, $aTransfoHandl
}
}
}

return $sHtml;
}

Expand All @@ -534,14 +538,17 @@ public function GetAsHTML(WebPage $oP = null, $bEditMode = false, $aTransfoHandl
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \OQLException
*
*
* @since 3.0.0 New $iOnBehalfOfId parameter
* @since 3.0.0 May throw \ArchivedObjectException exception
*/
public function AddLogEntry(string $sText, $sOnBehalfOf = '', $iOnBehalfOfId = null)
{
$sText = HTMLSanitizer::Sanitize($sText);
//date/time ops moved here for test stability
$iNow = time();
$sDate = date(AttributeDateTime::GetInternalFormat());

$sText = HTMLSanitizer::Sanitize($sText);
if ($sOnBehalfOf == '' && $iOnBehalfOfId === null) {
$sOnBehalfOf = UserRights::GetUserFriendlyName();
$iUserId = UserRights::GetUserId();
Expand Down Expand Up @@ -580,16 +587,20 @@ public function AddLogEntry(string $sText, $sOnBehalfOf = '', $iOnBehalfOfId = n
$this->m_aIndex[] = array(
'user_name' => $sOnBehalfOf,
'user_id' => $iUserId,
'date' => time(),
'date' => $iNow,
'text_length' => $iTextlength,
'separator_length' => $iSepLength,
'format' => static::ENUM_FORMAT_HTML,
);
$this->RebuildIndex();
$this->m_bModified = true;
}

public function AddLogEntryFromJSON($oJson, $bCheckUserId = true)
{
//date/time ops moved here for test stability
$iNow = time();

if (isset($oJson->user_id))
{
if (!UserRights::IsAdministrator())
Expand Down Expand Up @@ -620,15 +631,15 @@ public function AddLogEntryFromJSON($oJson, $bCheckUserId = true)
$iUserId = UserRights::GetUserId();
$sOnBehalfOf = UserRights::GetUserFriendlyName();
}

if (isset($oJson->date))
{
$oDate = new DateTime($oJson->date);
$iDate = (int) $oDate->format('U');
}
else
{
$iDate = time();
$iDate = $iNow;
}
if (isset($oJson->format))
{
Expand All @@ -653,14 +664,14 @@ public function AddLogEntryFromJSON($oJson, $bCheckUserId = true)
$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' => $iNow,
'text_length' => $iTextlength,
'separator_length' => $iSepLength,
'format' => $sFormat,
);

$this->RebuildIndex();
$this->m_bModified = true;
}

Expand Down Expand Up @@ -716,7 +727,7 @@ public function GetLatestEntryIndex()
$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
Expand All @@ -736,4 +747,17 @@ public function GetEntryAt($iIndex)
$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;
$this->m_bModified = true;
}
}
}
67 changes: 67 additions & 0 deletions core/ormcaselogservice.inc.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php
/**
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

/**
* Service dedicated to ormCaseLog rebuild
*
* @since 3.1.0 N°6275
*/
class ormCaseLogService
{
/**
* Array of "providers" of welcome popup messages
* @var iOrmCaseLogExtension[]
*/
protected $aOrmCaseLogExtensions = null;

public function __construct(array $aOrmCaseLogExtensions=null)
{
$this->aOrmCaseLogExtensions = $aOrmCaseLogExtensions;
}

protected function LoadCaseLogExtensions($aClassesForInterfaceOrmCaseLog=null) : array
{
if ($this->aOrmCaseLogExtensions !== null) return $this->aOrmCaseLogExtensions;

if ($aClassesForInterfaceOrmCaseLog === null) {
$aClassesForInterfaceOrmCaseLog = \utils::GetClassesForInterface(iOrmCaseLogExtension::class, '',
array('[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]', '[\\\\/]tests[\\\\/]'));
}

$aConfiguredOrmCaseLogExtensionClasses = MetaModel::GetConfig()->Get('ormcaselog_extension_classes');
$this->aOrmCaseLogExtensions = [];
foreach ($aConfiguredOrmCaseLogExtensionClasses as $sConfiguredOrmCaseLogExtensionClass) {
if (in_array($sConfiguredOrmCaseLogExtensionClass, $aClassesForInterfaceOrmCaseLog)){
$this->aOrmCaseLogExtensions[] = new $sConfiguredOrmCaseLogExtensionClass();
}
}

return $this->aOrmCaseLogExtensions;
}

/**
* @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->aOrmCaseLogExtensions as $oOrmCaseLogExtension){
/** var iOrmCaseLogExtension $oOrmCaseLogExtension */
$bTouched = $bTouched || $oOrmCaseLogExtension->Rebuild($sLog, $aIndex);
}

if ($bTouched){
return new \ormCaseLog($sLog, $aIndex);
}

return null;
}
}
1 change: 1 addition & 0 deletions lib/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -2970,6 +2970,7 @@
'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',
Expand Down
1 change: 1 addition & 0 deletions lib/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading