mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-24 02:58:43 +02:00
Compare commits
9 Commits
3.1.0
...
saas/manag
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc2f7e1592 | ||
|
|
a54b88c524 | ||
|
|
239c51bb53 | ||
|
|
bdf0b4daa9 | ||
|
|
7fdbb59c30 | ||
|
|
5acf38ac36 | ||
|
|
85f66f5e0c | ||
|
|
a5c980113b | ||
|
|
df1cb0b6e3 |
@@ -3,7 +3,7 @@
|
|||||||
//
|
//
|
||||||
// This file is part of iTop.
|
// 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
|
// 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
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
@@ -380,7 +380,7 @@ class CMDBSource
|
|||||||
public static function GetDBVendor()
|
public static function GetDBVendor()
|
||||||
{
|
{
|
||||||
$sDBVendor = static::ENUM_DB_VENDOR_MYSQL;
|
$sDBVendor = static::ENUM_DB_VENDOR_MYSQL;
|
||||||
|
|
||||||
$sVersionComment = static::GetServerVariable('version') . ' - ' . static::GetServerVariable('version_comment');
|
$sVersionComment = static::GetServerVariable('version') . ' - ' . static::GetServerVariable('version_comment');
|
||||||
if(preg_match('/mariadb/i', $sVersionComment) === 1)
|
if(preg_match('/mariadb/i', $sVersionComment) === 1)
|
||||||
{
|
{
|
||||||
@@ -390,7 +390,7 @@ class CMDBSource
|
|||||||
{
|
{
|
||||||
$sDBVendor = static::ENUM_DB_VENDOR_PERCONA;
|
$sDBVendor = static::ENUM_DB_VENDOR_PERCONA;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $sDBVendor;
|
return $sDBVendor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -664,7 +664,7 @@ class CMDBSource
|
|||||||
);
|
);
|
||||||
DeadLockLog::Info($sMessage, $iMySqlErrorNo, $aLogContext);
|
DeadLockLog::Info($sMessage, $iMySqlErrorNo, $aLogContext);
|
||||||
|
|
||||||
IssueLog::Error($sMessage, LogChannels::DEADLOCK, $e->getMessage());
|
IssueLog::Error($sMessage, LogChannels::DEADLOCK, [$e->getMessage()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -923,7 +923,7 @@ class CMDBSource
|
|||||||
{
|
{
|
||||||
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
|
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
|
||||||
}
|
}
|
||||||
|
|
||||||
while ($aRow = $oResult->fetch_array($iMode))
|
while ($aRow = $oResult->fetch_array($iMode))
|
||||||
{
|
{
|
||||||
$aData[] = $aRow;
|
$aData[] = $aRow;
|
||||||
@@ -1077,7 +1077,7 @@ class CMDBSource
|
|||||||
if (!array_key_exists($iKey, $aTableInfo["Fields"])) return false;
|
if (!array_key_exists($iKey, $aTableInfo["Fields"])) return false;
|
||||||
$aFieldData = $aTableInfo["Fields"][$iKey];
|
$aFieldData = $aTableInfo["Fields"][$iKey];
|
||||||
if (!array_key_exists("Key", $aFieldData)) return false;
|
if (!array_key_exists("Key", $aFieldData)) return false;
|
||||||
return ($aFieldData["Key"] == "PRI");
|
return ($aFieldData["Key"] == "PRI");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function IsAutoIncrement($sTable, $sField)
|
public static function IsAutoIncrement($sTable, $sField)
|
||||||
@@ -1088,7 +1088,7 @@ class CMDBSource
|
|||||||
$aFieldData = $aTableInfo["Fields"][$sField];
|
$aFieldData = $aTableInfo["Fields"][$sField];
|
||||||
if (!array_key_exists("Extra", $aFieldData)) return false;
|
if (!array_key_exists("Extra", $aFieldData)) return false;
|
||||||
//MyHelpers::debug_breakpoint($aFieldData);
|
//MyHelpers::debug_breakpoint($aFieldData);
|
||||||
return (strstr($aFieldData["Extra"], "auto_increment"));
|
return (strstr($aFieldData["Extra"], "auto_increment"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function IsField($sTable, $sField)
|
public static function IsField($sTable, $sField)
|
||||||
@@ -1355,13 +1355,13 @@ class CMDBSource
|
|||||||
public static function GetTableFieldsList($sTable)
|
public static function GetTableFieldsList($sTable)
|
||||||
{
|
{
|
||||||
assert(!empty($sTable));
|
assert(!empty($sTable));
|
||||||
|
|
||||||
$aTableInfo = self::GetTableInfo($sTable);
|
$aTableInfo = self::GetTableInfo($sTable);
|
||||||
if (empty($aTableInfo)) return array(); // #@# or an error ?
|
if (empty($aTableInfo)) return array(); // #@# or an error ?
|
||||||
|
|
||||||
return array_keys($aTableInfo["Fields"]);
|
return array_keys($aTableInfo["Fields"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache the information about existing tables, and their fields
|
// Cache the information about existing tables, and their fields
|
||||||
private static $m_aTablesInfo = array();
|
private static $m_aTablesInfo = array();
|
||||||
private static function _TablesInfoCacheReset($sTableName = null)
|
private static function _TablesInfoCacheReset($sTableName = null)
|
||||||
@@ -1494,7 +1494,7 @@ class CMDBSource
|
|||||||
{
|
{
|
||||||
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
|
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
|
||||||
}
|
}
|
||||||
|
|
||||||
$aRows = array();
|
$aRows = array();
|
||||||
while ($aRow = $oResult->fetch_array(MYSQLI_ASSOC))
|
while ($aRow = $oResult->fetch_array(MYSQLI_ASSOC))
|
||||||
{
|
{
|
||||||
@@ -1503,7 +1503,7 @@ class CMDBSource
|
|||||||
$oResult->free();
|
$oResult->free();
|
||||||
return $aRows;
|
return $aRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the value of the specified server variable
|
* Returns the value of the specified server variable
|
||||||
* @param string $sVarName Name of the server variable
|
* @param string $sVarName Name of the server variable
|
||||||
@@ -1519,7 +1519,7 @@ class CMDBSource
|
|||||||
/**
|
/**
|
||||||
* Returns the privileges of the current user
|
* Returns the privileges of the current user
|
||||||
* @return string privileges in a raw format
|
* @return string privileges in a raw format
|
||||||
*/
|
*/
|
||||||
public static function GetRawPrivileges()
|
public static function GetRawPrivileges()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -1545,8 +1545,8 @@ class CMDBSource
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine the slave status of the server
|
* Determine the slave status of the server
|
||||||
* @return bool true if the server is slave
|
* @return bool true if the server is slave
|
||||||
*/
|
*/
|
||||||
public static function IsSlaveServer()
|
public static function IsSlaveServer()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
|
|
||||||
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
|
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
|
||||||
use Combodo\iTop\Service\Events\EventData;
|
use Combodo\iTop\Service\Events\EventData;
|
||||||
|
use Combodo\iTop\Service\Events\EventException;
|
||||||
use Combodo\iTop\Service\Events\EventService;
|
use Combodo\iTop\Service\Events\EventService;
|
||||||
|
use Combodo\iTop\Service\Events\EventServiceLog;
|
||||||
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectManager;
|
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -203,6 +205,8 @@ abstract class DBObject implements iDisplay
|
|||||||
|
|
||||||
const MAX_UPDATE_LOOP_COUNT = 10;
|
const MAX_UPDATE_LOOP_COUNT = 10;
|
||||||
|
|
||||||
|
private $aEventListeners = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DBObject constructor.
|
* DBObject constructor.
|
||||||
*
|
*
|
||||||
@@ -255,6 +259,10 @@ abstract class DBObject implements iDisplay
|
|||||||
$this->RegisterEventListeners();
|
$this->RegisterEventListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see RegisterCRUDListener
|
||||||
|
* @see EventService::RegisterListener()
|
||||||
|
*/
|
||||||
protected function RegisterEventListeners()
|
protected function RegisterEventListeners()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -612,8 +620,11 @@ abstract class DBObject implements iDisplay
|
|||||||
public function Set($sAttCode, $value)
|
public function Set($sAttCode, $value)
|
||||||
{
|
{
|
||||||
if (!utils::StartsWith(get_class($this), 'CMDBChange') && $this->GetKey() > 0) {
|
if (!utils::StartsWith(get_class($this), 'CMDBChange') && $this->GetKey() > 0) {
|
||||||
// not all the values have __to_string() so print_r is sed and preferred over var_export for the handling or circular references
|
if (is_object($value) || is_array($value)) {
|
||||||
$this->LogCRUDEnter(__METHOD__, "$sAttCode => ".print_r($value, true));
|
$this->LogCRUDEnter(__METHOD__, "$sAttCode => object or array");
|
||||||
|
} else {
|
||||||
|
$this->LogCRUDEnter(__METHOD__, "$sAttCode => ".print_r($value, true));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$sMessage = $this->IsReadOnly();
|
$sMessage = $this->IsReadOnly();
|
||||||
@@ -6162,6 +6173,51 @@ abstract class DBObject implements iDisplay
|
|||||||
return OPT_ATT_NORMAL;
|
return OPT_ATT_NORMAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final function GetListeners(): array
|
||||||
|
{
|
||||||
|
$aListeners = [];
|
||||||
|
foreach ($this->aEventListeners as $aEventListener) {
|
||||||
|
$aListeners = array_merge($aListeners, $aEventListener);
|
||||||
|
}
|
||||||
|
return $aListeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a callback for a specific event. The method to call will be saved in the object instance itself whereas calling {@see EventService::RegisterListener()} would
|
||||||
|
* save a callable (thus the method name AND the whole DBObject instance)
|
||||||
|
*
|
||||||
|
* @param string $sEvent corresponding event
|
||||||
|
* @param string $callback The callback method to call
|
||||||
|
* @param float $fPriority optional priority for callback order
|
||||||
|
* @param string $sModuleId
|
||||||
|
*
|
||||||
|
* @see EventService::RegisterListener()
|
||||||
|
*
|
||||||
|
* @since 3.1.0-3 3.1.1 3.2.0 N°6716
|
||||||
|
*/
|
||||||
|
final protected function RegisterCRUDListener(string $sEvent, string $callback, float $fPriority = 0.0, string $sModuleId = '')
|
||||||
|
{
|
||||||
|
$aEventCallbacks = $this->aEventListeners[$sEvent] ?? [];
|
||||||
|
|
||||||
|
$aEventCallbacks[] = array(
|
||||||
|
'event' => $sEvent,
|
||||||
|
'callback' => $callback,
|
||||||
|
'priority' => $fPriority,
|
||||||
|
'module' => $sModuleId,
|
||||||
|
);
|
||||||
|
usort($aEventCallbacks, function ($a, $b) {
|
||||||
|
$fPriorityA = $a['priority'];
|
||||||
|
$fPriorityB = $b['priority'];
|
||||||
|
if ($fPriorityA == $fPriorityB) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($fPriorityA < $fPriorityB) ? -1 : 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->aEventListeners[$sEvent] = $aEventCallbacks;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $sEvent
|
* @param string $sEvent
|
||||||
* @param array $aEventData
|
* @param array $aEventData
|
||||||
@@ -6173,15 +6229,53 @@ abstract class DBObject implements iDisplay
|
|||||||
*/
|
*/
|
||||||
public function FireEvent(string $sEvent, array $aEventData = array()): void
|
public function FireEvent(string $sEvent, array $aEventData = array()): void
|
||||||
{
|
{
|
||||||
if (EventService::IsEventRegistered($sEvent)) {
|
$aEventData['debug_info'] = 'from: '.get_class($this).':'.$this->GetKey();
|
||||||
$aEventData['debug_info'] = 'from: '.get_class($this).':'.$this->GetKey();
|
$aEventData['object'] = $this;
|
||||||
$aEventData['object'] = $this;
|
|
||||||
$aEventSources = [$this->m_sObjectUniqId];
|
// Call local listeners first
|
||||||
foreach (MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL, false) as $sClass) {
|
$aEventCallbacks = $this->aEventListeners[$sEvent] ?? [];
|
||||||
$aEventSources[] = $sClass;
|
$oFirstException = null;
|
||||||
|
$sFirstExceptionMessage = '';
|
||||||
|
foreach ($aEventCallbacks as $aEventCallback) {
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
|
$sCallback = $aEventCallback['callback'];
|
||||||
|
if (!method_exists($this, $sCallback)) {
|
||||||
|
EventServiceLog::Error("Callback '".get_class($this).":$sCallback' does not exist");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
EventServiceLog::Debug("Fire event '$sEvent' calling '".get_class($this).":$sCallback'");
|
||||||
|
try {
|
||||||
|
call_user_func([$this, $sCallback], new EventData($sEvent, null, $aEventData));
|
||||||
|
}
|
||||||
|
catch (EventException $e) {
|
||||||
|
EventServiceLog::Error("Event '$sEvent' for '$sCallback'} failed with blocking error: ".$e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
catch (Exception $e) {
|
||||||
|
$sMessage = "Event '$sEvent' for '$sCallback'} failed with non-blocking error: ".$e->getMessage();
|
||||||
|
EventServiceLog::Error($sMessage);
|
||||||
|
if (is_null($oFirstException)) {
|
||||||
|
$sFirstExceptionMessage = $sMessage;
|
||||||
|
$oFirstException = $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
$oKPI->ComputeStats('FireEvent', $sEvent);
|
||||||
}
|
}
|
||||||
EventService::FireEvent(new EventData($sEvent, $aEventSources, $aEventData));
|
|
||||||
}
|
}
|
||||||
|
if (!is_null($oFirstException)) {
|
||||||
|
throw new Exception($sFirstExceptionMessage, $oFirstException->getCode(), $oFirstException);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call global event listeners
|
||||||
|
if (!EventService::IsEventRegistered($sEvent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$aEventSources = [];
|
||||||
|
foreach (MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL, false) as $sClass) {
|
||||||
|
$aEventSources[] = $sClass;
|
||||||
|
}
|
||||||
|
EventService::FireEvent(new EventData($sEvent, $aEventSources, $aEventData));
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////
|
//////////////////
|
||||||
|
|||||||
@@ -290,7 +290,6 @@ function DisplayEvents(WebPage $oPage, $sClass)
|
|||||||
foreach (MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sChildClass) {
|
foreach (MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sChildClass) {
|
||||||
if (!MetaModel::IsAbstract($sChildClass)) {
|
if (!MetaModel::IsAbstract($sChildClass)) {
|
||||||
$oObject = MetaModel::NewObject($sChildClass);
|
$oObject = MetaModel::NewObject($sChildClass);
|
||||||
$aSources[] = $oObject->GetObjectUniqId();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,7 +298,6 @@ function DisplayEvents(WebPage $oPage, $sClass)
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$oObject = MetaModel::NewObject($sClass);
|
$oObject = MetaModel::NewObject($sClass);
|
||||||
$aSources[] = $oObject->GetObjectUniqId();
|
|
||||||
foreach (MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL, false) as $sParentClass) {
|
foreach (MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL, false) as $sParentClass) {
|
||||||
$aSources[] = $sParentClass;
|
$aSources[] = $sParentClass;
|
||||||
}
|
}
|
||||||
@@ -320,12 +318,19 @@ function DisplayEvents(WebPage $oPage, $sClass)
|
|||||||
});
|
});
|
||||||
$aColumns = [
|
$aColumns = [
|
||||||
'event' => ['label' => Dict::S('UI:Schema:Events:Event')],
|
'event' => ['label' => Dict::S('UI:Schema:Events:Event')],
|
||||||
'listener' => ['label' => Dict::S('UI:Schema:Events:Listener')],
|
'callback' => ['label' => Dict::S('UI:Schema:Events:Listener')],
|
||||||
'priority' => ['label' => Dict::S('UI:Schema:Events:Rank')],
|
'priority' => ['label' => Dict::S('UI:Schema:Events:Rank')],
|
||||||
'module' => ['label' => Dict::S('UI:Schema:Events:Module')],
|
'module' => ['label' => Dict::S('UI:Schema:Events:Module')],
|
||||||
];
|
];
|
||||||
|
// Get the object listeners first
|
||||||
$aRows = [];
|
$aRows = [];
|
||||||
$oReflectionClass = new ReflectionClass($sClass);
|
$oReflectionClass = new ReflectionClass($sClass);
|
||||||
|
if ($oReflectionClass->isInstantiable()) {
|
||||||
|
/** @var DBObject $oClass */
|
||||||
|
$oClass = new $sClass();
|
||||||
|
$aRows = $oClass->GetListeners();
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($aListeners as $aListener) {
|
foreach ($aListeners as $aListener) {
|
||||||
if (is_object($aListener['callback'][0])) {
|
if (is_object($aListener['callback'][0])) {
|
||||||
$sListenerClass = $sClass;
|
$sListenerClass = $sClass;
|
||||||
@@ -343,7 +348,7 @@ function DisplayEvents(WebPage $oPage, $sClass)
|
|||||||
}
|
}
|
||||||
$aRows[] = [
|
$aRows[] = [
|
||||||
'event' => $aListener['event'],
|
'event' => $aListener['event'],
|
||||||
'listener' => $sListener,
|
'callback' => $sListener,
|
||||||
'priority' => $aListener['priority'],
|
'priority' => $aListener['priority'],
|
||||||
'module' => $aListener['module'],
|
'module' => $aListener['module'],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
//
|
//
|
||||||
// This file is part of iTop.
|
// 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
|
// 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
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
@@ -146,18 +146,23 @@ class DBBackup
|
|||||||
/**
|
/**
|
||||||
* Create a normalized backup name, depending on the current date/time and Database
|
* Create a normalized backup name, depending on the current date/time and Database
|
||||||
*
|
*
|
||||||
* @param string $sNameSpec Name and path, eventually containing itop placeholders + time formatting following the strftime() format {@link https://www.php.net/manual/fr/function.strftime.php}
|
* @param string|null $sNameSpec Name and path, eventually containing itop placeholders + time formatting following the strftime() format {@link https://www.php.net/manual/fr/function.strftime.php}
|
||||||
* @param \DateTime|null $oDateTime Date time to use for the name
|
* @param \DateTime|null $oDateTime Date time to use for the name
|
||||||
*
|
*
|
||||||
* @return string Name of the backup file WITHOUT the file extension (eg. `.tar.gz`)
|
* @return string Name of the backup file WITHOUT the file extension (eg. `.tar.gz`)
|
||||||
* @since 3.1.0 N°5279 Add $oDateTime parameter
|
* @since 3.1.0 N°5279 Add $oDateTime parameter
|
||||||
*/
|
*/
|
||||||
public function MakeName(string $sNameSpec = "__DB__-%Y-%m-%d", DateTime $oDateTime = null)
|
public function MakeName(?string $sNameSpec = null, ?DateTime $oDateTime = null)
|
||||||
{
|
{
|
||||||
if ($oDateTime === null) {
|
if ($oDateTime === null) {
|
||||||
$oDateTime = new DateTime();
|
$oDateTime = new DateTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//N°6640
|
||||||
|
if ($sNameSpec === null) {
|
||||||
|
$sNameSpec = "__DB__-%Y-%m-%d";
|
||||||
|
}
|
||||||
|
|
||||||
$sFileName = $sNameSpec;
|
$sFileName = $sNameSpec;
|
||||||
$sFileName = str_replace('__HOST__', $this->sDBHost, $sFileName);
|
$sFileName = str_replace('__HOST__', $this->sDBHost, $sFileName);
|
||||||
$sFileName = str_replace('__DB__', $this->sDBName, $sFileName);
|
$sFileName = str_replace('__DB__', $this->sDBName, $sFileName);
|
||||||
@@ -222,7 +227,7 @@ class DBBackup
|
|||||||
*
|
*
|
||||||
* @param string $sSourceConfigFile
|
* @param string $sSourceConfigFile
|
||||||
* @param string $sTmpFolder
|
* @param string $sTmpFolder
|
||||||
* @param bool $bSkipSQLDumpForTesting
|
* @param bool $bSkipSQLDumpForTesting
|
||||||
*
|
*
|
||||||
* @return array list of files to archive
|
* @return array list of files to archive
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
@@ -273,7 +278,7 @@ class DBBackup
|
|||||||
if(!file_exists(APPROOT.'/'.$sExtraFileOrDir)) {
|
if(!file_exists(APPROOT.'/'.$sExtraFileOrDir)) {
|
||||||
continue; // Ignore non-existing files
|
continue; // Ignore non-existing files
|
||||||
}
|
}
|
||||||
|
|
||||||
$sExtraFullPath = utils::RealPath(APPROOT.'/'.$sExtraFileOrDir, APPROOT);
|
$sExtraFullPath = utils::RealPath(APPROOT.'/'.$sExtraFileOrDir, APPROOT);
|
||||||
if ($sExtraFullPath === false)
|
if ($sExtraFullPath === false)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1449,17 +1449,12 @@ EOF
|
|||||||
}
|
}
|
||||||
$sMethods .= "\n $sCallbackFct\n\n";
|
$sMethods .= "\n $sCallbackFct\n\n";
|
||||||
}
|
}
|
||||||
if (strpos($sCallback, '::') === false) {
|
|
||||||
$sEventListener = '[$this, \''.$sCallback.'\']';
|
|
||||||
} else {
|
|
||||||
$sEventListener = "'$sCallback'";
|
|
||||||
}
|
|
||||||
|
|
||||||
$sListenerRank = (float)($oListener->GetChildText('rank', '0'));
|
$sListenerRank = (float)($oListener->GetChildText('rank', '0'));
|
||||||
$sEvents .= <<<PHP
|
$sEvents .= <<<PHP
|
||||||
|
|
||||||
// listenerId = $sListenerId
|
// listenerId = $sListenerId
|
||||||
Combodo\iTop\Service\Events\EventService::RegisterListener("$sEventName", $sEventListener, \$this->m_sObjectUniqId, [], null, $sListenerRank, '$sModuleRelativeDir');
|
\$this->RegisterCRUDListener("$sEventName", '$sCallback', $sListenerRank, '$sModuleRelativeDir');
|
||||||
PHP;
|
PHP;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -892,7 +892,10 @@ class iTopDesignFormat
|
|||||||
$oNodeList = $oXPath->query("/itop_design/classes//class/fields/field/values/value");
|
$oNodeList = $oXPath->query("/itop_design/classes//class/fields/field/values/value");
|
||||||
foreach ($oNodeList as $oNode) {
|
foreach ($oNodeList as $oNode) {
|
||||||
$sCode = $oNode->textContent;
|
$sCode = $oNode->textContent;
|
||||||
$oNode->textContent = '';
|
// N°6562 textContent is readonly, see https://www.php.net/manual/en/class.domnode.php#95545
|
||||||
|
// $oNode->textContent = '';
|
||||||
|
// N°6562 to update text node content we must use the node methods !
|
||||||
|
$oNode->removeChild($oNode->firstChild);
|
||||||
$oCodeNode = $oNode->ownerDocument->createElement("code", $sCode);
|
$oCodeNode = $oNode->ownerDocument->createElement("code", $sCode);
|
||||||
$oNode->appendChild($oCodeNode);
|
$oNode->appendChild($oCodeNode);
|
||||||
}
|
}
|
||||||
@@ -982,7 +985,12 @@ class iTopDesignFormat
|
|||||||
if ($oStyleNode) {
|
if ($oStyleNode) {
|
||||||
$this->DeleteNode($oStyleNode);
|
$this->DeleteNode($oStyleNode);
|
||||||
}
|
}
|
||||||
$oNode->textContent = $sCode;
|
|
||||||
|
// N°6562 textContent is readonly, see https://www.php.net/manual/en/class.domnode.php#95545
|
||||||
|
// $oNode->textContent = $sCode;
|
||||||
|
// N°6562 to update text node content we must use the node methods !
|
||||||
|
$oTextContentNode = new DOMText($sCode);
|
||||||
|
$oNode->appendChild($oTextContentNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// - Style
|
// - Style
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use Closure;
|
|||||||
use Combodo\iTop\Service\Events\Description\EventDescription;
|
use Combodo\iTop\Service\Events\Description\EventDescription;
|
||||||
use ContextTag;
|
use ContextTag;
|
||||||
use CoreException;
|
use CoreException;
|
||||||
|
use DBObject;
|
||||||
use Exception;
|
use Exception;
|
||||||
use ExecutionKPI;
|
use ExecutionKPI;
|
||||||
use ReflectionClass;
|
use ReflectionClass;
|
||||||
@@ -53,6 +54,12 @@ final class EventService
|
|||||||
/**
|
/**
|
||||||
* Register a callback for a specific event
|
* Register a callback for a specific event
|
||||||
*
|
*
|
||||||
|
* **Warning** : be ultra careful on memory footprint ! each callback will be saved in {@see aEventListeners}, and a callback is
|
||||||
|
* made of the whole object instance and the method name ({@link https://www.php.net/manual/en/language.types.callable.php}).
|
||||||
|
* For example to register on DBObject instances, you should better use {@see DBObject::RegisterCRUDListener()}
|
||||||
|
*
|
||||||
|
* @uses aEventListeners
|
||||||
|
*
|
||||||
* @api
|
* @api
|
||||||
* @param string $sEvent corresponding event
|
* @param string $sEvent corresponding event
|
||||||
* @param callable $callback The callback to call
|
* @param callable $callback The callback to call
|
||||||
@@ -61,8 +68,12 @@ final class EventService
|
|||||||
* @param array|string|null $context context filter
|
* @param array|string|null $context context filter
|
||||||
* @param float $fPriority optional priority for callback order
|
* @param float $fPriority optional priority for callback order
|
||||||
*
|
*
|
||||||
* @return string Id of the registration
|
* @return string registration identifier
|
||||||
*
|
*
|
||||||
|
* @see DBObject::RegisterCRUDListener() to register in DBObject instances instead, to reduce memory footprint (callback saving)
|
||||||
|
*
|
||||||
|
* @since 3.1.0 method creation
|
||||||
|
* @since 3.1.0-3 3.1.1 3.2.0 N°6716 PHPDoc change to warn on memory footprint, and {@see DBObject::RegisterCRUDListener()} alternative
|
||||||
*/
|
*/
|
||||||
public static function RegisterListener(string $sEvent, callable $callback, $sEventSource = null, array $aCallbackData = [], $context = null, float $fPriority = 0.0, $sModuleId = ''): string
|
public static function RegisterListener(string $sEvent, callable $callback, $sEventSource = null, array $aCallbackData = [], $context = null, float $fPriority = 0.0, $sModuleId = ''): string
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -138,12 +138,24 @@ class Router
|
|||||||
{
|
{
|
||||||
$aRoutes = [];
|
$aRoutes = [];
|
||||||
$bUseCache = false === utils::IsDevelopmentEnvironment();
|
$bUseCache = false === utils::IsDevelopmentEnvironment();
|
||||||
|
$bMustWriteCache = false;
|
||||||
$sCacheFilePath = $this->GetCacheFileAbsPath();
|
$sCacheFilePath = $this->GetCacheFileAbsPath();
|
||||||
|
|
||||||
// Try to read from cache
|
// Try to read from cache
|
||||||
if ($bUseCache) {
|
if ($bUseCache) {
|
||||||
if (is_file($sCacheFilePath)) {
|
if (is_file($sCacheFilePath)) {
|
||||||
$aRoutes = include $sCacheFilePath;
|
$aCachedRoutes = include $sCacheFilePath;
|
||||||
|
|
||||||
|
// N°6618 - Protection against corrupted cache returning `1` instead of an array of routes
|
||||||
|
if (is_array($aCachedRoutes)) {
|
||||||
|
$aRoutes = $aCachedRoutes;
|
||||||
|
} else {
|
||||||
|
// Invalid cache force re-generation
|
||||||
|
// Note that even if it is re-generated corrupted again, this protection should prevent crashes
|
||||||
|
$bMustWriteCache = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$bMustWriteCache = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,11 +192,11 @@ class Router
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save to cache
|
// Save to cache if it doesn't exist already
|
||||||
if ($bUseCache) {
|
if ($bMustWriteCache) {
|
||||||
$sCacheContent = "<?php\n\nreturn ".var_export($aRoutes, true).";";
|
$sCacheContent = "<?php\n\nreturn ".var_export($aRoutes, true).";";
|
||||||
SetupUtils::builddir(dirname($sCacheFilePath));
|
SetupUtils::builddir(dirname($sCacheFilePath));
|
||||||
file_put_contents($sCacheFilePath, $sCacheContent);
|
file_put_contents($sCacheFilePath, $sCacheContent, LOCK_EX);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $aRoutes;
|
return $aRoutes;
|
||||||
|
|||||||
@@ -136,6 +136,8 @@ class ItopDataTestCase extends ItopTestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CMDBObject::SetCurrentChange(null);
|
||||||
|
|
||||||
parent::tearDown();
|
parent::tearDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,10 @@ class ItopTestCase extends TestCase
|
|||||||
|
|
||||||
if (CMDBSource::IsInsideTransaction()) {
|
if (CMDBSource::IsInsideTransaction()) {
|
||||||
// Nested transactions were opened but not finished !
|
// Nested transactions were opened but not finished !
|
||||||
|
// Rollback to avoid side effects on next tests
|
||||||
|
while (CMDBSource::IsInsideTransaction()) {
|
||||||
|
CMDBSource::Query('ROLLBACK');
|
||||||
|
}
|
||||||
throw new MySQLTransactionNotClosedException('Some DB transactions were opened but not closed ! Fix the code by adding ROLLBACK or COMMIT statements !', []);
|
throw new MySQLTransactionNotClosedException('Some DB transactions were opened but not closed ! Fix the code by adding ROLLBACK or COMMIT statements !', []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -312,4 +316,4 @@ class ItopTestCase extends TestCase
|
|||||||
}
|
}
|
||||||
closedir($dir);
|
closedir($dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
|||||||
use ContactType;
|
use ContactType;
|
||||||
use CoreException;
|
use CoreException;
|
||||||
use DBObject;
|
use DBObject;
|
||||||
|
use DBObject\MockDBObjectWithCRUDEventListener;
|
||||||
use DBObjectSet;
|
use DBObjectSet;
|
||||||
use DBSearch;
|
use DBSearch;
|
||||||
use lnkPersonToTeam;
|
use lnkPersonToTeam;
|
||||||
@@ -521,6 +522,24 @@ class CRUDEventTest extends ItopDataTestCase
|
|||||||
|
|
||||||
$this->assertEquals(2, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
|
$this->assertEquals(2, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests with MockDBObject
|
||||||
|
public function testFireCRUDEvent()
|
||||||
|
{
|
||||||
|
$this->RequireOnceUnitTestFile('DBObject/MockDBObjectWithCRUDEventListener.php');
|
||||||
|
|
||||||
|
// For Metamodel list of classes
|
||||||
|
MockDBObjectWithCRUDEventListener::Init();
|
||||||
|
$oDBObject = new MockDBObjectWithCRUDEventListener();
|
||||||
|
$oDBObject2 = new MockDBObjectWithCRUDEventListener();
|
||||||
|
|
||||||
|
$oDBObject->FireEvent(MockDBObjectWithCRUDEventListener::TEST_EVENT);
|
||||||
|
|
||||||
|
$this->assertNotNull($oDBObject->oEventDataReceived);
|
||||||
|
$this->assertNull($oDBObject2->oEventDataReceived);
|
||||||
|
|
||||||
|
//echo($oDBObject->oEventDataReceived->Get('debug_info'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||||
|
* @license http://opensource.org/licenses/AGPL-3.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace DBObject;
|
||||||
|
|
||||||
|
use Combodo\iTop\Service\Events\EventData;
|
||||||
|
use MetaModel;
|
||||||
|
|
||||||
|
class MockDBObjectWithCRUDEventListener extends \DBObject
|
||||||
|
{
|
||||||
|
const TEST_EVENT = 'test_event';
|
||||||
|
public $oEventDataReceived = null;
|
||||||
|
|
||||||
|
public static function Init()
|
||||||
|
{
|
||||||
|
$aParams = array
|
||||||
|
(
|
||||||
|
'category' => 'bizmodel, searchable',
|
||||||
|
'key_type' => 'autoincrement',
|
||||||
|
'name_attcode' => '',
|
||||||
|
'state_attcode' => '',
|
||||||
|
'reconc_keys' => [],
|
||||||
|
'db_table' => 'priv_unit_tests_mock',
|
||||||
|
'db_key_field' => 'id',
|
||||||
|
'db_finalclass_field' => '',
|
||||||
|
'display_template' => '',
|
||||||
|
'indexes' => [],
|
||||||
|
);
|
||||||
|
MetaModel::Init_Params($aParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function RegisterEventListeners()
|
||||||
|
{
|
||||||
|
$this->RegisterCRUDListener(self::TEST_EVENT, 'TestEventCallback', 0, 'unit-test');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function TestEventCallback(EventData $oEventData)
|
||||||
|
{
|
||||||
|
$this->oEventDataReceived = $oEventData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -897,4 +897,40 @@ class DBObjectTest extends ItopDataTestCase
|
|||||||
return $oPerson;
|
return $oPerson;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.1.0-3 3.1.1 3.2.0 N°6716 test creation
|
||||||
|
*/
|
||||||
|
public function testConstructorMemoryFootprint():void
|
||||||
|
{
|
||||||
|
$idx = 0;
|
||||||
|
$fStart = microtime(true);
|
||||||
|
$fStartLoop = $fStart;
|
||||||
|
$iInitialPeak = 0;
|
||||||
|
$iMaxAllowedMemoryIncrease = 1 * 1024 * 1024;
|
||||||
|
|
||||||
|
for ($i = 0; $i < 5000; $i++) {
|
||||||
|
/** @noinspection PhpUnusedLocalVariableInspection We intentionally use a reference that will disappear on each loop */
|
||||||
|
$oPerson = new \Person();
|
||||||
|
if (0 == ($idx % 100)) {
|
||||||
|
$fDuration = microtime(true) - $fStartLoop;
|
||||||
|
$iMemoryPeakUsage = memory_get_peak_usage();
|
||||||
|
if ($iInitialPeak === 0) {
|
||||||
|
$iInitialPeak = $iMemoryPeakUsage;
|
||||||
|
$sInitialPeak = \utils::BytesToFriendlyFormat($iInitialPeak, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sCurrPeak = \utils::BytesToFriendlyFormat($iMemoryPeakUsage, 4);
|
||||||
|
echo "$idx ".sprintf('%.1f ms', $fDuration * 1000)." - Peak Memory Usage: $sCurrPeak\n";
|
||||||
|
|
||||||
|
$this->assertTrue(($iMemoryPeakUsage - $iInitialPeak) <= $iMaxAllowedMemoryIncrease , "Peak memory changed from $sInitialPeak to $sCurrPeak after $i loops");
|
||||||
|
|
||||||
|
$fStartLoop = microtime(true);
|
||||||
|
}
|
||||||
|
$idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fTotalDuration = microtime(true) - $fStart;
|
||||||
|
echo 'Total duration: '.sprintf('%.3f s', $fTotalDuration)."\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -546,7 +546,7 @@ class ExpressionEvaluateTest extends iTopDataTestCase
|
|||||||
$oDate = new DateTime($sStartDate);
|
$oDate = new DateTime($sStartDate);
|
||||||
for ($i = 0 ; $i < $iRepeat ; $i++)
|
for ($i = 0 ; $i < $iRepeat ; $i++)
|
||||||
{
|
{
|
||||||
$sDate = date_format($oDate, 'Y-m-d, H:i:s');
|
$sDate = date_format($oDate, 'Y-m-d H:i:s');
|
||||||
$this->debug("Checking '$sDate'");
|
$this->debug("Checking '$sDate'");
|
||||||
$this->testEveryTimeFormat($sDate);
|
$this->testEveryTimeFormat($sDate);
|
||||||
$oDate->add(new DateInterval($sInterval));
|
$oDate->add(new DateInterval($sInterval));
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ class DBBackupTest extends ItopTestCase
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function testMakeName(string $sInputFormat, DateTime $oBackupDateTime, string $sExpectedFilename): void
|
public function testMakeName(?string $sInputFormat, ?DateTime $oBackupDateTime, string $sExpectedFilename): void
|
||||||
{
|
{
|
||||||
$oConfig = utils::GetConfig();
|
$oConfig = utils::GetConfig();
|
||||||
|
|
||||||
@@ -138,6 +138,11 @@ class DBBackupTest extends ItopTestCase
|
|||||||
$oBackupDateTime = DateTime::createFromFormat('Y-m-d H:i:s', '1985-07-30 15:30:59');
|
$oBackupDateTime = DateTime::createFromFormat('Y-m-d H:i:s', '1985-07-30 15:30:59');
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
'Default format - no params' => [
|
||||||
|
'__DB__-%Y-%m-%d',
|
||||||
|
$oBackupDateTime,
|
||||||
|
static::DUMMY_DB_NAME.'-1985-07-30',
|
||||||
|
],
|
||||||
'Default format' => [
|
'Default format' => [
|
||||||
'__DB__-%Y-%m-%d',
|
'__DB__-%Y-%m-%d',
|
||||||
$oBackupDateTime,
|
$oBackupDateTime,
|
||||||
@@ -160,4 +165,28 @@ class DBBackupTest extends ItopTestCase
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* N°6640 - Broken unattended setup with XML backup configuration
|
||||||
|
*/
|
||||||
|
public function testMakeNameWithoutParams(): void
|
||||||
|
{
|
||||||
|
$oConfig = utils::GetConfig();
|
||||||
|
|
||||||
|
// See https://github.com/Combodo/iTop/commit/f7ee21f1d7d1c23910506e9e31b57f33311bd5e0#diff-d693fb790e3463d1aa960c2b8b293532b1bbd12c3b8f885d568d315c404f926aR131
|
||||||
|
$oConfig->Set('db_host', static::DUMMY_DB_HOST);
|
||||||
|
$oConfig->Set('db_name', static::DUMMY_DB_NAME);
|
||||||
|
$oConfig->Set('db_subname', static::DUMMY_DB_SUBNAME);
|
||||||
|
|
||||||
|
$oDateTime = new DateTime();
|
||||||
|
$sExpectedFilename = static::DUMMY_DB_NAME . '-' . $oDateTime->format("Y-m-d");
|
||||||
|
|
||||||
|
$oBackup = new DBBackup($oConfig);
|
||||||
|
|
||||||
|
$sTestedFilename = $oBackup->MakeName(null);
|
||||||
|
$this->assertEquals($sExpectedFilename, $sTestedFilename, "Backup filename format doesn't match. Got '$sTestedFilename', expected '$sExpectedFilename'.");
|
||||||
|
|
||||||
|
$sTestedFilename = $oBackup->MakeName();
|
||||||
|
$this->assertEquals($sExpectedFilename, $sTestedFilename, "Backup filename format doesn't match. Got '$sTestedFilename', expected '$sExpectedFilename'.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,16 @@ use utils;
|
|||||||
*/
|
*/
|
||||||
class RouterTest extends ItopTestCase
|
class RouterTest extends ItopTestCase
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->RequireOnceItopFile('setup/setuputils.class.inc.php');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers \Combodo\iTop\Service\Router\Router::GenerateUrl
|
* @covers \Combodo\iTop\Service\Router\Router::GenerateUrl
|
||||||
* @dataProvider GenerateUrlProvider
|
* @dataProvider GenerateUrlProvider
|
||||||
@@ -169,6 +179,94 @@ class RouterTest extends ItopTestCase
|
|||||||
$this->assertEquals($bShouldBePresent, $bIsPresent, "Route '$sRoute' was not expected amongst the available routes.");
|
$this->assertEquals($bShouldBePresent, $bIsPresent, "Route '$sRoute' was not expected amongst the available routes.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \Combodo\iTop\Service\Router\Router::GetRoutes
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since N°6618 Covers that the cache isn't re-generated at each call of the GetRoutes method
|
||||||
|
*/
|
||||||
|
public function testGetRoutesCacheGeneratedOnlyOnce(): void
|
||||||
|
{
|
||||||
|
$oRouter = Router::GetInstance();
|
||||||
|
$sRoutesCacheFilePath = $this->InvokeNonPublicMethod(Router::class, 'GetCacheFileAbsPath', $oRouter, []);
|
||||||
|
|
||||||
|
// Developer mode must be disabled for the routes cache to be used
|
||||||
|
$oConf = utils::GetConfig();
|
||||||
|
$mDeveloperModePreviousValue = $oConf->Get('developer_mode.enabled');
|
||||||
|
$oConf->Set('developer_mode.enabled', false);
|
||||||
|
|
||||||
|
// Generate cache for first time
|
||||||
|
$this->InvokeNonPublicMethod(Router::class, 'GetRoutes', $oRouter, []);
|
||||||
|
|
||||||
|
// Check that file exists and retrieve modification timestamp
|
||||||
|
if (false === is_file($sRoutesCacheFilePath)) {
|
||||||
|
$this->fail("Cache file was not generated ($sRoutesCacheFilePath)");
|
||||||
|
}
|
||||||
|
|
||||||
|
clearstatcache();
|
||||||
|
$iFirstModificationTimestamp = filemtime($sRoutesCacheFilePath);
|
||||||
|
$this->debug("Initial timestamp: $iFirstModificationTimestamp");
|
||||||
|
|
||||||
|
// Wait for just 1s to ensure timestamps would be different is the file is re-generated
|
||||||
|
sleep(1);
|
||||||
|
|
||||||
|
// Call GetRoutes() again to see if cache gets re-generated or not
|
||||||
|
$this->InvokeNonPublicMethod(Router::class, 'GetRoutes', $oRouter, []);
|
||||||
|
|
||||||
|
// Check that file still exists and that modification timestamp has not changed
|
||||||
|
if (false === is_file($sRoutesCacheFilePath)) {
|
||||||
|
$this->fail("Cache file is no longer present, that should not happen! ($sRoutesCacheFilePath)");
|
||||||
|
}
|
||||||
|
|
||||||
|
clearstatcache();
|
||||||
|
$iSecondModificationTimestamp = filemtime($sRoutesCacheFilePath);
|
||||||
|
$this->debug("Second timestamp: $iSecondModificationTimestamp");
|
||||||
|
|
||||||
|
$this->assertSame($iFirstModificationTimestamp, $iSecondModificationTimestamp, "Cache file timestamp changed, seems like cache is not working and was re-generated when it should not!");
|
||||||
|
|
||||||
|
// Restore previous value for following tests
|
||||||
|
$oConf->Set('developer_mode.enabled', $mDeveloperModePreviousValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \Combodo\iTop\Service\Router\Router::GetRoutes
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since N°6618 Covers that the cache is re-generated correctly if corrupted
|
||||||
|
*/
|
||||||
|
public function testGetRoutesCacheRegeneratedCorrectlyIfCorrupted(): void
|
||||||
|
{
|
||||||
|
$oRouter = Router::GetInstance();
|
||||||
|
$sRoutesCacheFilePath = $this->InvokeNonPublicMethod(Router::class, 'GetCacheFileAbsPath', $oRouter, []);
|
||||||
|
|
||||||
|
// Developer mode must be disabled for the routes cache to be used
|
||||||
|
$oConf = utils::GetConfig();
|
||||||
|
$mDeveloperModePreviousValue = $oConf->Get('developer_mode.enabled');
|
||||||
|
$oConf->Set('developer_mode.enabled', false);
|
||||||
|
|
||||||
|
// Generate corrupted cache manually
|
||||||
|
$sFaultyStatement = 'return 1;';
|
||||||
|
file_put_contents($sRoutesCacheFilePath, <<<PHP
|
||||||
|
<?php
|
||||||
|
|
||||||
|
{$sFaultyStatement}
|
||||||
|
PHP
|
||||||
|
);
|
||||||
|
|
||||||
|
// Retrieve routes to access / fix cache in the process
|
||||||
|
$aRoutes = $this->InvokeNonPublicMethod(Router::class, 'GetRoutes', $oRouter, []);
|
||||||
|
|
||||||
|
// Check that routes are an array
|
||||||
|
$this->assertTrue(is_array($aRoutes));
|
||||||
|
|
||||||
|
// Check that file content doesn't contain `return 1`
|
||||||
|
clearstatcache();
|
||||||
|
$this->assertStringNotContainsString($sFaultyStatement, file_get_contents($sRoutesCacheFilePath), "Cache file still contains the faulty statement ($sFaultyStatement)");
|
||||||
|
|
||||||
|
// Restore previous value for following tests
|
||||||
|
$oConf->Set('developer_mode.enabled', $mDeveloperModePreviousValue);
|
||||||
|
}
|
||||||
|
|
||||||
public function GetRoutesProvider(): array
|
public function GetRoutesProvider(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
|||||||
Reference in New Issue
Block a user