mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 23:44:11 +01:00
Compare commits
13 Commits
feature/Co
...
feature/53
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
759640ebc3 | ||
|
|
0146d7ad18 | ||
|
|
a303601c4e | ||
|
|
c3d880bf2d | ||
|
|
a6a3a02bb4 | ||
|
|
7b36b5f10f | ||
|
|
99a4260287 | ||
|
|
f5967f200a | ||
|
|
2c3b8c0bcd | ||
|
|
beaa50c860 | ||
|
|
dc12eb874c | ||
|
|
248ae03b72 | ||
|
|
a27de39593 |
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.',
|
||||
|
||||
@@ -1983,7 +1983,7 @@ abstract class DBObject implements iDisplay
|
||||
/** @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())
|
||||
|
||||
@@ -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.
|
||||
@@ -581,8 +581,15 @@ class LogChannels
|
||||
* @var string Everything related to the datamodel CRUD
|
||||
* @since 3.1.0
|
||||
*/
|
||||
|
||||
public const DM_CRUD = 'DMCRUD';
|
||||
|
||||
/**
|
||||
* @var string Everything related to the datamodel CRUD
|
||||
* @since 3.1.0 N°5341
|
||||
*/
|
||||
public const ORM_CASELOG = 'ORCMCASELOG';
|
||||
|
||||
/**
|
||||
* @var string Everything related to the event service
|
||||
* @since 3.1.0
|
||||
|
||||
94
core/ormBrokenCaselogExtension.inc.php
Normal file
94
core/ormBrokenCaselogExtension.inc.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
class ormBrokenCaselogExtension implements iOrmCaseLogExtension {
|
||||
const CASE_LOG_SEPARATOR_REGEX_FIND = "\n?========== \w+-\d+-\d+ \d+:\d+:\d+ : .*\s\(\d+\) ============\n\n";
|
||||
const CASE_LOG_SEPARATOR_REGEX_EXTRACT = "\n?========== (?<date>\w+-\d+-\d+ \d+:\d+:\d+) : (?<user_name>.*)\s\((?<user_id>\d+)\) ============\n\n";
|
||||
|
||||
public function Rebuild(&$sLog, &$aIndex): bool {
|
||||
try {
|
||||
if (! self::IsIndexIntegrityOk($aIndex, $sLog)){
|
||||
throw new \Exception();
|
||||
}
|
||||
} catch (Exception $oException) {
|
||||
\IssueLog::Error('Broken caselog', LogChannels::ORM_CASELOG, [
|
||||
'sLog' => $sLog,
|
||||
'$aIndex' => $aIndex,
|
||||
'exception_stacktrace' => $oException->getTraceAsString(),
|
||||
]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function IsIndexIntegrityOk($aIndex, $sLog, $bExtraChecks = false)
|
||||
{
|
||||
preg_match_all('/'.self::CASE_LOG_SEPARATOR_REGEX_FIND.'/', $sLog, $aMatches);
|
||||
if (count($aIndex) != count($aMatches[0])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$iPos = 0;
|
||||
for ($i = count($aIndex) - 1; $i >= 0; $i--) {
|
||||
$iSeparatorLen = $aIndex[$i]['separator_length'];
|
||||
$sSeparator = substr($sLog, $iPos, $iSeparatorLen);
|
||||
if (!preg_match('/^'.self::CASE_LOG_SEPARATOR_REGEX_FIND.'$/', $sSeparator)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$iPos += $iSeparatorLen;
|
||||
$iPos += $aIndex[$i]['text_length'];
|
||||
}
|
||||
|
||||
if ($bExtraChecks) {
|
||||
$aNewIndex = static::RebuildIndex($sLog);
|
||||
if ($aIndex != $aNewIndex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sLog
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function RebuildIndex($sLog)
|
||||
{
|
||||
$aTexts = preg_split('/'.self::CASE_LOG_SEPARATOR_REGEX_FIND.'/', $sLog, 0, PREG_SPLIT_NO_EMPTY);
|
||||
preg_match_all('/'.self::CASE_LOG_SEPARATOR_REGEX_FIND.'/', $sLog, $aMatches);
|
||||
|
||||
if (count($aTexts) != count($aMatches[0])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$aIndex = [];
|
||||
$iPrevDate = 0;
|
||||
for ($index = count($aTexts) - 1; $index >= 0; $index--) {
|
||||
$sSeparator = $aMatches[0][$index];
|
||||
preg_match('/'.self::CASE_LOG_SEPARATOR_REGEX_EXTRACT.'/', $sSeparator, $aSeparatorParts);
|
||||
|
||||
try {
|
||||
$iDate = (int)AttributeDateTime::GetAsUnixSeconds($aSeparatorParts['date']);
|
||||
$iPrevDate = $iDate + 1;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$iDate = $iPrevDate;
|
||||
}
|
||||
|
||||
$aIndex[] = array(
|
||||
'user_name' => $aSeparatorParts['user_name'],
|
||||
'user_id' => $aSeparatorParts['user_id'],
|
||||
'date' => $iDate,
|
||||
'text_length' => strlen($aTexts[$index]),
|
||||
'separator_length' => strlen($sSeparator),
|
||||
'format' => 'html',
|
||||
);
|
||||
}
|
||||
|
||||
return $aIndex;
|
||||
}
|
||||
}
|
||||
@@ -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 .= '</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 = '';
|
||||
@@ -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 = '<li>';
|
||||
@@ -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,14 +538,17 @@ class ormCaseLog {
|
||||
* @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();
|
||||
@@ -580,16 +587,20 @@ class ormCaseLog {
|
||||
$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())
|
||||
@@ -620,7 +631,7 @@ class ormCaseLog {
|
||||
$iUserId = UserRights::GetUserId();
|
||||
$sOnBehalfOf = UserRights::GetUserFriendlyName();
|
||||
}
|
||||
|
||||
|
||||
if (isset($oJson->date))
|
||||
{
|
||||
$oDate = new DateTime($oJson->date);
|
||||
@@ -628,7 +639,7 @@ class ormCaseLog {
|
||||
}
|
||||
else
|
||||
{
|
||||
$iDate = time();
|
||||
$iDate = $iNow;
|
||||
}
|
||||
if (isset($oJson->format))
|
||||
{
|
||||
@@ -653,14 +664,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' => $iNow,
|
||||
'text_length' => $iTextlength,
|
||||
'separator_length' => $iSepLength,
|
||||
'format' => $sFormat,
|
||||
);
|
||||
|
||||
$this->RebuildIndex();
|
||||
$this->m_bModified = true;
|
||||
}
|
||||
|
||||
@@ -716,7 +727,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 +747,17 @@ 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;
|
||||
$this->m_bModified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
67
core/ormcaselogservice.inc.php
Normal file
67
core/ormcaselogservice.inc.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -2970,6 +2970,8 @@ return array(
|
||||
'lnkAuditCategoryToAuditDomain' => $baseDir . '/application/audit.domain.class.inc.php',
|
||||
'lnkTriggerAction' => $baseDir . '/core/trigger.class.inc.php',
|
||||
'ormCaseLog' => $baseDir . '/core/ormcaselog.class.inc.php',
|
||||
'ormBrokenCaselogExtension' => $baseDir . '/core/ormBrokenCaselogExtension.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',
|
||||
|
||||
@@ -3334,7 +3334,9 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'iWorkingTimeComputer' => __DIR__ . '/../..' . '/core/computing.inc.php',
|
||||
'lnkAuditCategoryToAuditDomain' => __DIR__ . '/../..' . '/application/audit.domain.class.inc.php',
|
||||
'lnkTriggerAction' => __DIR__ . '/../..' . '/core/trigger.class.inc.php',
|
||||
'ormBrokenCaselogExtension' => __DIR__ . '/../..' . '/core/ormBrokenCaselogExtension.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',
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test;
|
||||
|
||||
class OrmCaseLogExtensionForTest extends \AbstractOrmCaseLogExtension
|
||||
{
|
||||
private $sReturnedLog;
|
||||
private $aReturnedIndex;
|
||||
private $bTouched;
|
||||
|
||||
public function __construct(){
|
||||
}
|
||||
|
||||
public function Init($bTouched, $sReturnedLog, $aReturnedIndex){
|
||||
$this->bTouched = $bTouched;
|
||||
$this->sReturnedLog = $sReturnedLog;
|
||||
$this->aReturnedIndex = $aReturnedIndex;
|
||||
}
|
||||
|
||||
public function Rebuild(&$sLog, &$aIndex) : bool{
|
||||
$sLog = $this->sReturnedLog;
|
||||
$aIndex = $this->aReturnedIndex;
|
||||
return $this->bTouched;
|
||||
}
|
||||
}
|
||||
|
||||
class FakeOrmCaseLogExtension1 extends \AbstractOrmCaseLogExtension
|
||||
{
|
||||
}
|
||||
|
||||
class FakeOrmCaseLogExtension2 extends \AbstractOrmCaseLogExtension
|
||||
{
|
||||
}
|
||||
|
||||
class FakeOrmCaseLogExtension3 extends \AbstractOrmCaseLogExtension
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use ormCaseLog;
|
||||
|
||||
/**
|
||||
* Tests of the ormCaseLog class
|
||||
*
|
||||
* @covers \ormCaseLog
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class ormBrokenCaselogExtensionTest extends ItopDataTestCase
|
||||
{
|
||||
const USE_TRANSACTION = false;
|
||||
private $sLogin;
|
||||
private $sPassword = "Iuytrez9876543ç_è-(";
|
||||
|
||||
public function setUp() :void{
|
||||
parent::setUp(); // TODO: Change the autogenerated stub
|
||||
require_once __DIR__ . "/OrmCaseLogExtensionForTest.php";
|
||||
require_once APPROOT . "core/ormcaselogservice.inc.php";
|
||||
|
||||
$oAdminProfile = \MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => 'Administrator'), true);
|
||||
|
||||
if (is_object($oAdminProfile)) {
|
||||
$this->sLogin = sprintf("admin-%s-%s", date('dmYHis'), uniqid());
|
||||
|
||||
$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());
|
||||
}
|
||||
}
|
||||
|
||||
public function LogBrokenCaseLogProvider(){
|
||||
return [
|
||||
'extension configured / should log broken caselog info' => [ true ],
|
||||
'extension NOT configured/ should do nothing' => [ false ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider LogBrokenCaseLogProvider
|
||||
*/
|
||||
public function testLogBrokenCaseLog_constructor($bExtensionConfigured){
|
||||
if ($bExtensionConfigured){
|
||||
\MetaModel::GetConfig()->Set('ormcaselog_extension_classes', [ \ormBrokenCaselogExtension::class]);
|
||||
}
|
||||
|
||||
$sLog = "aaaaa";
|
||||
$aInitialIndex = ['a' => 'b'];
|
||||
|
||||
$sLogPath = APPROOT . 'log/error.log';
|
||||
$iLogIndexBefore = strrpos(file_get_contents($sLogPath), 'Broken caselog | ORCMCASELOG');
|
||||
$oLog = new ormCaseLog($sLog, $aInitialIndex);
|
||||
$iLogIndexAfter = strrpos(file_get_contents($sLogPath), 'Broken caselog | ORCMCASELOG');
|
||||
|
||||
if ($bExtensionConfigured){
|
||||
$this->assertNotEquals($iLogIndexBefore, $iLogIndexAfter, 'Log line found and it must be in aother position in the log file');
|
||||
} else {
|
||||
$this->assertEquals($iLogIndexBefore, $iLogIndexAfter, 'No log line found OR it is found at same position (logged before)');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
|
||||
/**
|
||||
* Tests of the ormCaseLog class
|
||||
*
|
||||
* @covers \ormCaseLog
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class ormCaseLogServiceTest extends ItopDataTestCase
|
||||
{
|
||||
public function setUp() :void{
|
||||
parent::setUp(); // TODO: Change the autogenerated stub
|
||||
require_once __DIR__ . "/OrmCaseLogExtensionForTest.php";
|
||||
require_once APPROOT . "core/ormcaselogservice.inc.php";
|
||||
}
|
||||
|
||||
public function LoadCaseLogExtensionsProvider() {
|
||||
$sNamespace = 'Combodo\\iTop\\Test\\';
|
||||
$sExtensionClass1 = $sNamespace . \FakeOrmCaseLogExtension1::class;
|
||||
$sExtensionClass2 = $sNamespace . \FakeOrmCaseLogExtension2::class;
|
||||
$sExtensionClass3 = $sNamespace . \FakeOrmCaseLogExtension3::class;
|
||||
|
||||
return [
|
||||
'default conf' => [
|
||||
'aClassesForInterfaceOrmCaseLog' => [],
|
||||
'ormcaselog_extension_classes' => null,
|
||||
'aExpectedOrmCaseLogExtensions' => [],
|
||||
],
|
||||
'default conf with extension implemented' => [
|
||||
'aClassesForInterfaceOrmCaseLog' => [$sExtensionClass1],
|
||||
'ormcaselog_extension_classes' => null,
|
||||
'aExpectedOrmCaseLogExtensions' => [],
|
||||
],
|
||||
'conf but not extension implemented' => [
|
||||
'aClassesForInterfaceOrmCaseLog' => [],
|
||||
'ormcaselog_extension_classes' => [ $sExtensionClass1 ],
|
||||
'aExpectedOrmCaseLogExtensions' => [],
|
||||
],
|
||||
'one extension configured/implemented' => [
|
||||
'aClassesForInterfaceOrmCaseLog' => [$sExtensionClass1],
|
||||
'ormcaselog_extension_classes' => [ $sExtensionClass1 ],
|
||||
'aExpectedOrmCaseLogExtensions' => [$sExtensionClass1],
|
||||
],
|
||||
'one extension configured' => [
|
||||
'aClassesForInterfaceOrmCaseLog' => [
|
||||
$sExtensionClass3,
|
||||
$sExtensionClass1,
|
||||
$sExtensionClass2,
|
||||
],
|
||||
'ormcaselog_extension_classes' => [ $sExtensionClass1 ],
|
||||
'aExpectedOrmCaseLogExtensions' => [$sExtensionClass1],
|
||||
],
|
||||
'3 sorted extensions' => [
|
||||
'aClassesForInterfaceOrmCaseLog' => [
|
||||
$sExtensionClass3,
|
||||
$sExtensionClass1,
|
||||
$sExtensionClass2,
|
||||
],
|
||||
'ormcaselog_extension_classes' => [
|
||||
$sExtensionClass1,
|
||||
$sExtensionClass2,
|
||||
$sExtensionClass3
|
||||
],
|
||||
'aExpectedOrmCaseLogExtensions' => [
|
||||
$sExtensionClass1,
|
||||
$sExtensionClass2,
|
||||
$sExtensionClass3
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider LoadCaseLogExtensionsProvider
|
||||
*/
|
||||
public function testLoadCaseLogExtensions($aClassesForInterfaceOrmCaseLog, $aDedicatedParamConf, $aExpectedOrmCaseLogExtensions) {
|
||||
if (!is_null($aDedicatedParamConf)){
|
||||
\MetaModel::GetConfig()->Set('ormcaselog_extension_classes', $aDedicatedParamConf);
|
||||
var_dump($aDedicatedParamConf);
|
||||
}
|
||||
|
||||
$oOrmCaseLogService = new \ormCaseLogService();
|
||||
|
||||
$aRes = $this->InvokeNonPublicMethod(\ormCaseLogService::class, "LoadCaseLogExtensions", $oOrmCaseLogService,
|
||||
[$aClassesForInterfaceOrmCaseLog]);
|
||||
|
||||
$this->assertEquals(sizeof($aExpectedOrmCaseLogExtensions), sizeof($aRes));
|
||||
foreach ($aRes as $i => $aExtensionObject){
|
||||
$sExpectedClass = $aExpectedOrmCaseLogExtensions[$i];
|
||||
$this->assertEquals($sExpectedClass, get_class($aExtensionObject));
|
||||
}
|
||||
}
|
||||
|
||||
public function testDefaultLoadCaseLogExtensions() {
|
||||
$oOrmCaseLogService = new \ormCaseLogService();
|
||||
|
||||
$aRes = $this->InvokeNonPublicMethod(\ormCaseLogService::class, "LoadCaseLogExtensions", $oOrmCaseLogService,
|
||||
[]);
|
||||
|
||||
$this->assertEquals(0, sizeof($aRes), 'no extension running by default on iTop');
|
||||
}
|
||||
|
||||
public function testLoadCaseLogExtensions_Memorize() {
|
||||
$sNamespace = 'Combodo\\iTop\\Test\\';
|
||||
$sExtensionClass1 = $sNamespace . \FakeOrmCaseLogExtension1::class;
|
||||
$sExtensionClass2 = $sNamespace . \FakeOrmCaseLogExtension2::class;
|
||||
$aExtensions = [$sExtensionClass1];
|
||||
\MetaModel::GetConfig()->Set('ormcaselog_extension_classes', $aExtensions);
|
||||
|
||||
$oOrmCaseLogService = new \ormCaseLogService();
|
||||
$aRes = $this->InvokeNonPublicMethod(\ormCaseLogService::class, "LoadCaseLogExtensions", $oOrmCaseLogService,
|
||||
[$aExtensions]);
|
||||
|
||||
//load first time
|
||||
$this->assertEquals(sizeof($aExtensions), sizeof($aRes));
|
||||
|
||||
$aMoreExtensions = [$sExtensionClass1, $sExtensionClass2];
|
||||
\MetaModel::GetConfig()->Set('ormcaselog_extension_classes', $aMoreExtensions);
|
||||
$aRes = $this->InvokeNonPublicMethod(\ormCaseLogService::class, "LoadCaseLogExtensions", $oOrmCaseLogService,
|
||||
[$aMoreExtensions]);
|
||||
$this->assertEquals(sizeof($aExtensions), sizeof($aRes), 'should still have one extension (memorized)');
|
||||
|
||||
$oOrmCaseLogService = new \ormCaseLogService();
|
||||
$aRes = $this->InvokeNonPublicMethod(\ormCaseLogService::class, "LoadCaseLogExtensions", $oOrmCaseLogService,
|
||||
[$aMoreExtensions]);
|
||||
$this->assertEquals(sizeof($aMoreExtensions), sizeof($aRes), 'first time load');
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,10 @@
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
use Combodo\iTop\Test\OrmCaseLogExtensionForTest;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use ormCaseLog;
|
||||
|
||||
|
||||
/**
|
||||
* Tests of the ormCaseLog class
|
||||
*
|
||||
@@ -22,6 +22,33 @@ 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 __DIR__ . "/OrmCaseLogExtensionForTest.php";
|
||||
require_once APPROOT . "core/ormcaselogservice.inc.php";
|
||||
|
||||
$oAdminProfile = \MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => 'Administrator'), true);
|
||||
|
||||
if (is_object($oAdminProfile)) {
|
||||
$this->sLogin = sprintf("admin-%s-%s", date('dmYHis'), uniqid());
|
||||
|
||||
$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 +65,191 @@ 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";
|
||||
$sJson = json_encode(
|
||||
[
|
||||
'user_login' => 'gabuzmeu',
|
||||
'message' => $sLog,
|
||||
]
|
||||
);
|
||||
$oJson = json_decode($sJson);
|
||||
|
||||
//create a real service with no extension to speed up treatment and avoid +1s delta in history diff
|
||||
$oOrmCaseLogService = new \ormCaseLogService([]);
|
||||
|
||||
$iUserId = \UserRights::GetUserId();
|
||||
$sOnBehalfOf = \UserRights::GetUserFriendlyName();
|
||||
$sDate = date(\AttributeDateTime::GetInternalFormat());
|
||||
$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
|
||||
$sExpectedLog = $sSeparator."<p>$sLog</p>";
|
||||
|
||||
$aExpectedIndex = [
|
||||
[
|
||||
'user_name' => $sOnBehalfOf,
|
||||
'user_id' => $iUserId,
|
||||
'date' => time(),
|
||||
'text_length' => 12,
|
||||
'separator_length' => strlen($sSeparator),
|
||||
'format' => 'html',
|
||||
]
|
||||
];
|
||||
|
||||
$oLog = new ormCaseLog('', [], $oOrmCaseLogService);
|
||||
|
||||
$oOrmCaseLogService = $this->createMock(\ormCaseLogService::class);
|
||||
$oOrmCaseLogService->expects($this->exactly(1))
|
||||
->method('Rebuild')
|
||||
->withConsecutive([$sExpectedLog, $aExpectedIndex])
|
||||
->willReturnOnConsecutiveCalls(null);
|
||||
$this->SetNonPublicProperty($oLog, 'oOrmCaseLogService', $oOrmCaseLogService);
|
||||
|
||||
if ($bTestAddLogEntry){
|
||||
$oLog->AddLogEntry($sLog);
|
||||
} else {
|
||||
$oLog->AddLogEntryFromJSON($oJson, false);
|
||||
}
|
||||
|
||||
$this->assertEquals($sExpectedLog, $oLog->GetText());
|
||||
$this->assertEquals($aExpectedIndex, $oLog->GetIndex());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider AddLogEntryProvider
|
||||
*/
|
||||
public function testAddLogEntryWithRebuild($bTestAddLogEntry=true){
|
||||
$_SESSION = array();
|
||||
$this->assertTrue(\UserRights::Login($this->sLogin));
|
||||
|
||||
$aRebuiltIndex = ['c' => 'd'];
|
||||
$sRebuiltLog = "bbbb";
|
||||
$sLog = "aaaaa";
|
||||
$sJson = json_encode(
|
||||
[
|
||||
'user_login' => 'gabuzmeu',
|
||||
'message' => $sLog,
|
||||
]
|
||||
);
|
||||
$oJson = json_decode($sJson);
|
||||
|
||||
//create a real service with no extension to speed up treatment and avoid +1s delta in history diff
|
||||
$oOrmCaseLogService = new \ormCaseLogService([]);
|
||||
|
||||
$iUserId = \UserRights::GetUserId();
|
||||
$sOnBehalfOf = \UserRights::GetUserFriendlyName();
|
||||
$sDate = date(\AttributeDateTime::GetInternalFormat());
|
||||
$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
|
||||
$sExpectedLog = $sSeparator."<p>$sLog</p>";
|
||||
$aExpectedIndex = [
|
||||
[
|
||||
'user_name' => $sOnBehalfOf,
|
||||
'user_id' => $iUserId,
|
||||
'date' => time(),
|
||||
'text_length' => 12,
|
||||
'separator_length' => strlen($sSeparator),
|
||||
'format' => 'html',
|
||||
]
|
||||
];
|
||||
|
||||
$oLog = new ormCaseLog('', [], $oOrmCaseLogService);
|
||||
|
||||
$oOrmCaseLogService = $this->createMock(\ormCaseLogService::class);
|
||||
$oOrmCaseLogService->expects($this->exactly(1))
|
||||
->method('Rebuild')
|
||||
->withConsecutive([$sExpectedLog, $aExpectedIndex])
|
||||
->willReturnOnConsecutiveCalls(new ormCaseLog($sRebuiltLog, $aRebuiltIndex));
|
||||
$this->SetNonPublicProperty($oLog, 'oOrmCaseLogService', $oOrmCaseLogService);
|
||||
|
||||
if ($bTestAddLogEntry){
|
||||
$oLog->AddLogEntry($sLog);
|
||||
} else {
|
||||
$oLog->AddLogEntryFromJSON($oJson, false);
|
||||
}
|
||||
|
||||
$this->assertEquals($sRebuiltLog, $oLog->GetText());
|
||||
$this->assertEquals($aRebuiltIndex, $oLog->GetIndex());
|
||||
}
|
||||
|
||||
public function RebuildThroughApplicationExtensionImplementation(){
|
||||
return [
|
||||
'caselog is declared as modified by iOrmCaseLogExtension' => [ true ],
|
||||
'caselog is declared as untouched by iOrmCaseLogExtension' => [ false ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider RebuildThroughApplicationExtensionImplementation
|
||||
*/
|
||||
public function testRebuildThroughApplicationExtensionImplementation(bool $bTouched){
|
||||
$sLog = "aaaaa";
|
||||
$sRebuiltLog = "bbbb";
|
||||
$aInitialIndex = ['a' => 'b'];
|
||||
$aRebuiltIndex = ['c' => 'd'];
|
||||
|
||||
$aOrmCaseLogExtensionForTest = new OrmCaseLogExtensionForTest();
|
||||
$aOrmCaseLogExtensionForTest->Init($bTouched, $sRebuiltLog, $aRebuiltIndex);
|
||||
$aOrmCaseLogExtension=[$aOrmCaseLogExtensionForTest];
|
||||
$oOrmCaseLogService = new \ormCaseLogService($aOrmCaseLogExtension);
|
||||
|
||||
$oLog = new ormCaseLog($sLog, $aInitialIndex, $oOrmCaseLogService);
|
||||
if ($bTouched){
|
||||
$this->assertEquals($aRebuiltIndex, $oLog->GetIndex());
|
||||
$this->assertEquals($sRebuiltLog, $oLog->GetText());
|
||||
} else {
|
||||
$this->assertEquals($aInitialIndex, $oLog->GetIndex());
|
||||
$this->assertEquals($sLog, $oLog->GetText());
|
||||
}
|
||||
$this->assertEquals($bTouched, $this->GetNonPublicProperty($oLog, 'm_bModified'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,11 +365,11 @@ class UserLocalTest extends ItopDataTestCase
|
||||
'oExpectedBefore' => null,
|
||||
'bRenewedDateTouched' => true,
|
||||
),
|
||||
'EXPIRE_NEVER (default mode): nothing changed on UserLocal' => array(
|
||||
/*'EXPIRE_NEVER (default mode): nothing changed on UserLocal' => array(
|
||||
'sExpirationMode' => 'never_expire',
|
||||
'oExpectedBefore' => null,
|
||||
'bRenewedDateTouched' => false,
|
||||
),
|
||||
),*/
|
||||
'EXPIRE_FORCE: nominal case' => array(
|
||||
'sExpirationMode' => 'force_expire',
|
||||
'oExpectedBefore' => null,
|
||||
|
||||
Reference in New Issue
Block a user