mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-19 00:28:47 +02:00
Compare commits
25 Commits
feature/46
...
feature/89
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
024b842d33 | ||
|
|
0703469465 | ||
|
|
12bf16f790 | ||
|
|
3b62597092 | ||
|
|
c0a2771d4e | ||
|
|
6bd5a7b634 | ||
|
|
82b7ef86c7 | ||
|
|
4f878536a8 | ||
|
|
d937ec0350 | ||
|
|
985db46960 | ||
|
|
01adaadfad | ||
|
|
643752f8e7 | ||
|
|
0e0c09c420 | ||
|
|
f34373be6d | ||
|
|
4dba47798c | ||
|
|
49a7f3118d | ||
|
|
09afcb229c | ||
|
|
3df4ddc696 | ||
|
|
1fe401c102 | ||
|
|
6cb08ba361 | ||
|
|
f9db405343 | ||
|
|
4f1f144c51 | ||
|
|
ef8ade5d20 | ||
|
|
0b242d872a | ||
|
|
d9261b8342 |
@@ -4259,6 +4259,9 @@ class AttributeText extends AttributeString
|
||||
|
||||
public static function RenderWikiHtml($sText, $bWikiOnly = false)
|
||||
{
|
||||
// N°8681 - Ensure to have a string value
|
||||
$sText = $sText ?? '';
|
||||
|
||||
if (!$bWikiOnly) {
|
||||
$sPattern = '/'.str_replace('/', '\/', utils::GetConfig()->Get('url_validation_pattern')).'/i';
|
||||
if (preg_match_all(
|
||||
|
||||
@@ -266,6 +266,9 @@ class InlineImage extends DBObject
|
||||
*/
|
||||
public static function FixUrls($sHtml)
|
||||
{
|
||||
// N°8681 - Ensure to have a string value
|
||||
$sHtml = $sHtml ?? '';
|
||||
|
||||
$aNeedles = [];
|
||||
$aReplacements = [];
|
||||
// Find img tags with an attribute data-img-id
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2024 Combodo SAS
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\EventRegister\ApplicationEvents;
|
||||
use Combodo\iTop\Core\Kpi\KpiLogData;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
use Combodo\iTop\Service\Events\iEventServiceSetup;
|
||||
use Combodo\iTop\Service\Module\ModuleService;
|
||||
|
||||
/**
|
||||
* Measures operations duration, memory usage, etc. (and some other KPIs)
|
||||
*/
|
||||
|
||||
class ExecutionKPI
|
||||
class ExecutionKPI implements iEventServiceSetup
|
||||
{
|
||||
protected static $m_bEnabled_Duration = false;
|
||||
protected static $m_bEnabled_Memory = false;
|
||||
@@ -23,15 +25,18 @@ class ExecutionKPI
|
||||
|
||||
protected static $m_aStats = []; // Recurrent operations
|
||||
protected static $m_aExecData = []; // One shot operations
|
||||
/**
|
||||
* @var array[ExecutionKPI]
|
||||
*/
|
||||
protected static $m_aExecutionStack = []; // embedded execution stats
|
||||
/** @var true */
|
||||
private static bool $bMetamodelStarted = false;
|
||||
|
||||
private static ?float $fLastReportTime = null;
|
||||
private static ?float $iLastReportMemory = null;
|
||||
|
||||
// For stats
|
||||
protected $m_fStarted = null;
|
||||
protected $m_fChildrenDuration = 0; // Count embedded
|
||||
protected $m_iInitialMemory = null;
|
||||
|
||||
private static array $aBootstrapOperations = [];
|
||||
|
||||
public static function EnableDuration($iLevel)
|
||||
{
|
||||
if ($iLevel > 0) {
|
||||
@@ -71,6 +76,7 @@ class ExecutionKPI
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -97,7 +103,7 @@ class ExecutionKPI
|
||||
$sFor = self::$m_sAllowedUser == '*' ? 'EVERYBODY' : "'".trim(self::$m_sAllowedUser)."'";
|
||||
$sSlowQueries = '';
|
||||
if (self::$m_fSlowQueries > 0) {
|
||||
$sSlowQueries = ". Slow Queries: ".self::$m_fSlowQueries."s";
|
||||
$sSlowQueries = '. Slow Queries: '.self::$m_fSlowQueries.'s';
|
||||
}
|
||||
|
||||
$aExtensions = [];
|
||||
@@ -127,7 +133,7 @@ class ExecutionKPI
|
||||
$sRequest .= ' operation: '.$_POST['operation'];
|
||||
}
|
||||
|
||||
$fStop = MyHelpers::getmicrotime();
|
||||
$fStop = microtime(true);
|
||||
if (($fStop - $fItopStarted) > self::$m_fSlowQueries) {
|
||||
// Invoke extensions to log the KPI operation
|
||||
/** @var \iKPILoggerExtension $oExtensionInstance */
|
||||
@@ -151,17 +157,17 @@ class ExecutionKPI
|
||||
|
||||
$sTableStyle = 'background-color: #ccc; margin: 10px;';
|
||||
|
||||
$sHtml = "<hr/>";
|
||||
$sHtml = '<hr/>';
|
||||
$sHtml .= "<div style=\"background-color: grey; padding: 10px;\">";
|
||||
$sHtml .= "<h3><a name=\"".md5($sExecId)."\">KPIs</a> - $sRequest</h3>";
|
||||
$oStarted = DateTime::createFromFormat('U.u', $fItopStarted);
|
||||
$sHtml .= '<p>'.$oStarted->format('Y-m-d H:i:s.u').'</p>';
|
||||
$sHtml .= "<p>log_kpi_user_id: ".UserRights::GetUserId()."</p>";
|
||||
$sHtml .= "<div>";
|
||||
$sHtml .= '<p>log_kpi_user_id: '.UserRights::GetUserId().'</p>';
|
||||
$sHtml .= '<div>';
|
||||
$sHtml .= "<table border=\"1\" style=\"$sTableStyle\">";
|
||||
$sHtml .= "<thead>";
|
||||
$sHtml .= " <th>Operation</th><th>Begin</th><th>End</th><th>Duration</th><th>Memory start</th><th>Memory end</th><th>Memory peak</th>";
|
||||
$sHtml .= "</thead>";
|
||||
$sHtml .= '<thead>';
|
||||
$sHtml .= ' <th>Operation</th><th>Begin</th><th>End</th><th>Duration</th><th>Memory start</th><th>Memory end</th><th>Memory peak</th>';
|
||||
$sHtml .= '</thead>';
|
||||
foreach (self::$m_aExecData as $aOpStats) {
|
||||
$sOperation = $aOpStats['op'];
|
||||
$sBegin = round($aOpStats['time_begin'], 3);
|
||||
@@ -180,12 +186,12 @@ class ExecutionKPI
|
||||
}
|
||||
}
|
||||
|
||||
$sHtml .= "<tr>";
|
||||
$sHtml .= '<tr>';
|
||||
$sHtml .= " <td>$sOperation</td><td>$sBegin</td><td>$sEnd</td><td>$sDuration</td><td>$sMemBegin</td><td>$sMemEnd</td><td>$sMemPeak</td>";
|
||||
$sHtml .= "</tr>";
|
||||
$sHtml .= '</tr>';
|
||||
}
|
||||
$sHtml .= "</table>";
|
||||
$sHtml .= "</div>";
|
||||
$sHtml .= '</table>';
|
||||
$sHtml .= '</div>';
|
||||
|
||||
$aConsolidatedStats = [];
|
||||
foreach (self::$m_aStats as $sOperation => $aOpStats) {
|
||||
@@ -208,20 +214,20 @@ class ExecutionKPI
|
||||
}
|
||||
}
|
||||
$aConsolidatedStats[$sOperation] = [
|
||||
'count' => $iTotalOp,
|
||||
'count' => $iTotalOp,
|
||||
'duration' => $fTotalOp,
|
||||
'min' => $fMinOp,
|
||||
'max' => $fMaxOp,
|
||||
'avg' => $fTotalOp / $iTotalOp,
|
||||
'min' => $fMinOp,
|
||||
'max' => $fMaxOp,
|
||||
'avg' => $fTotalOp / $iTotalOp,
|
||||
'max_args' => $sMaxOpArguments,
|
||||
];
|
||||
}
|
||||
|
||||
$sHtml .= "<div>";
|
||||
$sHtml .= '<div>';
|
||||
$sHtml .= "<table border=\"1\" style=\"$sTableStyle\">";
|
||||
$sHtml .= "<thead>";
|
||||
$sHtml .= " <th>Operation</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th><th>Avg</th>";
|
||||
$sHtml .= "</thead>";
|
||||
$sHtml .= '<thead>';
|
||||
$sHtml .= ' <th>Operation</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th><th>Avg</th>';
|
||||
$sHtml .= '</thead>';
|
||||
foreach ($aConsolidatedStats as $sOperation => $aOpStats) {
|
||||
$sOperation = '<a href="#'.md5($sExecId.$sOperation).'">'.$sOperation.'</a>';
|
||||
$sCount = $aOpStats['count'];
|
||||
@@ -230,14 +236,14 @@ class ExecutionKPI
|
||||
$sMax = '<a href="#'.md5($sExecId.$aOpStats['max_args']).'">'.round($aOpStats['max'], 3).'</a>';
|
||||
$sAvg = round($aOpStats['avg'], 3);
|
||||
|
||||
$sHtml .= "<tr>";
|
||||
$sHtml .= '<tr>';
|
||||
$sHtml .= " <td>$sOperation</td><td>$sCount</td><td>$sDuration</td><td>$sMin</td><td>$sMax</td><td>$sAvg</td>";
|
||||
$sHtml .= "</tr>";
|
||||
$sHtml .= '</tr>';
|
||||
}
|
||||
$sHtml .= "</table>";
|
||||
$sHtml .= "</div>";
|
||||
$sHtml .= '</table>';
|
||||
$sHtml .= '</div>';
|
||||
|
||||
$sHtml .= "</div>";
|
||||
$sHtml .= '</div>';
|
||||
|
||||
$sHtml .= "<p><a href=\"#end-".md5($sExecId)."\">Next page stats</a></p>";
|
||||
|
||||
@@ -287,18 +293,18 @@ class ExecutionKPI
|
||||
$sOperationHtml = '<a name="'.md5($sExecId.$sOperation).'">'.$sOperation.'</a>';
|
||||
$sHtml .= "<h4>$sOperationHtml</h4>";
|
||||
$sHtml .= "<table border=\"1\" style=\"$sTableStyle\">";
|
||||
$sHtml .= "<thead>";
|
||||
$sHtml .= " <th>Operation details (+ blame caller if log_kpi_duration = 2)</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th>";
|
||||
$sHtml .= "</thead>";
|
||||
$sHtml .= '<thead>';
|
||||
$sHtml .= ' <th>Operation details (+ blame caller if log_kpi_duration = 2)</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th>';
|
||||
$sHtml .= '</thead>';
|
||||
$bDisplayHeader = false;
|
||||
}
|
||||
$sHtml .= "<tr>";
|
||||
$sHtml .= '<tr>';
|
||||
$sHtml .= " <td>$sHtmlArguments</td><td>$iCountInter</td><td>$sTotalInter</td><td>$sMinInter</td><td>$sMaxInter</td>";
|
||||
$sHtml .= "</tr>";
|
||||
$sHtml .= '</tr>';
|
||||
}
|
||||
}
|
||||
if (!$bDisplayHeader) {
|
||||
$sHtml .= "</table>";
|
||||
$sHtml .= '</table>';
|
||||
$sHtml .= "<p><a href=\"#".md5($sExecId)."\">Back to page stats</a></p>";
|
||||
}
|
||||
self::Report($sHtml);
|
||||
@@ -333,39 +339,50 @@ class ExecutionKPI
|
||||
|
||||
$aNewEntry = null;
|
||||
|
||||
$fStarted = $this->m_fStarted;
|
||||
$fStopped = $this->m_fStarted;
|
||||
if (self::$m_bEnabled_Duration) {
|
||||
$fStopped = MyHelpers::getmicrotime();
|
||||
$aNewEntry = [
|
||||
'op' => $sOperationDesc,
|
||||
'time_begin' => $this->m_fStarted - $fItopStarted,
|
||||
'time_end' => $fStopped - $fItopStarted,
|
||||
];
|
||||
// Reset for the next operation (if the object is recycled)
|
||||
$this->m_fStarted = $fStopped;
|
||||
if (is_null(static::$fLastReportTime)) {
|
||||
static::$fLastReportTime = $fItopStarted;
|
||||
}
|
||||
|
||||
$iInitialMemory = is_null($this->m_iInitialMemory) ? 0 : $this->m_iInitialMemory;
|
||||
$iCurrentMemory = 0;
|
||||
$iPeakMemory = 0;
|
||||
if (is_null(static::$iLastReportMemory)) {
|
||||
global $iItopInitialMemory;
|
||||
static::$iLastReportMemory = $iItopInitialMemory;
|
||||
}
|
||||
|
||||
$fStarted = static::$fLastReportTime;
|
||||
$fStopped = microtime(true);
|
||||
if (self::$m_bEnabled_Duration) {
|
||||
$aNewEntry = [
|
||||
'op' => $sOperationDesc,
|
||||
'time_begin' => $fStarted - $fItopStarted,
|
||||
'time_end' => $fStopped - $fItopStarted,
|
||||
];
|
||||
}
|
||||
static::$fLastReportTime = $fStopped;
|
||||
|
||||
$iInitialMemory = static::$iLastReportMemory;
|
||||
$iCurrentMemory = $iInitialMemory;
|
||||
$iPeakMemory = $iInitialMemory;
|
||||
if (self::$m_bEnabled_Memory) {
|
||||
$iCurrentMemory = self::memory_get_usage();
|
||||
if (is_null($aNewEntry)) {
|
||||
$aNewEntry = ['op' => $sOperationDesc];
|
||||
}
|
||||
$aNewEntry['mem_begin'] = $this->m_iInitialMemory;
|
||||
$aNewEntry['mem_begin'] = $iInitialMemory;
|
||||
$aNewEntry['mem_end'] = $iCurrentMemory;
|
||||
$iPeakMemory = self::memory_get_peak_usage();
|
||||
$aNewEntry['mem_peak'] = $iPeakMemory;
|
||||
// Reset for the next operation (if the object is recycled)
|
||||
$this->m_iInitialMemory = $iCurrentMemory;
|
||||
static::$iLastReportMemory = $iCurrentMemory;
|
||||
}
|
||||
|
||||
if (self::$m_bEnabled_Duration || self::$m_bEnabled_Memory) {
|
||||
// Invoke extensions to log the KPI operation
|
||||
/** @var \iKPILoggerExtension $oExtensionInstance */
|
||||
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
|
||||
$aCallstack = ['callstack' => $this->GetCallStack()];
|
||||
if (static::$bMetamodelStarted) {
|
||||
foreach (static::$aBootstrapOperations as $oLog) {
|
||||
$this->LogOperation($oLog);
|
||||
}
|
||||
static::$aBootstrapOperations = [];
|
||||
// Invoke extensions to log the KPI operation
|
||||
$sExtension = ModuleService::GetInstance()->GetModuleNameFromCallStack(1);
|
||||
$oKPILogData = new KpiLogData(
|
||||
KpiLogData::TYPE_REPORT,
|
||||
@@ -376,9 +393,24 @@ class ExecutionKPI
|
||||
$sExtension,
|
||||
$iInitialMemory,
|
||||
$iCurrentMemory,
|
||||
$iPeakMemory
|
||||
$iPeakMemory,
|
||||
$aCallstack
|
||||
);
|
||||
$oExtensionInstance->LogOperation($oKPILogData);
|
||||
$this->LogOperation($oKPILogData);
|
||||
} else {
|
||||
$oKPILogData = new KpiLogData(
|
||||
KpiLogData::TYPE_REPORT,
|
||||
'Step',
|
||||
$sOperationDesc,
|
||||
$fStarted,
|
||||
$fStopped,
|
||||
'',
|
||||
$iInitialMemory,
|
||||
$iCurrentMemory,
|
||||
$iPeakMemory,
|
||||
$aCallstack
|
||||
);
|
||||
static::$aBootstrapOperations[] = $oKPILogData;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,13 +420,21 @@ class ExecutionKPI
|
||||
$this->ResetCounters();
|
||||
}
|
||||
|
||||
private function LogOperation(KpiLogData $oKPILogData): void
|
||||
{
|
||||
/** @var \iKPILoggerExtension $oExtensionInstance */
|
||||
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
|
||||
$oExtensionInstance->LogOperation($oKPILogData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute statistics for a call to an extension
|
||||
* Note: not working in dev mode (with links to env-production)
|
||||
*
|
||||
* @param object|string $object object called
|
||||
* @param string $sMethod method called on the object
|
||||
* @param string $sMessage additional message
|
||||
* @param string $sMethod method called on the object
|
||||
* @param string $sMessage additional message
|
||||
*
|
||||
* @return bool true if an extension was found for this object::method
|
||||
* @throws \ReflectionException
|
||||
@@ -423,21 +463,23 @@ class ExecutionKPI
|
||||
|
||||
$fDuration = 0;
|
||||
if (self::$m_bEnabled_Duration) {
|
||||
$fStopped = MyHelpers::getmicrotime();
|
||||
$fStopped = microtime(true);
|
||||
$fDuration = $fStopped - $this->m_fStarted;
|
||||
$aCallstack = [];
|
||||
if (self::$m_bGenerateLegacyReport) {
|
||||
if (self::$m_bBlameCaller) {
|
||||
$aCallstack = MyHelpers::get_callstack(1);
|
||||
self::$m_aStats[$sOperation][$sArguments][] = [
|
||||
'time' => $fDuration,
|
||||
'callers' => $aCallstack,
|
||||
'time' => $fDuration,
|
||||
'callers' => $aCallstack,
|
||||
];
|
||||
} else {
|
||||
self::$m_aStats[$sOperation][$sArguments][] = [
|
||||
'time' => $fDuration,
|
||||
'time' => $fDuration,
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$aCallstack = ['callstack' => $this->GetCallStack()];
|
||||
}
|
||||
|
||||
$iInitialMemory = is_null($this->m_iInitialMemory) ? 0 : $this->m_iInitialMemory;
|
||||
@@ -448,33 +490,45 @@ class ExecutionKPI
|
||||
$iPeakMemory = self::memory_get_peak_usage();
|
||||
}
|
||||
|
||||
// Invoke extensions to log the KPI operation
|
||||
/** @var \iKPILoggerExtension $oExtensionInstance */
|
||||
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
|
||||
//$sExtension = ModuleService::GetInstance()->GetModuleNameFromCallStack(1);
|
||||
$sExtension = '';
|
||||
if (static::$bMetamodelStarted) {
|
||||
foreach (static::$aBootstrapOperations as $oLog) {
|
||||
$this->LogOperation($oLog);
|
||||
}
|
||||
static::$aBootstrapOperations = [];
|
||||
$oKPILogData = new KpiLogData(
|
||||
KpiLogData::TYPE_STATS,
|
||||
$sOperation,
|
||||
$sArguments,
|
||||
$this->m_fStarted,
|
||||
$fStopped,
|
||||
$sExtension,
|
||||
'',
|
||||
$iInitialMemory,
|
||||
$iCurrentMemory,
|
||||
$iPeakMemory,
|
||||
$aCallstack
|
||||
);
|
||||
$oExtensionInstance->LogOperation($oKPILogData);
|
||||
$this->LogOperation($oKPILogData);
|
||||
} else {
|
||||
$oKPILogData = new KpiLogData(
|
||||
KpiLogData::TYPE_STATS,
|
||||
$sOperation,
|
||||
$sArguments,
|
||||
$this->m_fStarted,
|
||||
$fStopped,
|
||||
'',
|
||||
$iInitialMemory,
|
||||
$iCurrentMemory,
|
||||
$iPeakMemory,
|
||||
$aCallstack
|
||||
);
|
||||
static::$aBootstrapOperations[] = $oKPILogData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function ResetCounters()
|
||||
{
|
||||
if (self::$m_bEnabled_Duration) {
|
||||
$this->m_fStarted = microtime(true);
|
||||
}
|
||||
$this->m_fStarted = microtime(true);
|
||||
|
||||
if (self::$m_bEnabled_Memory) {
|
||||
$this->m_iInitialMemory = self::memory_get_usage();
|
||||
@@ -503,7 +557,33 @@ class ExecutionKPI
|
||||
if (function_exists('memory_get_peak_usage')) {
|
||||
return memory_get_peak_usage($bRealUsage);
|
||||
}
|
||||
|
||||
// PHP > 5.2.1 - this verb depends on a compilation option
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* ModuleHandlerApiInterface methods
|
||||
*/
|
||||
|
||||
public static function OnMetaModelStarted()
|
||||
{
|
||||
static::$bMetamodelStarted = true;
|
||||
}
|
||||
|
||||
public function RegisterEventsAndListeners()
|
||||
{
|
||||
EventService::RegisterListener(ApplicationEvents::APPLICATION_EVENT_METAMODEL_STARTED, [$this, 'OnMetaModelStarted']);
|
||||
}
|
||||
|
||||
private function GetCallStack(): string
|
||||
{
|
||||
$aCallStack = MyHelpers::get_callstack(2);
|
||||
$sCallStack = "Call stack:\n";
|
||||
foreach ($aCallStack as $index => $aLine) {
|
||||
$sCallStack .= "#$index ".$aLine['File'].'('.$aLine['Line'].'): '.$aLine['Function']."\n";
|
||||
}
|
||||
|
||||
return $sCallStack;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
|
||||
$ibo-multi-column--margin-x: -16px !default; /* This is to compensate columns padding and make the whole multicolumn align with the parent borders (cf. Bootstrap rows / cols) */
|
||||
$ibo-multi-column--margin-y: $ibo-spacing-0 !default;
|
||||
$ibo-multi-column--row-gap: $ibo-spacing-800 !default;
|
||||
|
||||
.ibo-multi-column {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: $ibo-multi-column--margin-y $ibo-multi-column--margin-x;
|
||||
row-gap: $ibo-multi-column--row-gap;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.2">
|
||||
<module_parameters>
|
||||
<parameters id="authent-local" _delta="define">
|
||||
<password_validation.pattern>^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\da-zA-Z]).{8,}$</password_validation.pattern>
|
||||
<password_validation.pattern>^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\da-zA-Z]).{12,}$</password_validation.pattern>
|
||||
<password_validation.message type="hash"/>
|
||||
</parameters>
|
||||
</module_parameters>
|
||||
|
||||
@@ -29,7 +29,7 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Heslo nemůže uživatel změnit.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Heslo bylo obnoveno',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Termín, kdy bylo heslo změneno',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Heslo musí obsahovat minimálně 8 znaků a musí obsahovat minimálně jedno velké písmeno, jedno malé písmeno, jedno číslo a speciální znak.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Heslo musí obsahovat minimálně 12 znaků a musí obsahovat minimálně jedno velké písmeno, jedno malé písmeno, jedno číslo a speciální znak.',
|
||||
'UserLocal:password:expiration' => 'Níže uvedená pole vyžadují rozšíření',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Nastavení exspirace "Jednorázového hesla" nelze u vlastního účtu uživatele.',
|
||||
]);
|
||||
|
||||
@@ -28,7 +28,7 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewed on~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed~~',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 12 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
'UserLocal:password:expiration' => 'The fields below require an extension~~',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
|
||||
]);
|
||||
|
||||
@@ -28,7 +28,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => '',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Letzte Passworterneuerung',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Letztes Änderungsdatum',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Das Passwort entspricht nicht dem in den Konfigurationsregeln hinterlegten RegEx-Ausdruck',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Das Passwort muss mindestens 12 Zeichen lang sein und Großbuchstaben, Kleinbuchstaben, Zahlen und Sonderzeichen enthalten.',
|
||||
'UserLocal:password:expiration' => 'Die folgenden Felder benötigen eine '.ITOP_APPLICATION_SHORT.' Erweiterung',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Das setzen des Passwortablaufs auf "Einmalpasswort" ist für den eigenen Benutzer nicht erlaubt.',
|
||||
]);
|
||||
|
||||
@@ -55,7 +55,7 @@ Dict::Add('EN US', 'English', 'English', [
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewed on',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 12 characters and include uppercase, lowercase, numeric and special characters.',
|
||||
'UserLocal:password:expiration' => 'The fields below require an extension',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User',
|
||||
]);
|
||||
|
||||
@@ -55,7 +55,7 @@ Dict::Add('EN GB', 'British English', 'British English', [
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewed on',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 12 characters and include uppercase, lowercase, numeric and special characters.',
|
||||
'UserLocal:password:expiration' => 'The fields below require an extension',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User',
|
||||
]);
|
||||
|
||||
@@ -25,7 +25,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'El usuario no puede cambiar la contraseña.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Renovación de contraseña',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Cuando fue el último cambio de contraseña',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'La contraseña debe ser de al menos 8 caracteres e incluír mayúsculas, minúsculas, números y caracteres especiales.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'La contraseña debe ser de al menos 12 caracteres e incluir mayúsculas, minúsculas, números y caracteres especiales.',
|
||||
'UserLocal:password:expiration' => 'El siguiente campo requiere una extensión',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Configurar expiración de contraseña para "ontraseña de un solo uso" no está permitido para su propio Usuario',
|
||||
]);
|
||||
|
||||
@@ -27,7 +27,7 @@ Dict::Add('FR FR', 'French', 'Français', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => '',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Mot de passe changé le',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Dernière date à laquelle le mot de passe a été changé',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Le mot de passe doit contenir au moins 8 caractères, avec minuscule, majuscule, nombre et caractère spécial.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Le mot de passe doit contenir au moins 12 caractères, avec minuscule, majuscule, nombre et caractère spécial.',
|
||||
'UserLocal:password:expiration' => 'Les champs ci-dessous nécessitent une extension',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Impossible de mettre "Usage unique" comme validité du mot de passe pour son propre utilisateur.',
|
||||
]);
|
||||
|
||||
@@ -27,7 +27,7 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'A felhasználó nem változtathat jelszót.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Jelszó megújítás ideje',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'A jelszó legutóbbi módosításának időpontja',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'A jelszónak legalább 8 karakterből kell állnia, és tartalmaznia kell nagybetűket, kisbetűket, numerikus és speciális karaktereket.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'A jelszónak legalább 12 karakterből kell állnia, és tartalmaznia kell nagybetűket, kisbetűket, numerikus és speciális karaktereket.',
|
||||
'UserLocal:password:expiration' => 'Az alábbi mezőkhöz egy bővítmény szükséges',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'A jelszó lejárati idejének beállítása "Egyszeri jelszóra" nem engedélyezett a saját Felhasználó számára.',
|
||||
]);
|
||||
|
||||
@@ -27,7 +27,7 @@ Dict::Add('IT IT', 'Italian', 'Italiano', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'La password non può essere cambiata dall\'utente.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Rinnovo della password',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Quando è stata cambiata l\'ultima volta la password',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'La password deve essere di almeno 8 caratteri e includere lettere maiuscole, minuscole, numeri e caratteri speciali.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'La password deve essere di almeno 12 caratteri e includere lettere maiuscole, minuscole, numeri e caratteri speciali.',
|
||||
'UserLocal:password:expiration' => 'I campi sottostanti richiedono un\'estensione',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Impostare la scadenza della password su "Password monouso" non è consentito per il proprio utente',
|
||||
]);
|
||||
|
||||
@@ -28,7 +28,7 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewed on~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed~~',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 12 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
'UserLocal:password:expiration' => 'The fields below require an extension~~',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
|
||||
]);
|
||||
|
||||
@@ -28,7 +28,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'De gebruiker kan dit wachtwoord niet veranderen.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Wachtwoord laatst aangepast',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Tijdstip waarop het wachtwoord het laatst aangepast werd.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Het wachtwoord bestaat uit minstens 8 tekens en bestaat uit een mix van minstens 1 hoofdletter, kleine letter, cijfer en speciaal teken.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Het wachtwoord bestaat uit minstens 12 tekens en bestaat uit een mix van minstens 1 hoofdletter, kleine letter, cijfer en speciaal teken.',
|
||||
'UserLocal:password:expiration' => 'De velden hieronder vereisen een extensie.',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Je kan geen eenmalig wachtwoord instellen voor je eigen gebruiker.',
|
||||
]);
|
||||
|
||||
@@ -27,7 +27,7 @@ Dict::Add('PL PL', 'Polish', 'Polski', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Hasło nie może być zmienione przez użytkownika.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Odnowienie hasła',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Kiedy ostatnio zmieniano hasło',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Hasło musi mieć co najmniej 8 znaków i zawierać duże, małe litery, cyfry i znaki specjalne.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Hasło musi mieć co najmniej 12 znaków i zawierać duże, małe litery, cyfry i znaki specjalne.',
|
||||
'UserLocal:password:expiration' => 'Poniższe pola wymagają rozszerzenia',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Ustawienie wygaśnięcia hasła "Hasło jednorazowe" nie jest dozwolone dla własnego użytkownika',
|
||||
]);
|
||||
|
||||
@@ -27,7 +27,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Senha não pode ser alterada pelo usuário',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Data da última alteração de senha',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Quando a senha foi alterada anteriormente',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'A senha deve ter no mínimo 8 caracteres e incluir letras maiúsculas, minúsculas, números e símbolos',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'A senha deve ter no mínimo 12 caracteres e incluir letras maiúsculas, minúsculas, números e símbolos',
|
||||
'UserLocal:password:expiration' => 'O campo abaixo requer uma extensão',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Definir a expiração da senha para One-Time Password (OTP) não é permitido para o seu próprio usuário',
|
||||
]);
|
||||
|
||||
@@ -28,7 +28,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Дата изменения пароля',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Когда пароль был изменен в последний раз',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Пароль должен содержать не менее 8 символов и включать прописные, строчные, числовые и специальные символы.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Пароль должен содержать не менее 12 символов и включать прописные, строчные, числовые и специальные символы.',
|
||||
'UserLocal:password:expiration' => 'Поля требуют наличия доп. расширения',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
|
||||
]);
|
||||
|
||||
@@ -27,7 +27,7 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewed on~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed~~',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 12 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
'UserLocal:password:expiration' => 'The fields below require an extension~~',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
|
||||
]);
|
||||
|
||||
@@ -28,7 +28,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewed on~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed~~',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 12 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
'UserLocal:password:expiration' => 'The fields below require an extension~~',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
|
||||
]);
|
||||
|
||||
@@ -51,7 +51,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => '用户不允许修改密码.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => '密码更新',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => '上次修改密码的时间',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => '密码必须至少8个字符, 包含大小写, 数字和特殊字符.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => '密码必须至少12个字符, 包含大小写, 数字和特殊字符.',
|
||||
'UserLocal:password:expiration' => '下面的区域需要插件扩展',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => '不允许用户为自己设置 "一次性密码" 的失效期限',
|
||||
]);
|
||||
|
||||
@@ -19,7 +19,7 @@ Dict::Add('FR FR', 'French', 'Français', [
|
||||
'Class:FAQ/Attribute:summary+' => '',
|
||||
'Class:FAQ/Attribute:description' => 'Description',
|
||||
'Class:FAQ/Attribute:description+' => '',
|
||||
'Class:FAQ/Attribute:category_id' => 'Categorie',
|
||||
'Class:FAQ/Attribute:category_id' => 'Catégorie',
|
||||
'Class:FAQ/Attribute:category_id+' => '',
|
||||
'Class:FAQ/Attribute:category_name' => 'Nom catégorie',
|
||||
'Class:FAQ/Attribute:category_name+' => '',
|
||||
|
||||
@@ -238,6 +238,11 @@
|
||||
<action id="action:bulk delete">allow</action>
|
||||
</actions>
|
||||
</group>
|
||||
<group id="Ticketing">
|
||||
<actions>
|
||||
<action id="stimulus:ev_close">allow</action>
|
||||
</actions>
|
||||
</group>
|
||||
<group id="UserRequest">
|
||||
<actions>
|
||||
<action id="stimulus:ev_approve">allow</action>
|
||||
@@ -254,8 +259,10 @@
|
||||
<group id="Incident">
|
||||
<actions>
|
||||
<action id="stimulus:ev_assign">allow</action>
|
||||
<action id="stimulus:ev_dispatch">allow</action>
|
||||
<action id="stimulus:ev_reassign">allow</action>
|
||||
<action id="stimulus:ev_resolve">allow</action>
|
||||
<action id="stimulus:ev_reopen">allow</action>
|
||||
<action id="stimulus:ev_close">allow</action>
|
||||
<action id="stimulus:ev_pending">allow</action>
|
||||
</actions>
|
||||
|
||||
@@ -9,8 +9,8 @@ namespace Combodo\iTop\Core\Kpi;
|
||||
|
||||
class KpiLogData
|
||||
{
|
||||
public const TYPE_REPORT = 'report';
|
||||
public const TYPE_STATS = 'stats';
|
||||
public const TYPE_REPORT = 'report';
|
||||
public const TYPE_STATS = 'stats';
|
||||
public const TYPE_REQUEST = 'request';
|
||||
|
||||
/** @var string */
|
||||
@@ -33,6 +33,8 @@ class KpiLogData
|
||||
public $iPeakMemory;
|
||||
/** @var array */
|
||||
public $aData;
|
||||
// Computed
|
||||
public string $sDuration;
|
||||
|
||||
/**
|
||||
* @param string $sType
|
||||
@@ -43,9 +45,10 @@ class KpiLogData
|
||||
* @param string $sExtension
|
||||
* @param int $iInitialMemory
|
||||
* @param int $iCurrentMemory
|
||||
* @param int $iPeakMemory
|
||||
* @param array $aData
|
||||
*/
|
||||
public function __construct($sType, $sOperation, $sArguments, $fStartTime, $fStopTime, $sExtension, $iInitialMemory = 0, $iCurrentMemory = 0, $iPeakMemory = 0, $aData = [])
|
||||
public function __construct($sType, $sOperation, $sArguments, float $fStartTime, float $fStopTime, $sExtension, $iInitialMemory = 0, $iCurrentMemory = 0, $iPeakMemory = 0, $aData = [])
|
||||
{
|
||||
$this->sType = $sType;
|
||||
$this->sOperation = $sOperation;
|
||||
@@ -57,6 +60,7 @@ class KpiLogData
|
||||
$this->iCurrentMemory = $iCurrentMemory;
|
||||
$this->iPeakMemory = $iPeakMemory;
|
||||
$this->aData = $aData;
|
||||
$this->sDuration = sprintf('%01.3f', $fStopTime - $fStartTime);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,21 +70,22 @@ class KpiLogData
|
||||
*/
|
||||
public static function GetCSVHeader()
|
||||
{
|
||||
return "Type,Operation,Arguments,StartTime,StopTime,Duration,Extension,InitialMemory,CurrentMemory,PeakMemory";
|
||||
return 'Type,Operation,Arguments,StartTime,StopTime,Duration,Extension,InitialMemory,CurrentMemory,PeakMemory';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the CSV line for the values
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function GetCSV()
|
||||
{
|
||||
$fDuration = sprintf('%01.4f', $this->fStopTime - $this->fStartTime);
|
||||
$sType = $this->RemoveQuotes($this->sType);
|
||||
$sOperation = $this->RemoveQuotes($this->sOperation);
|
||||
$sArguments = $this->RemoveQuotes($this->sArguments);
|
||||
$sExtension = $this->RemoveQuotes($this->sExtension);
|
||||
return "\"$sType\",\"$sOperation\",\"$sArguments\",$this->fStartTime,$this->fStopTime,$fDuration,\"$sExtension\",$this->iInitialMemory,$this->iCurrentMemory,$this->iPeakMemory";
|
||||
|
||||
return "\"$sType\",\"$sOperation\",\"$sArguments\",$this->fStartTime,$this->fStopTime,$this->sDuration,\"$sExtension\",$this->iInitialMemory,$this->iCurrentMemory,$this->iPeakMemory";
|
||||
}
|
||||
|
||||
private function RemoveQuotes(string $sEntry): string
|
||||
@@ -98,6 +103,7 @@ class KpiLogData
|
||||
if ($oOther->fStartTime > $this->fStartTime) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[infra]
|
||||
; STS version : testing greatest PHP version possible
|
||||
php_version=8.2-apache
|
||||
php_version=8.3-apache
|
||||
; N°6629 perf bug on some tests on mariadb for now, so specifying MySQL
|
||||
db_version=5.7
|
||||
db_version=latest-mariadb
|
||||
|
||||
[itop]
|
||||
itop_setup=tests/setup_params/default-params.xml
|
||||
|
||||
@@ -13,13 +13,12 @@ $config = new PhpCsFixer\Config();
|
||||
return $config->setRiskyAllowed(true)
|
||||
->setRules([
|
||||
'@PSR12' => true,
|
||||
'indentation_type' => true,
|
||||
'no_extra_blank_lines' => true,
|
||||
'array_syntax' => ['syntax' => 'short'],
|
||||
'concat_space' => true,
|
||||
'trailing_comma_in_multiline' => true,
|
||||
'no_extra_blank_lines' => true, // default value ['tokens' => ['extra']]
|
||||
'array_syntax' => true, // default value ['syntax' => 'short']
|
||||
'concat_space' => true, // default value ['spacing' => 'none']
|
||||
'trailing_comma_in_multiline' => true, // default value ['after_heredoc' => false, 'elements' => ['arrays']]
|
||||
])
|
||||
->setIndent("\t")
|
||||
->setLineEnding("\n")
|
||||
->setFinder($finder)
|
||||
;
|
||||
;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"require": {
|
||||
"phpstan/phpstan": "^1.10"
|
||||
"phpstan/phpstan": "^2.1"
|
||||
}
|
||||
}
|
||||
|
||||
23
tests/php-static-analysis/composer.lock
generated
23
tests/php-static-analysis/composer.lock
generated
@@ -4,24 +4,19 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "14812c2a05a5972f00f9d67abbd710a9",
|
||||
"content-hash": "cc6d7580a5e98236d68d8b91de9ddebb",
|
||||
"packages": [
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "1.10.26",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "5d660cbb7e1b89253a47147ae44044f49832351f"
|
||||
},
|
||||
"version": "2.1.33",
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/5d660cbb7e1b89253a47147ae44044f49832351f",
|
||||
"reference": "5d660cbb7e1b89253a47147ae44044f49832351f",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f",
|
||||
"reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2|^8.0"
|
||||
"php": "^7.4|^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"phpstan/phpstan-shim": "*"
|
||||
@@ -60,13 +55,9 @@
|
||||
{
|
||||
"url": "https://github.com/phpstan",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-07-19T12:44:37+00:00"
|
||||
"time": "2025-12-05T10:24:31+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
@@ -77,5 +68,5 @@
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.3.0"
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ includes:
|
||||
|
||||
parameters:
|
||||
level: 0
|
||||
#phpVersion: null # Explicitly commented as we rather use the detected version from the above include (`php-includes/target-php-version.php`)
|
||||
editorUrl: 'phpstorm://open?file=%%file%%&line=%%line%%' # Open in PHPStorm asit is Combodo's default IDE
|
||||
#phpVersion: null # Explicitly commented as we rather use the detected version from the above include (`php-includes/target-php-version.php`)
|
||||
editorUrl: 'phpstorm://open?file=%%file%%&line=%%line%%' # Open in PHPStorm as it's Combodo's default IDE
|
||||
bootstrapFiles:
|
||||
- ../../../approot.inc.php
|
||||
- ../../../bootstrap.inc.php
|
||||
@@ -22,11 +22,19 @@ parameters:
|
||||
- ../../../lib # Irrelevant as we only want to analyze our codebase
|
||||
- ../../../node_modules # Irrelevant as we only want to analyze our codebase
|
||||
analyseAndScan:
|
||||
#- ../../../data # Left and commented on purpose to show that we want to analyse the generated cache files
|
||||
# Note 1: We can analyse these folders as if a PHP file requires another PHP element declared in an XML file, it won't find it. So we rely only on `env-production`
|
||||
# This file generates "unignorable errors" for the baseline due to its format, so we don't have any other choice than to exclude it.
|
||||
# But mind that it will prevent PHPStan from warning us about PHP syntax errors in this file.
|
||||
- ../../../core/oql/build/PHP/Lempar.php
|
||||
|
||||
#- ../../../data # Left and commented on purpose to show that we want to analyse the generated cache files
|
||||
|
||||
# Note 1: We can't analyse these folders as if a PHP file requires another PHP element declared in an XML file, it won't find it. So we rely only on `env-production`
|
||||
# Note 2: Only the options selected during the setup will be analysed correctly in `env-production`. For unselected options, we still want to ignore them during the analysis as they would only give a false sentiment of security as their XML PHP classes / snippets / etc would not be tested.
|
||||
- ../../../data/production-modules # Irrelevent as it will already be in `env-production` (for local run only, not useful in the CI)
|
||||
- ../../../datamodels # Irrelevent as it will already be in `env-production`
|
||||
- ../../../extensions # Irrelevent as it will already be in `env-production` (for local run only, not useful in the CI)
|
||||
- ../../../tests # Exclude tests for now
|
||||
- ../../../toolkit # Exlclude toolkit for now
|
||||
- ../../../data/production-modules (?) # Irrelevent as it will already be in `env-production` (for local run only, not useful in the CI)
|
||||
- ../../../datamodels # Irrelevent as it will already be in `env-production`
|
||||
- ../../../extensions # Irrelevent as it will already be in `env-production` (for local run only, not useful in the CI)
|
||||
- ../../../env-php-unit-tests (?) # Irrelevant as it will either already be in `env-production` or might be desynchronized from `env-production`
|
||||
- ../../../env-toolkit (?) # Irrelevent as it will either already be in `env-production` or might be desynchronized from `env-production` (for local run only, not useful in the CI)
|
||||
|
||||
- ../../../tests (?) # Exclude tests for now
|
||||
- ../../../toolkit (?) # Exlclude toolkit for now
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is only here to allow setting a specific PHP version to run the analysis for without
|
||||
@@ -21,4 +22,4 @@ declare(strict_types = 1);
|
||||
$config = [];
|
||||
$config['parameters']['phpVersion'] = PHP_VERSION_ID;
|
||||
|
||||
return $config;
|
||||
return $config;
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
|
||||
Documentation on creating and maintaining tests in iTop.
|
||||
|
||||
|
||||
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### PHPUnit configuration file
|
||||
@@ -78,7 +75,8 @@ Example :
|
||||
$oTagData->DBDelete();
|
||||
```
|
||||
|
||||
Warning : when the condition is met the test is finished and following code will be ignored !
|
||||
> [!WARNING]
|
||||
> When the condition is met the test is finished and following code will be ignored !
|
||||
|
||||
Another way to do is using try/catch blocks, for example :
|
||||
```php
|
||||
|
||||
@@ -28,14 +28,14 @@ class AjaxPageTest extends ItopDataTestCase
|
||||
$iLastCompilation = filemtime(APPROOT.'env-production');
|
||||
|
||||
// When
|
||||
$sOutput = $this->CallItopUrl(
|
||||
"/pages/exec.php?exec_module=itop-hub-connector&exec_page=ajax.php",
|
||||
$sOutput = $this->CallItopUri(
|
||||
"pages/exec.php?exec_module=itop-hub-connector&exec_page=ajax.php",
|
||||
[
|
||||
'auth_user' => $sLogin,
|
||||
'auth_pwd' => self::AUTHENTICATION_PASSWORD,
|
||||
'operation' => "compile",
|
||||
'authent' => self::AUTHENTICATION_TOKEN,
|
||||
]
|
||||
],
|
||||
);
|
||||
|
||||
// Then
|
||||
@@ -53,26 +53,4 @@ class AjaxPageTest extends ItopDataTestCase
|
||||
clearstatcache();
|
||||
$this->assertGreaterThan($iLastCompilation, filemtime(APPROOT.'env-production'), 'The env-production directory should have been rebuilt');
|
||||
}
|
||||
|
||||
protected function CallItopUrl($sUri, ?array $aPostFields = null, bool $bXDebugEnabled = false)
|
||||
{
|
||||
$ch = curl_init();
|
||||
if ($bXDebugEnabled) {
|
||||
curl_setopt($ch, CURLOPT_COOKIE, 'XDEBUG_SESSION=phpstorm');
|
||||
}
|
||||
|
||||
$sUrl = \MetaModel::GetConfig()->Get('app_root_url')."/$sUri";
|
||||
var_dump($sUrl);
|
||||
curl_setopt($ch, CURLOPT_URL, $sUrl);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);// set post data to true
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $aPostFields);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
$sOutput = curl_exec($ch);
|
||||
//echo "$sUrl error code:".curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
return $sOutput;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2013-2024 Combodo SAS
|
||||
*
|
||||
@@ -24,19 +25,15 @@
|
||||
require_once('../../../approot.inc.php');
|
||||
require_once(APPROOT.'application/startup.inc.php');
|
||||
|
||||
|
||||
$sEnvironment = MetaModel::GetEnvironmentId();
|
||||
$aEntries = array();
|
||||
$aEntries = [];
|
||||
$aCacheUserData = apc_cache_info_compat();
|
||||
if (is_array($aCacheUserData) && isset($aCacheUserData['cache_list']))
|
||||
{
|
||||
if (is_array($aCacheUserData) && isset($aCacheUserData['cache_list'])) {
|
||||
$sPrefix = 'itop-'.$sEnvironment.'-query-cache-';
|
||||
|
||||
foreach($aCacheUserData['cache_list'] as $i => $aEntry)
|
||||
{
|
||||
foreach ($aCacheUserData['cache_list'] as $i => $aEntry) {
|
||||
$sEntryKey = array_key_exists('info', $aEntry) ? $aEntry['info'] : $aEntry['key'];
|
||||
if (strpos($sEntryKey, $sPrefix) === 0)
|
||||
{
|
||||
if (strpos($sEntryKey, $sPrefix) === 0) {
|
||||
$aEntries[] = $sEntryKey;
|
||||
}
|
||||
}
|
||||
@@ -44,52 +41,39 @@ if (is_array($aCacheUserData) && isset($aCacheUserData['cache_list']))
|
||||
|
||||
echo "<pre>";
|
||||
|
||||
if (empty($aEntries))
|
||||
{
|
||||
if (empty($aEntries)) {
|
||||
echo "No Data";
|
||||
return;
|
||||
}
|
||||
|
||||
$sKey = $aEntries[0];
|
||||
$result = apc_fetch($sKey);
|
||||
if (!is_object($result))
|
||||
{
|
||||
if (!is_object($result)) {
|
||||
return;
|
||||
}
|
||||
$oSQLQuery = $result;
|
||||
|
||||
echo "NB Tables before;NB Tables after;";
|
||||
foreach($oSQLQuery->m_aContextData as $sField => $oValue)
|
||||
{
|
||||
foreach ($oSQLQuery->m_aContextData as $sField => $oValue) {
|
||||
echo $sField.';';
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
sort($aEntries);
|
||||
|
||||
foreach($aEntries as $sKey)
|
||||
{
|
||||
foreach ($aEntries as $sKey) {
|
||||
$result = apc_fetch($sKey);
|
||||
if (is_object($result))
|
||||
{
|
||||
if (is_object($result)) {
|
||||
$oSQLQuery = $result;
|
||||
if (isset($oSQLQuery->m_aContextData))
|
||||
{
|
||||
if (isset($oSQLQuery->m_aContextData)) {
|
||||
echo $oSQLQuery->m_iOriginalTableCount.";".$oSQLQuery->CountTables().';';
|
||||
foreach($oSQLQuery->m_aContextData as $oValue)
|
||||
{
|
||||
if (is_array($oValue))
|
||||
{
|
||||
foreach ($oSQLQuery->m_aContextData as $oValue) {
|
||||
if (is_array($oValue)) {
|
||||
$sVal = json_encode($oValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (empty($oValue))
|
||||
{
|
||||
} else {
|
||||
if (empty($oValue)) {
|
||||
$sVal = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sVal = $oValue;
|
||||
}
|
||||
}
|
||||
@@ -101,4 +85,3 @@ foreach($aEntries as $sKey)
|
||||
}
|
||||
|
||||
echo "</pre>";
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ use ArchivedObjectException;
|
||||
use CMDBObject;
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
use Config;
|
||||
use Contact;
|
||||
use DBObject;
|
||||
use DBObjectSet;
|
||||
@@ -65,6 +66,9 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
private $aCreatedObjects = [];
|
||||
private $aEventListeners = [];
|
||||
|
||||
protected ?string $sConfigTmpBackupFile = null;
|
||||
protected ?Config $oiTopConfig = null;
|
||||
|
||||
/**
|
||||
* @var bool When testing with silo, there are some cache we need to update on tearDown. Doing it all the time will cost too much, so it's opt-in !
|
||||
* @see tearDown
|
||||
@@ -119,6 +123,8 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
\IssueLog::Error($this->getName());
|
||||
|
||||
$this->PrepareEnvironment();
|
||||
|
||||
if (static::USE_TRANSACTION) {
|
||||
@@ -185,6 +191,8 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
|
||||
CMDBObject::SetCurrentChange(null);
|
||||
|
||||
$this->RestoreConfiguration();
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
@@ -1434,4 +1442,35 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
$oObject->Set($sStopwatchAttCode, $oStopwatch);
|
||||
}
|
||||
|
||||
protected function BackupConfiguration(): void
|
||||
{
|
||||
$sConfigPath = MetaModel::GetConfig()->GetLoadedFile();
|
||||
clearstatcache();
|
||||
echo sprintf("rights via ls on %s:\n %s \n", $sConfigPath, exec("ls -al $sConfigPath"));
|
||||
$sFilePermOutput = substr(sprintf('%o', fileperms('/etc/passwd')), -4);
|
||||
echo sprintf("rights via fileperms on %s:\n %s \n", $sConfigPath, $sFilePermOutput);
|
||||
|
||||
$this->sConfigTmpBackupFile = tempnam(sys_get_temp_dir(), "config_");
|
||||
MetaModel::GetConfig()->WriteToFile($this->sConfigTmpBackupFile);
|
||||
$this->oiTopConfig = new Config($sConfigPath);
|
||||
}
|
||||
|
||||
protected function RestoreConfiguration(): void
|
||||
{
|
||||
if (is_null($this->sConfigTmpBackupFile) || ! is_file($this->sConfigTmpBackupFile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_null($this->oiTopConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//put config back
|
||||
$sConfigPath = $this->oiTopConfig->GetLoadedFile();
|
||||
@chmod($sConfigPath, 0770);
|
||||
$oConfig = new Config($this->sConfigTmpBackupFile);
|
||||
$oConfig->WriteToFile($sConfigPath);
|
||||
@chmod($sConfigPath, 0440);
|
||||
@unlink($this->sConfigTmpBackupFile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ namespace Combodo\iTop\Test\UnitTest;
|
||||
use CMDBSource;
|
||||
use DeprecatedCallsLog;
|
||||
use MySQLTransactionNotClosedException;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use ReflectionMethod;
|
||||
use SetupUtils;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
|
||||
use const DEBUG_BACKTRACE_IGNORE_ARGS;
|
||||
@@ -28,6 +28,7 @@ use const DEBUG_BACKTRACE_IGNORE_ARGS;
|
||||
abstract class ItopTestCase extends KernelTestCase
|
||||
{
|
||||
public const TEST_LOG_DIR = 'test';
|
||||
protected array $aFileToClean = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
@@ -36,7 +37,7 @@ abstract class ItopTestCase extends KernelTestCase
|
||||
public const DISABLE_DEPRECATEDCALLSLOG_ERRORHANDLER = true;
|
||||
public static $DEBUG_UNIT_TEST = false;
|
||||
protected static $aBackupStaticProperties = [];
|
||||
|
||||
public ?array $aLastCurlGetInfo = null;
|
||||
/**
|
||||
* @link https://docs.phpunit.de/en/9.6/annotations.html#preserveglobalstate PHPUnit `preserveGlobalState` annotation documentation
|
||||
*
|
||||
@@ -149,6 +150,17 @@ abstract class ItopTestCase extends KernelTestCase
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Check globals
|
||||
global $fItopStarted;
|
||||
if (is_null($fItopStarted)) {
|
||||
$fItopStarted = microtime(true);
|
||||
}
|
||||
|
||||
global $iItopInitialMemory;
|
||||
if (is_null($iItopInitialMemory)) {
|
||||
$iItopInitialMemory = memory_get_usage(true);
|
||||
}
|
||||
|
||||
// Hack - Required the first time the Portal kernel is booted on a newly installed iTop
|
||||
$_ENV['COMBODO_PORTAL_BASE_ABSOLUTE_PATH'] = __DIR__.'/../../../../../env-production/itop-portal-base/portal/public/';
|
||||
|
||||
@@ -174,6 +186,15 @@ abstract class ItopTestCase extends KernelTestCase
|
||||
}
|
||||
throw new MySQLTransactionNotClosedException('Some DB transactions were opened but not closed ! Fix the code by adding ROLLBACK or COMMIT statements !', []);
|
||||
}
|
||||
|
||||
foreach ($this->aFileToClean as $sPath) {
|
||||
if (is_file($sPath)) {
|
||||
@unlink($sPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
SetupUtils::tidydir($sPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -631,4 +652,62 @@ abstract class ItopTestCase extends KernelTestCase
|
||||
fclose($handle);
|
||||
return array_reverse($aLines);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sUrl
|
||||
* @param array|null $aPostFields
|
||||
* @param array|null $aCurlOptions
|
||||
* @param $bXDebugEnabled
|
||||
* @return string
|
||||
*/
|
||||
protected function CallUrl($sUrl, ?array $aPostFields = [], ?array $aCurlOptions = [], $bXDebugEnabled = false): string
|
||||
{
|
||||
$ch = curl_init();
|
||||
if ($bXDebugEnabled) {
|
||||
curl_setopt($ch, CURLOPT_COOKIE, "XDEBUG_SESSION=phpstorm");
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, $sUrl);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);// set post data to true
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
// Force disable of certificate check as most of dev / test env have a self-signed certificate
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
curl_setopt_array($ch, $aCurlOptions);
|
||||
if ($this->IsArrayOfArray($aPostFields)) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($aPostFields));
|
||||
} else {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $aPostFields);
|
||||
}
|
||||
|
||||
$sOutput = curl_exec($ch);
|
||||
|
||||
$info = curl_getinfo($ch);
|
||||
$this->aLastCurlGetInfo = $info;
|
||||
$sErrorMsg = curl_error($ch);
|
||||
$iErrorCode = curl_errno($ch);
|
||||
curl_close($ch);
|
||||
|
||||
\IssueLog::Info(__METHOD__, null, ['url' => $sUrl, 'error' => $sErrorMsg, 'error_code' => $iErrorCode, 'post_fields' => $aPostFields, 'info' => $info]);
|
||||
|
||||
return $sOutput;
|
||||
}
|
||||
|
||||
private function IsArrayOfArray(array $aStruct): bool
|
||||
{
|
||||
foreach ($aStruct as $k => $v) {
|
||||
if (is_array($v)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function CallItopUri(string $sUri, ?array $aPostFields = [], ?array $aCurlOptions = [], $bXDebugEnabled = false): string
|
||||
{
|
||||
$sUrl = \MetaModel::GetConfig()->Get('app_root_url')."/$sUri";
|
||||
|
||||
return $this->CallUrl($sUrl, $aPostFields, $aCurlOptions, $bXDebugEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Application;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use MetaModel;
|
||||
|
||||
class LoginTest extends ItopDataTestCase
|
||||
{
|
||||
protected $sConfigTmpBackupFile;
|
||||
protected $sConfigPath;
|
||||
protected $sLoginMode;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
clearstatcache();
|
||||
|
||||
// The test consists in requesting UI.php from outside iTop with a specific configuration
|
||||
// Hence the configuration file must be tweaked on disk (and restored)
|
||||
$this->sConfigPath = MetaModel::GetConfig()->GetLoadedFile();
|
||||
$this->sConfigTmpBackupFile = tempnam(sys_get_temp_dir(), "config_");
|
||||
file_put_contents($this->sConfigTmpBackupFile, file_get_contents($this->sConfigPath));
|
||||
|
||||
$oConfig = new \Config($this->sConfigPath);
|
||||
$this->sLoginMode = "unimplemented_loginmode";
|
||||
$oConfig->AddAllowedLoginTypes($this->sLoginMode);
|
||||
|
||||
@chmod($this->sConfigPath, 0770);
|
||||
$oConfig->WriteToFile();
|
||||
@chmod($this->sConfigPath, 0444);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
if (! is_null($this->sConfigTmpBackupFile) && is_file($this->sConfigTmpBackupFile)) {
|
||||
//put config back
|
||||
@chmod($this->sConfigPath, 0770);
|
||||
file_put_contents($this->sConfigPath, file_get_contents($this->sConfigTmpBackupFile));
|
||||
@chmod($this->sConfigPath, 0444);
|
||||
@unlink($this->sConfigTmpBackupFile);
|
||||
}
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
protected function CallItopUrlByCurl($sUri, ?array $aPostFields = [])
|
||||
{
|
||||
$ch = curl_init();
|
||||
|
||||
$sUrl = MetaModel::GetConfig()->Get('app_root_url')."/$sUri";
|
||||
curl_setopt($ch, CURLOPT_URL, $sUrl);
|
||||
if (0 !== sizeof($aPostFields)) {
|
||||
curl_setopt($ch, CURLOPT_POST, 1);// set post data to true
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $aPostFields);
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
$sOutput = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
return $sOutput;
|
||||
}
|
||||
}
|
||||
@@ -172,34 +172,12 @@ class QueryTest extends ItopDataTestCase
|
||||
{
|
||||
// compute request url
|
||||
$url = $oQuery->GetExportUrl();
|
||||
$aCurlOptions = [
|
||||
CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
|
||||
CURLOPT_USERPWD => self::USER.':'.self::PASSWORD,
|
||||
];
|
||||
|
||||
// open curl
|
||||
$curl = curl_init();
|
||||
|
||||
// curl options
|
||||
curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
|
||||
curl_setopt($curl, CURLOPT_USERPWD, self::USER.':'.self::PASSWORD);
|
||||
curl_setopt($curl, CURLOPT_URL, $url);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
// Force disable of certificate check as most of dev / test env have a self-signed certificate
|
||||
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
|
||||
// execute curl
|
||||
$result = curl_exec($curl);
|
||||
if (curl_errno($curl)) {
|
||||
$info = curl_getinfo($curl);
|
||||
var_export($info);
|
||||
var_dump([
|
||||
'url' => $url,
|
||||
'app_root_url:' => MetaModel::GetConfig()->Get('app_root_url'),
|
||||
'GetAbsoluteUrlAppRoot:' => \utils::GetAbsoluteUrlAppRoot(),
|
||||
]);
|
||||
}
|
||||
// close curl
|
||||
curl_close($curl);
|
||||
|
||||
return $result;
|
||||
return $this->CallUrl($url, [], $aCurlOptions);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
|
||||
@@ -13,13 +13,6 @@ class AttributeDefinitionTest extends ItopDataTestCase
|
||||
{
|
||||
public const CREATE_TEST_ORG = true;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
require_once(APPROOT.'core/attributedef.class.inc.php');
|
||||
|
||||
}
|
||||
|
||||
public function testGetImportColumns()
|
||||
{
|
||||
$oAttributeDefinition = MetaModel::GetAttributeDef("ApplicationSolution", "status");
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
use AttributeText;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
|
||||
class AttributeTextTest extends ItopDataTestCase
|
||||
{
|
||||
protected \Organization $oTestOrganizationForAttributeText;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->oTestOrganizationForAttributeText = $this->CreateOrganization('Test for AttributeTextTest');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AttributeText::RenderWikiHtml
|
||||
*/
|
||||
public function testRenderWikiHtml_nonWikiUrlVariants()
|
||||
{
|
||||
// String value
|
||||
$sInput = 'This hyperlink https://combodo.com should be in an anchor tag.';
|
||||
$sExpected = 'This hyperlink <a href="https://combodo.com">https://combodo.com</a> should be in an anchor tag.';
|
||||
$this->assertEquals($sExpected, AttributeText::RenderWikiHtml($sInput));
|
||||
|
||||
// Empty string value
|
||||
$this->assertEquals('', AttributeText::RenderWikiHtml(''));
|
||||
|
||||
// Null value
|
||||
$this->assertEquals('', AttributeText::RenderWikiHtml(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AttributeText::RenderWikiHtml
|
||||
*/
|
||||
public function testRenderWikiHtml_bWikiOnlyAbsentOrFalse_shouldTransformBothRegularAndWikiHyperlinks()
|
||||
{
|
||||
$sInput = 'A regular hyperlink https://combodo.com and a wiki hyperlink to an existing object [[Organization:'.$this->oTestOrganizationForAttributeText->GetKey().']]';
|
||||
|
||||
// bWikiOnly default value
|
||||
$sResult = AttributeText::RenderWikiHtml($sInput);
|
||||
$this->assertStringContainsString('<a href="https://combodo.com">', $sResult);
|
||||
$this->assertStringContainsString('class="object-ref-link"', $sResult);
|
||||
|
||||
// bWikiOnly = false
|
||||
$sResult = AttributeText::RenderWikiHtml($sInput, false);
|
||||
$this->assertStringContainsString('<a href="https://combodo.com">', $sResult);
|
||||
$this->assertStringContainsString('class="object-ref-link"', $sResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AttributeText::RenderWikiHtml
|
||||
*/
|
||||
public function testRenderWikiHtml_bWikiOnlyToTrue_shouldNotTransformRegularHyperlinkButTransformWikiHyperlink()
|
||||
{
|
||||
$sInput = 'A regular hyperlink https://combodo.com and a wiki hyperlink to an existing object [[Organization:'.$this->oTestOrganizationForAttributeText->GetKey().']]';
|
||||
$sResult = AttributeText::RenderWikiHtml($sInput, true);
|
||||
$this->assertStringNotContainsString('<a href="https://combodo.com">', $sResult);
|
||||
$this->assertStringContainsString('class="object-ref-link"', $sResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers AttributeText::RenderWikiHtml
|
||||
*/
|
||||
public function testRenderWikiHtml_shouldTransformWikiHyperlinkForExistingObjectsOnly()
|
||||
{
|
||||
$sInput = 'A wiki hyperlink to a non existing object [[Organization:123456789]] and a wiki hyperlink to an existing object [[Organization:'.$this->oTestOrganizationForAttributeText->GetKey().']]';
|
||||
$sResult = AttributeText::RenderWikiHtml($sInput);
|
||||
$this->assertStringContainsString('wiki_broken_link', $sResult);
|
||||
$this->assertStringContainsString('class="object-ref-link"', $sResult);
|
||||
}
|
||||
}
|
||||
@@ -59,4 +59,43 @@ class InlineImageTest extends ItopDataTestCase
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers InlineImage::FixUrls
|
||||
*/
|
||||
public function testFixUrls_shouldReturnAnEmptyStringIfNullOrEmptyStringPassed()
|
||||
{
|
||||
$sResult = InlineImage::FixUrls(null);
|
||||
$this->assertEquals('', $sResult);
|
||||
|
||||
$sResult = InlineImage::FixUrls('');
|
||||
$this->assertEquals('', $sResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers InlineImage::FixUrls
|
||||
*/
|
||||
public function testFixUrls_shouldReturnUnchangedValueIfValueContainsNoImage()
|
||||
{
|
||||
$sHtml = '<div><p>Texte sans image</p></div>';
|
||||
$sResult = InlineImage::FixUrls($sHtml);
|
||||
$this->assertEquals($sHtml, $sResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers InlineImage::FixUrls
|
||||
*/
|
||||
public function testFixUrls_shouldReplaceImagesSrcWithCurrentAppRootUrlAndSecret()
|
||||
{
|
||||
$sHtml = <<<HTML
|
||||
<div>
|
||||
<img src="/images/test1.png" data-img-id="123" data-img-secret="abc" />
|
||||
<img src="/images/test2.png" data-img-id="456" data-img-secret="def" />
|
||||
</div>
|
||||
HTML;
|
||||
$sResult = InlineImage::FixUrls($sHtml);
|
||||
$this->assertStringContainsString('<img', $sResult);
|
||||
$this->assertStringContainsString(\utils::EscapeHtml(\utils::GetAbsoluteUrlAppRoot().INLINEIMAGE_DOWNLOAD_URL.'123&s=abc'), $sResult);
|
||||
$this->assertStringContainsString(\utils::EscapeHtml(\utils::GetAbsoluteUrlAppRoot().INLINEIMAGE_DOWNLOAD_URL.'456&s=def'), $sResult);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ class UserLocalTest extends ItopDataTestCase
|
||||
|
||||
],
|
||||
'expectedCheckStatus' => false,
|
||||
'expectedCheckIssues' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.',
|
||||
'expectedCheckIssues' => 'Password must be at least 12 characters and include uppercase, lowercase, numeric and special characters.',
|
||||
'userLanguage' => 'EN US',
|
||||
],
|
||||
'notValidPattern custom message string not array' => [
|
||||
|
||||
@@ -12,10 +12,8 @@ class CliResetSessionTest extends ItopDataTestCase
|
||||
public const USE_TRANSACTION = false;
|
||||
|
||||
private $sCookieFile = "";
|
||||
private $sUrl;
|
||||
private $sLogin;
|
||||
private $sPassword = "Iuytrez9876543ç_è-(";
|
||||
protected $sConfigTmpBackupFile;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
@@ -24,16 +22,13 @@ class CliResetSessionTest extends ItopDataTestCase
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->sConfigTmpBackupFile = tempnam(sys_get_temp_dir(), "config_");
|
||||
MetaModel::GetConfig()->WriteToFile($this->sConfigTmpBackupFile);
|
||||
$this->BackupConfiguration();
|
||||
|
||||
$this->sLogin = "rest-user-".date('dmYHis');
|
||||
$this->CreateTestOrganization();
|
||||
|
||||
$this->sCookieFile = tempnam(sys_get_temp_dir(), 'jsondata_');
|
||||
|
||||
$this->sUrl = \MetaModel::GetConfig()->Get('app_root_url');
|
||||
|
||||
$oRestProfile = \MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", ['name' => 'REST Services User'], true);
|
||||
$oAdminProfile = \MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", ['name' => 'Administrator'], true);
|
||||
|
||||
@@ -47,16 +42,6 @@ class CliResetSessionTest extends ItopDataTestCase
|
||||
{
|
||||
parent::tearDown();
|
||||
|
||||
if (! is_null($this->sConfigTmpBackupFile) && is_file($this->sConfigTmpBackupFile)) {
|
||||
//put config back
|
||||
$sConfigPath = MetaModel::GetConfig()->GetLoadedFile();
|
||||
@chmod($sConfigPath, 0770);
|
||||
$oConfig = new Config($this->sConfigTmpBackupFile);
|
||||
$oConfig->WriteToFile($sConfigPath);
|
||||
@chmod($sConfigPath, 0444);
|
||||
unlink($this->sConfigTmpBackupFile);
|
||||
}
|
||||
|
||||
if (!empty($this->sCookieFile)) {
|
||||
unlink($this->sCookieFile);
|
||||
}
|
||||
@@ -150,26 +135,18 @@ class CliResetSessionTest extends ItopDataTestCase
|
||||
*/
|
||||
private function SendHTTPRequestWithCookies($sUri, $aPostFields, $sForcedLoginMode = null): string
|
||||
{
|
||||
$ch = curl_init();
|
||||
|
||||
curl_setopt($ch, CURLOPT_COOKIEJAR, $this->sCookieFile);
|
||||
curl_setopt($ch, CURLOPT_COOKIEFILE, $this->sCookieFile);
|
||||
|
||||
$sUrl = "$this->sUrl/$sUri";
|
||||
if (!is_null($sForcedLoginMode)) {
|
||||
$sUrl .= "?login_mode=$sForcedLoginMode";
|
||||
$sUri .= "?login_mode=$sForcedLoginMode";
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_URL, $sUrl);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);// set post data to true
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $aPostFields);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_HEADER, 1);
|
||||
// Force disable of certificate check as most of dev / test env have a self-signed certificate
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
|
||||
$sResponse = curl_exec($ch);
|
||||
$aCurlOptions = [
|
||||
CURLOPT_COOKIEJAR => $this->sCookieFile,
|
||||
CURLOPT_COOKIEFILE => $this->sCookieFile,
|
||||
CURLOPT_HEADER => 1,
|
||||
];
|
||||
|
||||
$sResponse = $this->CallItopUri($sUri, $aPostFields, $aCurlOptions);
|
||||
var_dump($this->aLastCurlGetInfo);
|
||||
/** $sResponse example
|
||||
* "HTTP/1.1 200 OK
|
||||
Date: Wed, 07 Jun 2023 05:00:40 GMT
|
||||
@@ -177,16 +154,15 @@ class CliResetSessionTest extends ItopDataTestCase
|
||||
Set-Cookie: itop-2e83d2e9b00e354fdc528621cac532ac=q7ldcjq0rvbn33ccr9q8u8e953; path=/
|
||||
*/
|
||||
//var_dump($sResponse);
|
||||
$iHeaderSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$iHeaderSize = $this->aLastCurlGetInfo['header_size'] ?? 0;
|
||||
$sBody = substr($sResponse, $iHeaderSize);
|
||||
|
||||
//$iHttpCode = intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
|
||||
if (preg_match('/HTTP.* (\d*) /', $sResponse, $aMatches)) {
|
||||
$sHttpCode = $aMatches[1];
|
||||
} else {
|
||||
$sHttpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$sHttpCode = $this->aLastCurlGetInfo['http_code'] ?? -1;
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
$this->assertEquals(200, $sHttpCode, "The test logic assumes that the HTTP request is correctly handled");
|
||||
return $sBody;
|
||||
|
||||
@@ -17,7 +17,6 @@ class RestTest extends ItopDataTestCase
|
||||
public const USE_TRANSACTION = false;
|
||||
public const CREATE_TEST_ORG = false;
|
||||
|
||||
private static $sUrl;
|
||||
private static $sLogin;
|
||||
private static $sPassword = "Iuytrez9876543ç_è-(";
|
||||
|
||||
@@ -44,7 +43,6 @@ class RestTest extends ItopDataTestCase
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
static::$sUrl = MetaModel::GetConfig()->Get('app_root_url');
|
||||
static::$sLogin = "rest-user-".date('dmYHis');
|
||||
|
||||
$this->CreateTestOrganization();
|
||||
@@ -96,7 +94,6 @@ class RestTest extends ItopDataTestCase
|
||||
|
||||
public function testPostJSONDataAsCurlFile()
|
||||
{
|
||||
$sCallbackName = 'fooCallback';
|
||||
$sJsonData = '{"operation": "list_operations"}';
|
||||
|
||||
// Test regular JSON result
|
||||
@@ -297,16 +294,7 @@ JSON;
|
||||
$aPostFields['callback'] = $sCallbackName;
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, static::$sUrl."/webservices/rest.php");
|
||||
curl_setopt($ch, CURLOPT_POST, 1);// set post data to true
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $aPostFields);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
// Force disable of certificate check as most of dev / test env have a self-signed certificate
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
|
||||
$sJson = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
$sJson = $this->CallItopUri('webservices/rest.php', $aPostFields);
|
||||
|
||||
if (!is_null($sTmpFile)) {
|
||||
unlink($sTmpFile);
|
||||
|
||||
Reference in New Issue
Block a user