Compare commits

...

24 Commits

Author SHA1 Message Date
lenaick.moreira
8209c95a9a N°8854 - Replace trigger_error() usage with E_USER_ERROR 2026-02-16 16:56:05 +01:00
lenaick.moreira
5450d16961 N°8851 - Explicit nullable in functions parameters 2026-02-16 16:19:31 +01:00
lenaick.moreira
82b7ef86c7 N°8796 - Refactor PHP CS Fixer configuration for improved readability
* `'indentation_type'` already included in @PSR12 rule set
2026-02-16 15:35:46 +01:00
Eric Espie
4f878536a8 Fix CI 2026-02-05 16:53:02 +01:00
Eric Espie
d937ec0350 Fix CI 2026-02-05 16:24:27 +01:00
Eric Espie
985db46960 N°9193 - Start the KPI logs at the beginning of the http request 2026-02-05 14:57:54 +01:00
v-dumas
01adaadfad N°8492 - Missing accent for 'Categorie' 2026-02-02 14:56:53 +01:00
v-dumas
643752f8e7 N°8378 - Missing rights on incident for SuperUser 2026-01-23 16:24:19 +01:00
v-dumas
0e0c09c420 N°9027 - Add right on WorkOrder transition to SuperUser 2026-01-23 15:55:59 +01:00
Stephen Abello
f34373be6d N°7909 - Missing spacing between fields when columns collapse 2026-01-16 15:30:32 +01:00
odain
4dba47798c ci: fix broken test due to POST CURL file not sent 2025-12-19 18:35:42 +01:00
odain
49a7f3118d ci: fix Array to String Conversion in CallUrl with POST array of array 2025-12-19 16:12:53 +01:00
odain
09afcb229c ci: fix callUrl with posted params 2025-12-19 11:11:03 +01:00
odain
3df4ddc696 ci: fix code style 2025-12-19 10:39:15 +01:00
odain
1fe401c102 ci: rename CallItopUrl by CallUrl + cleanup 2025-12-19 10:01:37 +01:00
odain-cbd
6cb08ba361 Add few APIs to ease tests (#788)
* add few APIs to ease tests

* code style

* log testname via IssueLog only through ItopDataTestCase

* code style

* ci: phpunit fix fatal error
2025-12-18 13:31:48 +01:00
Molkobain
f9db405343 N°6644 - PHP Static Analysis: Update PHPStan to v2.1 2025-12-17 22:21:43 +01:00
Molkobain
4f1f144c51 N°6644 - PHP Static Analysis: Update configuration to:
- Ignore compiled folders other than "env-production"
 - Ignore Lempar.php as its content isn't valid PHP and it won't be included in the baseline
2025-12-17 22:04:49 +01:00
Molkobain
ef8ade5d20 📝 Update unit tests documentation 2025-12-17 18:36:18 +01:00
Molkobain
0b242d872a N°6644 - Tests: Restore proper CI branch, unit tests run and fix a typo 2025-12-17 11:06:36 +01:00
Molkobain
d9261b8342 N°6644 - Tests: Add static analysis for PHP (#536) 2025-12-17 10:45:53 +01:00
odain
4187f552a9 Merge branch 'support/3.2.1' into support/3.2 2025-11-18 15:36:32 +01:00
odain
7f7ce0837e Merge branch 'designer-3.2.1' into support/3.2.1 2025-11-18 15:35:40 +01:00
odain
7df09541ac N°8306 - ci fixes 2025-11-16 08:11:34 +01:00
104 changed files with 838 additions and 466 deletions

View File

@@ -6037,7 +6037,7 @@ JS
* @return void
* @since 3.1.0
*/
final public function AddAttributeFlags(string $sAttCode, int $iFlags, string $sTargetState = '', string $sReason = null): void
final public function AddAttributeFlags(string $sAttCode, int $iFlags, string $sTargetState = '', ?string $sReason = null): void
{
if (!isset($this->aAttributesFlags[$sTargetState])) {
$this->aAttributesFlags[$sTargetState] = [];
@@ -6060,7 +6060,7 @@ JS
* @return void
* @since 3.1.0
*/
final public function ForceAttributeFlags(string $sAttCode, int $iFlags, string $sTargetState = '', string $sReason = null): void
final public function ForceAttributeFlags(string $sAttCode, int $iFlags, string $sTargetState = '', ?string $sReason = null): void
{
if (!isset($this->aAttributesFlags[$sTargetState])) {
$this->aAttributesFlags[$sTargetState] = [];
@@ -6101,7 +6101,7 @@ JS
* @return void
* @since 3.1.0
*/
final public function AddInitialAttributeFlags(string $sAttCode, int $iFlags, string $sReason = null)
final public function AddInitialAttributeFlags(string $sAttCode, int $iFlags, ?string $sReason = null)
{
if (!isset($this->aInitialAttributesFlags)) {
$this->aInitialAttributesFlags = [];
@@ -6123,7 +6123,7 @@ JS
* @return void
* @since 3.1.0
*/
final public function ForceInitialAttributeFlags(string $sAttCode, int $iFlags, string $sReason = null)
final public function ForceInitialAttributeFlags(string $sAttCode, int $iFlags, ?string $sReason = null)
{
if (!isset($this->aInitialAttributesFlags)) {
$this->aInitialAttributesFlags = [];

View File

@@ -34,7 +34,7 @@ interface iNewsroomProvider
* @param User $oUser The user for who to check if the provider is applicable.
* return bool
*/
public function IsApplicable(User $oUser = null);
public function IsApplicable(?User $oUser = null);
/**
* The human readable (localized) label for this provider
@@ -139,7 +139,7 @@ abstract class NewsroomProviderBase implements iNewsroomProvider
*/
abstract public function GetViewAllURL();
public function IsApplicable(User $oUser = null)
public function IsApplicable(?User $oUser = null)
{
return false;
}

View File

@@ -151,7 +151,7 @@ abstract class Query extends cmdbAbstractObject
* @return string|null
* @since 3.1.0
*/
abstract public function GetExportUrl(array $aValues = null): ?string;
abstract public function GetExportUrl(?array $aValues = null): ?string;
/**
* Update last export information.
@@ -227,7 +227,7 @@ class QueryOQL extends Query
}
/** @inheritdoc */
public function GetExportUrl(array $aValues = null): ?string
public function GetExportUrl(?array $aValues = null): ?string
{
try {
// retrieve attributes

View File

@@ -1284,7 +1284,7 @@ class utils
* @throws \CoreException
* @throws \Exception
*/
public static function ExecITopScript(string $sScriptName, array $aArguments, string $sAuthUser = null, string $sAuthPwd = null)
public static function ExecITopScript(string $sScriptName, array $aArguments, ?string $sAuthUser = null, ?string $sAuthPwd = null)
{
$aDisabled = explode(', ', ini_get('disable_functions'));
if (in_array('exec', $aDisabled)) {
@@ -1374,7 +1374,7 @@ class utils
* @return string A path to a folder into which any module can store cache data
* The corresponding folder is created or cleaned upon code compilation
*/
public static function GetCachePath(string $sEnvironment = null): string
public static function GetCachePath(?string $sEnvironment = null): string
{
if (is_null($sEnvironment)) {
$sEnvironment = MetaModel::GetEnvironment();

View File

@@ -912,7 +912,7 @@ abstract class AttributeDefinition
return call_user_func($sComputeFunc);
}
abstract public function GetDefaultValue(DBObject $oHostObject = null);
abstract public function GetDefaultValue(?DBObject $oHostObject = null);
//
// To be overloaded in subclasses
@@ -1476,7 +1476,7 @@ class AttributeDashboard extends AttributeDefinition
return "";
}
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
return null;
}
@@ -1622,7 +1622,7 @@ class AttributeLinkedSet extends AttributeDefinition
* @throws \CoreException
* @throws \CoreWarning
*/
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
if ($oHostObject === null) {
return null;
@@ -2639,7 +2639,7 @@ class AttributeDBFieldVoid extends AttributeDefinition
return $this->Get("sql");
}
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
return $this->MakeRealValue("", $oHostObject);
}
@@ -2728,7 +2728,7 @@ class AttributeDBField extends AttributeDBFieldVoid
return array_merge(parent::ListExpectedParams(), ["default_value", "is_null_allowed"]);
}
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
return $this->MakeRealValue($this->Get("default_value"), $oHostObject);
}
@@ -2917,7 +2917,7 @@ class AttributeObjectKey extends AttributeDBFieldVoid
return "INT(11)".($bFullSpec ? " DEFAULT 0" : "");
}
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
return 0;
}
@@ -3649,7 +3649,7 @@ class AttributeClass extends AttributeString
parent::__construct($sCode, $aParams);
}
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
$sDefault = parent::GetDefaultValue($oHostObject);
if (!$this->IsNullAllowed() && $this->IsNull($sDefault)) {
@@ -3843,7 +3843,7 @@ class AttributeFinalClass extends AttributeString
$this->m_sValue = $sValue;
}
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
return $this->m_sValue;
}
@@ -4727,7 +4727,7 @@ class AttributeCaseLog extends AttributeLongText
}
}
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
return new ormCaseLog();
}
@@ -6125,7 +6125,7 @@ class AttributeDateTime extends AttributeDBField
return $iUnixSeconds;
}
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
$sDefaultValue = $this->Get('default_value');
if (utils::IsNotNullOrEmptyString($sDefaultValue)) {
@@ -6809,7 +6809,7 @@ class AttributeExternalKey extends AttributeDBFieldVoid
return $this->GetOptional('display_style', 'select');
}
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
return 0;
}
@@ -7544,7 +7544,7 @@ class AttributeExternalField extends AttributeDefinition
return $oExtAttDef->GetSQLExpr();
}
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
$oExtAttDef = $this->GetExtAttDef();
@@ -7907,12 +7907,12 @@ class AttributeBlob extends AttributeDefinition
return true;
}
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
return new ormDocument('', '', '');
}
public function IsNullAllowed(DBObject $oHostObject = null)
public function IsNullAllowed(?DBObject $oHostObject = null)
{
return $this->GetOptional("is_null_allowed", false);
}
@@ -8292,7 +8292,7 @@ class AttributeImage extends AttributeBlob
return $oDoc;
}
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
return new ormDocument('', '', '');
}
@@ -8478,7 +8478,7 @@ class AttributeStopWatch extends AttributeDefinition
return true;
}
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
return $this->NewStopWatch();
}
@@ -9345,7 +9345,7 @@ class AttributeSubItem extends AttributeDefinition
return false;
}
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
return null;
}
@@ -9563,7 +9563,7 @@ class AttributeOneWayPassword extends AttributeDefinition implements iAttributeN
return true;
}
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
return "";
}
@@ -10143,7 +10143,7 @@ abstract class AttributeSet extends AttributeDBFieldVoid
return true;
}
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
return null;
}
@@ -11356,7 +11356,7 @@ class AttributeTagSet extends AttributeSet
return new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems());
}
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
$oTagSet = new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems());
$oTagSet->SetValues([]);
@@ -11864,7 +11864,7 @@ class AttributeFriendlyName extends AttributeDefinition
$this->m_sValue = $sValue;
}
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
return $this->m_sValue;
}
@@ -12027,7 +12027,7 @@ class AttributeRedundancySettings extends AttributeDBField
return 20;
}
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
$sRet = 'disabled';
if ($this->Get('enabled')) {
@@ -12463,7 +12463,7 @@ class AttributeCustomFields extends AttributeDefinition
return false;
} // See ReadValue...
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
return new ormCustomFieldsValue($oHostObject, $this->GetCode());
}
@@ -13050,7 +13050,7 @@ class AttributeObsolescenceFlag extends AttributeBoolean
return null;
}
public function GetDefaultValue(DBObject $oHostObject = null)
public function GetDefaultValue(?DBObject $oHostObject = null)
{
return $this->MakeRealValue(false, $oHostObject);
}

View File

@@ -199,7 +199,7 @@ class CellStatus_SearchIssue extends CellStatus_Issue
* @param null $sAllowedValues : used for additional message that provides allowed values $sAllowedValues for current class
* @param string|null $sAllowedValuesSearch : used to search all allowed values
*/
public function __construct($sSerializedSearch, $sReason, $sClass = null, $sAllowedValues = null, string $sAllowedValuesSearch = null)
public function __construct($sSerializedSearch, $sReason, $sClass = null, $sAllowedValues = null, ?string $sAllowedValuesSearch = null)
{
parent::__construct(null, null, $sReason);
$this->sSerializedSearch = $sSerializedSearch;
@@ -873,7 +873,7 @@ class BulkChange
return $aResults;
}
protected function CreateObject(&$aResult, $iRow, $aRowData, CMDBChange $oChange = null)
protected function CreateObject(&$aResult, $iRow, $aRowData, ?CMDBChange $oChange = null)
{
$oTargetObj = MetaModel::NewObject($this->m_sClass);
@@ -962,7 +962,7 @@ class BulkChange
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
protected function UpdateObject(&$aResult, $iRow, $oTargetObj, $aRowData, CMDBChange $oChange = null)
protected function UpdateObject(&$aResult, $iRow, $oTargetObj, $aRowData, ?CMDBChange $oChange = null)
{
$aResult[$iRow] = $this->PrepareObject($oTargetObj, $aRowData, $aErrors);
@@ -1005,7 +1005,7 @@ class BulkChange
*
* @throws \BulkChangeException
*/
protected function UpdateMissingObject(&$aResult, $iRow, $oTargetObj, CMDBChange $oChange = null)
protected function UpdateMissingObject(&$aResult, $iRow, $oTargetObj, ?CMDBChange $oChange = null)
{
$aResult[$iRow] = $this->PrepareMissingObject($oTargetObj, $aErrors);
@@ -1040,7 +1040,7 @@ class BulkChange
}
}
public function Process(CMDBChange $oChange = null)
public function Process(?CMDBChange $oChange = null)
{
if ($oChange) {
CMDBObject::SetCurrentChange($oChange);

View File

@@ -2569,7 +2569,7 @@ abstract class DBObject implements iDisplay
*
* @see \RestUtils::FindObjectFromKey for the same check in the REST endpoint
*/
final public function CheckChangedExtKeysValues(callable $oIsObjectLoadableCallback = null)
final public function CheckChangedExtKeysValues(?callable $oIsObjectLoadableCallback = null)
{
if (is_null($oIsObjectLoadableCallback)) {
$oIsObjectLoadableCallback = function ($sClass, $sId) {
@@ -3729,7 +3729,7 @@ abstract class DBObject implements iDisplay
* @throws \MySQLException
* @throws \OQLException
*/
private function ActivateOnObjectUpdateTriggers(?DBObject $oObject, array $aAttributes = null): void
private function ActivateOnObjectUpdateTriggers(?DBObject $oObject, ?array $aAttributes = null): void
{
if (is_null($oObject)) {
return;

View File

@@ -724,7 +724,7 @@ abstract class DBSearch
*
* @throws OQLException
*/
public static function FromOQL($sQuery, $aParams = null, ModelReflection $oMetaModel = null)
public static function FromOQL($sQuery, $aParams = null, ?ModelReflection $oMetaModel = null)
{
if (empty($sQuery)) {
return null;

View File

@@ -453,7 +453,7 @@ class DesignElement extends \DOMElement
* @throws Exception
* @since 3.1.2 3.2.0 N°6974
*/
public static function _FindNode(DOMNode $oParent, DesignElement $oRefNode, string $sSearchId = null): ?DesignElement
public static function _FindNode(DOMNode $oParent, DesignElement $oRefNode, ?string $sSearchId = null): ?DesignElement
{
$oNodes = self::_FindNodes($oParent, $oRefNode, $sSearchId);
if ($oNodes instanceof DOMNodeList) {
@@ -477,7 +477,7 @@ class DesignElement extends \DOMElement
* @return \DOMNodeList|false|mixed
* @since 3.1.2 3.2.0 N°6974
*/
public static function _FindNodes(DOMNode $oParent, DesignElement $oRefNode, string $sSearchId = null)
public static function _FindNodes(DOMNode $oParent, DesignElement $oRefNode, ?string $sSearchId = null)
{
if ($oParent instanceof DOMDocument) {
$oDoc = $oParent->firstChild->ownerDocument;

View File

@@ -632,7 +632,7 @@ class DisplayableGroupNode extends DisplayableNode
$this->aObjects = [];
}
public function AddObject(DBObject $oObj = null)
public function AddObject(?DBObject $oObj = null)
{
if (is_object($oObj)) {
$sPrevClass = $this->GetObjectClass();

View File

@@ -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;
}
}

View File

@@ -1656,7 +1656,7 @@ class PHP_ParserGenerator_Data
function emit_code($out, PHP_ParserGenerator_Rule $rp, &$lineno)
{
$linecnt = 0;
/* Generate code to do the reduce action */
if ($rp->code) {
$this->tplt_linedir($out, $rp->line, $this->filename);

View File

@@ -1094,7 +1094,7 @@ static public $yy_action = array(
function yy_find_shift_action($iLookAhead)
{
$stateno = $this->yystack[$this->yyidx]->stateno;
/* if ($this->yyidx < 0) return self::YY_NO_ACTION; */
if (!isset(self::$yy_shift_ofst[$stateno])) {
// no shift actions
@@ -1781,7 +1781,7 @@ throw new OQLParserParseFailureException($this->m_sSourceQuery, $this->m_iLine,
function yy_syntax_error($yymajor, $TOKEN)
{
#line 25 "..\oql-parser.y"
throw new OQLParserSyntaxErrorException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN);
#line 1793 "..\oql-parser.php"
}
@@ -1820,7 +1820,7 @@ throw new OQLParserSyntaxErrorException($this->m_sSourceQuery, $this->m_iLine, $
// $yyact; /* The parser action. */
// $yyendofinput; /* True if we are at the end of input */
$yyerrorhit = 0; /* True if yymajor has invoked an error */
/* (re)initialize the parser, if necessary */
if ($this->yyidx === null || $this->yyidx < 0) {
/* if ($yymajor == 0) return; // not sure why this was here... */
@@ -1833,7 +1833,7 @@ throw new OQLParserSyntaxErrorException($this->m_sSourceQuery, $this->m_iLine, $
array_push($this->yystack, $x);
}
$yyendofinput = ($yymajor==0);
if (self::$yyTraceFILE) {
fprintf(
self::$yyTraceFILE,
@@ -1842,7 +1842,7 @@ throw new OQLParserSyntaxErrorException($this->m_sSourceQuery, $this->m_iLine, $
self::$yyTokenName[$yymajor]
);
}
do {
$yyact = $this->yy_find_shift_action($yymajor);
if ($yymajor < self::YYERRORSYMBOL
@@ -2016,7 +2016,7 @@ class OQLParser extends OQLParserRaw
$this->m_sSourceQuery = $sQuery;
// no constructor - parent::__construct();
}
public function doParse($token, $value, $iCurrPosition = 0)
{
$this->m_iColPrev = $this->m_iCol;
@@ -2030,7 +2030,7 @@ class OQLParser extends OQLParserRaw
$this->doParse(0, 0);
return $this->my_result;
}
public function __destruct()
{
// Bug in the original destructor, causing an infinite loop !

View File

@@ -370,7 +370,7 @@ class ormCaseLog
/**
* Produces an HTML representation, aimed at being used within the iTop framework
*/
public function GetAsHTML(WebPage $oP = null, $bEditMode = false, $aTransfoHandler = null)
public function GetAsHTML(?WebPage $oP = null, $bEditMode = false, $aTransfoHandler = null)
{
$bPrintableVersion = (utils::ReadParam('printable', '0') == '1');

View File

@@ -93,7 +93,7 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
* @param DBObjectSet|null $oOriginalSet
* @throws Exception
*/
public function __construct($sHostClass, $sAttCode, DBObjectSet $oOriginalSet = null)
public function __construct($sHostClass, $sAttCode, ?DBObjectSet $oOriginalSet = null)
{
$this->sHostClass = $sHostClass;
$this->sAttCode = $sAttCode;

View File

@@ -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;
}

View File

@@ -24,7 +24,7 @@ class DBRestore extends DBBackup
/** @var string */
private $sDBUser;
public function __construct(\Config $oConfig = null)
public function __construct(?\Config $oConfig = null)
{
parent::__construct($oConfig);

View File

@@ -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+' => '',

View File

@@ -18,7 +18,7 @@ class HubNewsroomProvider extends NewsroomProviderBase
* {@inheritDoc}
* @see NewsroomProviderBase::IsApplicable()
*/
public function IsApplicable(User $oUser = null)
public function IsApplicable(?User $oUser = null)
{
if ($oUser !== null) {
return UserRights::IsAdministrator($oUser);

View File

@@ -27,7 +27,7 @@ class PortalCollector extends AbstractDataCollector
}
/** @inheritdoc */
public function collect(Request $request, Response $response, Throwable $exception = null): void
public function collect(Request $request, Response $response, ?Throwable $exception = null): void
{
$oRegister = $this->oTemplatesProviderService->GetRegister();
$aTemplatesDefinitions = $oRegister->GetTemplatesDefinitions();

View File

@@ -129,7 +129,7 @@ class ObjectFormHandlerHelper
* @throws \OQLException
* @throws \Exception
*/
public function HandleForm(Request $oRequest, $sMode, $sObjectClass, $sObjectId = null, array $aFormProperties = null)
public function HandleForm(Request $oRequest, $sMode, $sObjectClass, $sObjectId = null, ?array $aFormProperties = null)
{
$aFormData = [];
$sOperation = $this->oRequestManipulator->ReadParam('operation', '');

View File

@@ -40,7 +40,7 @@ class AppVariable implements ArrayAccess
/** @var DecoratedAppVariable */
private $decorated;
public function __construct(DecoratedAppVariable $decorated, ContainerInterface $container = null)
public function __construct(DecoratedAppVariable $decorated, ?ContainerInterface $container = null)
{
$this->decorated = $decorated;
$this->container = $container;

View File

@@ -57,7 +57,7 @@ class TemplatesTwigExtension extends AbstractExtension
* @return string the template path
* @throws \ReflectionException
*/
public function GetTemplate(string $sId, string $sProviderClass = self::DEFAULT_PROVIDER_CLASS, object $oProviderInstance = null): string
public function GetTemplate(string $sId, string $sProviderClass = self::DEFAULT_PROVIDER_CLASS, ?object $oProviderInstance = null): string
{
if ($oProviderInstance === null) {
return $this->oTemplatesService->GetTemplatePath($sProviderClass, $sId);

View File

@@ -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>

View File

@@ -152,7 +152,7 @@ class DBBackup
* @return string Name of the backup file WITHOUT the file extension (eg. `.tar.gz`)
* @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 = "__DB__-%Y-%m-%d", ?DateTime $oDateTime = null)
{
if ($oDateTime === null) {
$oDateTime = new DateTime();

View File

@@ -38,7 +38,7 @@ class DOMFormatException extends Exception
* @param $previous
* @param DesignElement|null $node DOMNode causing the DOMFormatException
*/
public function __construct($message, $code = 0, $previous = null, DesignElement $node = null)
public function __construct($message, $code = 0, $previous = null, ?DesignElement $node = null)
{
if ($node !== null) {
$message .= ' ('.MFDocument::GetItopNodePath($node).' at line '.$node->getLineNo().')';
@@ -930,7 +930,7 @@ EOF
*
* @since 3.0.0 Add param. $bAddQuotes to be equivalent to {@see self::GetMandatoryPropString} and allow retrieving property without surrounding single quotes
*/
protected function GetPropString($oNode, string $sTag, string $sDefault = null, bool $bAddQuotes = true)
protected function GetPropString($oNode, string $sTag, ?string $sDefault = null, bool $bAddQuotes = true)
{
$val = $oNode->GetChildText($sTag);
if (is_null($val)) {

View File

@@ -2399,7 +2399,7 @@ class MFDocument extends \Combodo\iTop\DesignDocument
*/
// Return type union is not supported by PHP 7.4, we can remove the following PHP attribute and add the return type once iTop min PHP version is PHP 8.0+
#[\ReturnTypeWillChange]
public function saveXML(DOMNode $node = null, $options = 0)
public function saveXML(?DOMNode $node = null, $options = 0)
{
$oRootNode = $this->firstChild;
if (!$oRootNode) {

View File

@@ -126,7 +126,7 @@ class CKEditorHelper
*
* @return string|null
*/
public static function PrepareCKEditorValueTextEncodingForTextarea(string $sValue = null): ?string
public static function PrepareCKEditorValueTextEncodingForTextarea(?string $sValue = null): ?string
{
if ($sValue === null) {
return null;
@@ -145,7 +145,7 @@ class CKEditorHelper
*
* @return void
*/
public static function ConfigureCKEditorElementForWebPage(WebPage $oPage, string $sInputElementId, string $sInitialValue = null, bool $bWithMentions = false, array $aOverloadConfiguration = []): void
public static function ConfigureCKEditorElementForWebPage(WebPage $oPage, string $sInputElementId, ?string $sInitialValue = null, bool $bWithMentions = false, array $aOverloadConfiguration = []): void
{
// link CKEditor JS files
foreach (static::GetJSFilesRelPathsForCKEditor() as $sFile) {
@@ -187,7 +187,7 @@ class CKEditorHelper
*
* @return void
*/
public static function ConfigureCKEditorElementForRenderingOutput(RenderingOutput $oOutput, string $sInputElementId, string $sInitialValue = null, bool $bWithMentions = false, bool $bAddJSFiles = true, array $aOverloadConfiguration = []): void
public static function ConfigureCKEditorElementForRenderingOutput(RenderingOutput $oOutput, string $sInputElementId, ?string $sInitialValue = null, bool $bWithMentions = false, bool $bAddJSFiles = true, array $aOverloadConfiguration = []): void
{
// link CKEditor JS files
if ($bAddJSFiles) {
@@ -282,7 +282,7 @@ HTML;
* @throws \ConfigException
* @throws \CoreException
*/
public static function GetDOMSanitizerForCKEditor(DOMSanitizer $oSanitizer = null): array
public static function GetDOMSanitizerForCKEditor(?DOMSanitizer $oSanitizer = null): array
{
if ($oSanitizer === null) {
/* @var $oSanitizer DOMSanitizer */

View File

@@ -18,7 +18,7 @@ use User;
*/
class iTopNewsroomProvider extends NewsroomProviderBase
{
public function IsApplicable(User $oUser = null)
public function IsApplicable(?User $oUser = null)
{
return true;
}

View File

@@ -16,7 +16,7 @@ class Status
* @throws \DictExceptionUnknownLanguage
* @throws \MySQLException
*/
public function __construct(Config $oConfig = null)
public function __construct(?Config $oConfig = null)
{
$this->StatusStartup($oConfig);
}
@@ -74,7 +74,7 @@ class Status
* @throws \DictExceptionUnknownLanguage
* @throws \MySQLException
*/
private function StatusStartup(Config $oConfig = null)
private function StatusStartup(?Config $oConfig = null)
{
$this->StatusCheckConfigFile();

View File

@@ -96,7 +96,7 @@ class Button extends UIBlock
* @param string $sJsCode
* @param string $sOnClickJsCode
*/
public function __construct(string $sLabel, string $sId = null, string $sTooltip = '', string $sIconClass = '', string $sActionType = self::DEFAULT_ACTION_TYPE, string $sColorScheme = self::DEFAULT_COLOR_SCHEME, string $sJsCode = '', string $sOnClickJsCode = '')
public function __construct(string $sLabel, ?string $sId = null, string $sTooltip = '', string $sIconClass = '', string $sActionType = self::DEFAULT_ACTION_TYPE, string $sColorScheme = self::DEFAULT_COLOR_SCHEME, string $sJsCode = '', string $sOnClickJsCode = '')
{
// We only use resource ID (not sanitized) on button for now, but this might be reworked back into \UIBlock if needed
if (!is_null($sId)) {

View File

@@ -67,7 +67,7 @@ class ButtonJS extends Button
*/
public function __construct(
string $sLabel,
string $sId = null,
?string $sId = null,
string $sName = '',
string $sValue = '',
string $sType = self::DEFAULT_TYPE,

View File

@@ -55,7 +55,7 @@ class ButtonUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\Button\Button
*/
public static function MakeNeutral(string $sLabel, string $sName = null, ?string $sId = null)
public static function MakeNeutral(string $sLabel, ?string $sName = null, ?string $sId = null)
{
$oButton = new ButtonJS($sLabel, $sId);
$oButton->SetActionType(Button::ENUM_ACTION_TYPE_REGULAR)
@@ -81,8 +81,8 @@ class ButtonUIBlockFactory extends AbstractUIBlockFactory
*/
public static function MakeForPrimaryAction(
string $sLabel,
string $sName = null,
string $sValue = null,
?string $sName = null,
?string $sValue = null,
bool $bIsSubmit = false,
?string $sId = null
) {
@@ -102,8 +102,8 @@ class ButtonUIBlockFactory extends AbstractUIBlockFactory
*/
public static function MakeForSecondaryAction(
string $sLabel,
string $sName = null,
string $sValue = null,
?string $sName = null,
?string $sValue = null,
bool $bIsSubmit = false,
?string $sId = null
) {
@@ -123,8 +123,8 @@ class ButtonUIBlockFactory extends AbstractUIBlockFactory
*/
public static function MakeForPositiveAction(
string $sLabel,
string $sName = null,
string $sValue = null,
?string $sName = null,
?string $sValue = null,
bool $bIsSubmit = false,
?string $sId = null
) {
@@ -145,8 +145,8 @@ class ButtonUIBlockFactory extends AbstractUIBlockFactory
*/
public static function MakeForDestructiveAction(
string $sLabel,
string $sName = null,
string $sValue = null,
?string $sName = null,
?string $sValue = null,
bool $bIsSubmit = false,
?string $sId = null
) {
@@ -178,8 +178,8 @@ class ButtonUIBlockFactory extends AbstractUIBlockFactory
*/
public static function MakeAlternativeNeutral(
string $sLabel,
string $sName = null,
string $sValue = null,
?string $sName = null,
?string $sValue = null,
bool $bIsSubmit = false,
?string $sId = null
) {
@@ -208,8 +208,8 @@ class ButtonUIBlockFactory extends AbstractUIBlockFactory
*/
public static function MakeForAlternativePrimaryAction(
string $sLabel,
string $sName = null,
string $sValue = null,
?string $sName = null,
?string $sValue = null,
bool $bIsSubmit = false,
?string $sId = null
) {
@@ -237,8 +237,8 @@ class ButtonUIBlockFactory extends AbstractUIBlockFactory
*/
public static function MakeForAlternativeSecondaryAction(
string $sLabel,
string $sName = null,
string $sValue = null,
?string $sName = null,
?string $sValue = null,
bool $bIsSubmit = false,
?string $sId = null
) {
@@ -266,8 +266,8 @@ class ButtonUIBlockFactory extends AbstractUIBlockFactory
*/
public static function MakeForAlternativeValidationAction(
string $sLabel,
string $sName = null,
string $sValue = null,
?string $sName = null,
?string $sValue = null,
bool $bIsSubmit = false,
?string $sId = null
) {
@@ -295,8 +295,8 @@ class ButtonUIBlockFactory extends AbstractUIBlockFactory
*/
public static function MakeForAlternativeDestructiveAction(
string $sLabel,
string $sName = null,
string $sValue = null,
?string $sName = null,
?string $sValue = null,
bool $bIsSubmit = false,
?string $sId = null
) {
@@ -323,9 +323,9 @@ class ButtonUIBlockFactory extends AbstractUIBlockFactory
* @return \Combodo\iTop\Application\UI\Base\Component\Button\Button
*/
public static function MakeForCancel(
string $sLabel = null,
string $sName = null,
string $sValue = null,
?string $sLabel = null,
?string $sName = null,
?string $sValue = null,
bool $bIsSubmit = false,
?string $sId = null
) {
@@ -356,8 +356,8 @@ class ButtonUIBlockFactory extends AbstractUIBlockFactory
public static function MakeIconAction(
string $sIconClasses,
string $sTooltipText = '',
string $sName = null,
string $sValue = null,
?string $sName = null,
?string $sValue = null,
bool $bIsSubmit = false,
?string $sId = null
) {
@@ -483,8 +483,8 @@ class ButtonUIBlockFactory extends AbstractUIBlockFactory
string $sLabel,
string $sColor,
string $sActionType,
string $sValue = null,
string $sName = null,
?string $sValue = null,
?string $sName = null,
bool $bIsSubmit = false,
?string $sId = null
) {
@@ -528,7 +528,7 @@ class ButtonUIBlockFactory extends AbstractUIBlockFactory
string $sURL,
string $sColor,
string $sActionType,
string $sTarget = null,
?string $sTarget = null,
?string $sId = null
) {
$oButton = new ButtonURL($sLabel, $sURL, $sId, $sTarget);

View File

@@ -67,7 +67,7 @@ class ButtonURL extends Button
public function __construct(
string $sLabel,
string $sURL,
string $sId = null,
?string $sId = null,
string $sTarget = self::DEFAULT_TARGET,
string $sTooltip = '',
string $sIconClass = '',

View File

@@ -14,7 +14,7 @@ class DashletContainer extends UIContentBlock
public const BLOCK_CODE = 'ibo-dashlet';
public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/layouts/content-block/layout';
public function __construct(string $sId = null, array $aContainerClasses = [])
public function __construct(?string $sId = null, array $aContainerClasses = [])
{
parent::__construct($sId, $aContainerClasses);

View File

@@ -22,12 +22,12 @@ class DashletFactory
return new DashletBadge($sClassIconUrl, $sHyperlink, $iCount, $sClassLabel, $sCreateActionUrl, $sCreateActionLabel, $aRefreshParams);
}
public static function MakeForDashletHeaderStatic(string $sTitle, string $sIconUrl, string $sId = null): DashletHeaderStatic
public static function MakeForDashletHeaderStatic(string $sTitle, string $sIconUrl, ?string $sId = null): DashletHeaderStatic
{
return new DashletHeaderStatic($sTitle, $sIconUrl, $sId);
}
public static function MakeForDashletPlainText(string $sText, string $sId = null): DashletPlainText
public static function MakeForDashletPlainText(string $sText, ?string $sId = null): DashletPlainText
{
return new DashletPlainText($sText, $sId);
}

View File

@@ -33,7 +33,7 @@ class DashletHeaderStatic extends DashletContainer
* @param string $sIconUrl
* @param string|null $sId
*/
public function __construct(string $sTitle, string $sIconUrl, string $sId = null)
public function __construct(string $sTitle, string $sIconUrl, ?string $sId = null)
{
parent::__construct($sId);

View File

@@ -29,7 +29,7 @@ class DashletPlainText extends DashletContainer
*
* @param string $sText
*/
public function __construct(string $sText, string $sId = null)
public function __construct(string $sText, ?string $sId = null)
{
parent::__construct($sId);

View File

@@ -855,7 +855,7 @@ JS;
*
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
*/
public static function MakeForStaticData(string $sTitle, array $aColumns, array $aData, ?string $sId = null, array $aExtraParams = [], string $sFilter = "", array $aOptions = [], array $aRowActions = null)
public static function MakeForStaticData(string $sTitle, array $aColumns, array $aData, ?string $sId = null, array $aExtraParams = [], string $sFilter = "", array $aOptions = [], ?array $aRowActions = null)
{
$oBlock = new UIContentBlock();
if ($sTitle != "") {
@@ -896,7 +896,7 @@ JS;
*
* @return \Combodo\iTop\Application\UI\Base\Component\DataTable\StaticTable\FormTable\FormTable
*/
public static function MakeForForm(string $sRef, array $aColumns, array $aData = [], string $sFilter = '', array $aRowActions = null)
public static function MakeForForm(string $sRef, array $aColumns, array $aData = [], string $sFilter = '', ?array $aRowActions = null)
{
$oTable = new FormTable("datatable_".$sRef);
$oTable->SetRef($sRef);

View File

@@ -67,7 +67,7 @@ class StaticTable extends UIContentBlock
*/
private $aOptions;
public function __construct(string $sId = null, array $aContainerCSSClasses = [], array $aExtraParams = [])
public function __construct(?string $sId = null, array $aContainerCSSClasses = [], array $aExtraParams = [])
{
parent::__construct($sId, $aContainerCSSClasses);
$this->aColumns = [];

View File

@@ -57,7 +57,7 @@ class Field extends UIContentBlock
/** @var string */
protected $sComments;
public function __construct(string $sLabel, UIBlock $oValue = null, ?string $sId = null)
public function __construct(string $sLabel, ?UIBlock $oValue = null, ?string $sId = null)
{
parent::__construct($sId);
$this->sLabel = $sLabel;

View File

@@ -19,7 +19,7 @@ class FieldBadge extends UIContentBlock
// Overloaded constants
public const BLOCK_CODE = 'ibo-field-badge';
public function __construct(string $sId = null, array $aContainerClasses = [])
public function __construct(?string $sId = null, array $aContainerClasses = [])
{
parent::__construct($sId, $aContainerClasses);
}

View File

@@ -25,7 +25,7 @@ class Form extends UIContentBlock
/** @var string */
protected $sAction;
public function __construct(string $sId = null)
public function __construct(?string $sId = null)
{
parent::__construct($sId);
$this->sOnSubmitJsCode = null;

View File

@@ -30,7 +30,7 @@ class FormUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\Form\Form An HTML form in which you can add UIBlocks
*/
public static function MakeStandard(string $sId = null)
public static function MakeStandard(?string $sId = null)
{
return new Form($sId);
}

View File

@@ -26,7 +26,7 @@ class FileSelect extends UIBlock
/** @var bool */
private $bShowFilename;
public function __construct(string $sName, string $sId = null)
public function __construct(string $sName, ?string $sId = null)
{
parent::__construct($sId);
$this->sName = $sName;

View File

@@ -31,7 +31,7 @@ class FileSelectUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\Input\FileSelect\FileSelect A styled file input selector
*/
public static function MakeStandard(string $sName, string $sId = null)
public static function MakeStandard(string $sName, ?string $sId = null)
{
return new FileSelect($sName, $sId);
}

View File

@@ -36,7 +36,7 @@ class AjaxDataProviderForOQL extends AjaxDataProvider
* @param string|null $sWizardHelperJsVarName Wizard helper name
* @param array $aFieldsToLoad Array of fields to load
*/
public function __construct(string $sObjectClass, string $sOql, string $sWizardHelperJsVarName = null, array $aFieldsToLoad = [])
public function __construct(string $sObjectClass, string $sOql, ?string $sWizardHelperJsVarName = null, array $aFieldsToLoad = [])
{
parent::__construct('object.search', [
'object_class' => $sObjectClass,

View File

@@ -107,7 +107,7 @@ class Set extends AbstractInput
*
* @param string|null $sId Block identifier
*/
public function __construct(string $sId = null)
public function __construct(?string $sId = null)
{
parent::__construct($sId);

View File

@@ -138,7 +138,7 @@ class SetUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\Input\Set\Set
*/
public static function MakeForOQL(string $sId, string $sObjectClass, string $sOql, string $sWizardHelperJsVarName = null, array $aFieldsToLoad = [], ?string $sGroupField = null, string $sName = ''): Set
public static function MakeForOQL(string $sId, string $sObjectClass, string $sOql, ?string $sWizardHelperJsVarName = null, array $aFieldsToLoad = [], ?string $sGroupField = null, string $sName = ''): Set
{
// Create set ui block
$oSetUIBlock = new Set($sId);

View File

@@ -36,7 +36,7 @@ class DoNotShowAgainOptionBlock extends UIContentBlock
*
* @param string|null $sId
*/
public function __construct(string $sId = null)
public function __construct(?string $sId = null)
{
parent::__construct($sId, ['ibo-modal-option--do-not-show-again']);

View File

@@ -48,7 +48,7 @@ class PanelUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\Panel\Panel
*/
public static function MakeNeutral(string $sTitle, string $sSubTitle = null)
public static function MakeNeutral(string $sTitle, ?string $sSubTitle = null)
{
$oPanel = new Panel($sTitle);
if (!is_null($sSubTitle)) {
@@ -68,7 +68,7 @@ class PanelUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\Panel\Panel
*/
public static function MakeForInformation(string $sTitle, string $sSubTitle = null)
public static function MakeForInformation(string $sTitle, ?string $sSubTitle = null)
{
$oPanel = new Panel($sTitle);
if (!is_null($sSubTitle)) {
@@ -88,7 +88,7 @@ class PanelUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\Panel\Panel
*/
public static function MakeForSuccess(string $sTitle, string $sSubTitle = null)
public static function MakeForSuccess(string $sTitle, ?string $sSubTitle = null)
{
$oPanel = new Panel($sTitle);
if (!is_null($sSubTitle)) {
@@ -108,7 +108,7 @@ class PanelUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\Panel\Panel
*/
public static function MakeForWarning(string $sTitle, string $sSubTitle = null)
public static function MakeForWarning(string $sTitle, ?string $sSubTitle = null)
{
$oPanel = new Panel($sTitle);
if (!is_null($sSubTitle)) {
@@ -128,7 +128,7 @@ class PanelUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\Panel\Panel
*/
public static function MakeForDanger(string $sTitle, string $sSubTitle = null)
public static function MakeForDanger(string $sTitle, ?string $sSubTitle = null)
{
$oPanel = new Panel($sTitle);
if (!is_null($sSubTitle)) {
@@ -148,7 +148,7 @@ class PanelUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\Panel\Panel
*/
public static function MakeForFailure(string $sTitle, string $sSubTitle = null)
public static function MakeForFailure(string $sTitle, ?string $sSubTitle = null)
{
$oPanel = new Panel($sTitle);
if (!is_null($sSubTitle)) {
@@ -168,7 +168,7 @@ class PanelUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\Panel\Panel
*/
public static function MakeWithBrandingPrimaryColor(string $sTitle, string $sSubTitle = null)
public static function MakeWithBrandingPrimaryColor(string $sTitle, ?string $sSubTitle = null)
{
$oPanel = new Panel($sTitle);
if (!is_null($sSubTitle)) {
@@ -188,7 +188,7 @@ class PanelUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\Panel\Panel
*/
public static function MakeWithBrandingSecondaryColor(string $sTitle, string $sSubTitle = null)
public static function MakeWithBrandingSecondaryColor(string $sTitle, ?string $sSubTitle = null)
{
$oPanel = new Panel($sTitle);
if (!is_null($sSubTitle)) {
@@ -209,7 +209,7 @@ class PanelUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\Panel\Panel
*/
public static function MakeForClass(string $sClass, string $sTitle, string $sSubTitle = null)
public static function MakeForClass(string $sClass, string $sTitle, ?string $sSubTitle = null)
{
$oPanel = new Panel($sTitle);
if (!is_null($sSubTitle)) {

View File

@@ -31,7 +31,7 @@ class ToolbarSeparatorUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\Toolbar\Separator\VerticalSeparator
*/
public static function MakeVertical(string $sId = null)
public static function MakeVertical(?string $sId = null)
{
return new VerticalSeparator($sId);
}

View File

@@ -20,7 +20,7 @@ class Toolbar extends UIContentBlock
public const BLOCK_CODE = 'ibo-toolbar';
public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/components/toolbar/layout';
public function __construct(string $sId = null, array $aContainerClasses = [])
public function __construct(?string $sId = null, array $aContainerClasses = [])
{
parent::__construct($sId, $aContainerClasses);
}

View File

@@ -30,7 +30,7 @@ class ToolbarSpacerUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\Toolbar\ToolbarSpacer\ToolbarSpacer
*/
public static function MakeStandard(string $sId = null)
public static function MakeStandard(?string $sId = null)
{
return new ToolbarSpacer($sId);
}

View File

@@ -30,7 +30,7 @@ class ToolbarUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\Toolbar\Toolbar
*/
public static function MakeForAction(string $sId = null)
public static function MakeForAction(?string $sId = null)
{
return new Toolbar($sId, ['ibo-toolbar--action']);
}
@@ -42,7 +42,7 @@ class ToolbarUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\Toolbar\Toolbar
*/
public static function MakeStandard(string $sId = null, array $aContainerClasses = [])
public static function MakeStandard(?string $sId = null, array $aContainerClasses = [])
{
return new Toolbar($sId, $aContainerClasses);
}
@@ -54,7 +54,7 @@ class ToolbarUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\Toolbar\Toolbar
*/
public static function MakeForButton(string $sId = null, array $aContainerClasses = [])
public static function MakeForButton(?string $sId = null, array $aContainerClasses = [])
{
return new Toolbar($sId, array_merge($aContainerClasses, ['ibo-toolbar--button']));
}

View File

@@ -62,7 +62,7 @@ class CaseLogEntryForm extends UIContentBlock
* @param \DBObject $oObject
* @param string|null $sId
*/
public function __construct(DBObject $oObject, string $sAttCode, string $sId = null)
public function __construct(DBObject $oObject, string $sAttCode, ?string $sId = null)
{
parent::__construct($sId);
$this->oObject = $oObject;

View File

@@ -107,7 +107,7 @@ class NavigationMenu extends UIBlock implements iKeyboardShortcut
public function __construct(
ApplicationContext $oAppContext,
PopoverMenu $oUserMenu,
NewsroomMenu $oNewsroomMenu = null,
?NewsroomMenu $oNewsroomMenu = null,
?string $sId = null
) {
parent::__construct($sId);

View File

@@ -59,9 +59,9 @@ class TopBar extends UIBlock
*/
public function __construct(
$sId = null,
QuickCreate $oQuickCreate = null,
GlobalSearch $oGlobalSearch = null,
Breadcrumbs $oBreadcrumbs = null
?QuickCreate $oQuickCreate = null,
?GlobalSearch $oGlobalSearch = null,
?Breadcrumbs $oBreadcrumbs = null
) {
parent::__construct($sId);

View File

@@ -41,7 +41,7 @@ class UIContentBlock extends UIBlock implements iUIContentBlock
* @param string|null $sId
* @param array $aContainerClasses Array of additional CSS classes
*/
public function __construct(string $sId = null, array $aContainerClasses = [])
public function __construct(?string $sId = null, array $aContainerClasses = [])
{
parent::__construct($sId);

View File

@@ -34,7 +34,7 @@ class UIContentBlockUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
*/
public static function MakeStandard(string $sId = null, array $aContainerClasses = [])
public static function MakeStandard(?string $sId = null, array $aContainerClasses = [])
{
return new UIContentBlock($sId, $aContainerClasses);
}
@@ -49,7 +49,7 @@ class UIContentBlockUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
*/
public static function MakeForCode(string $sCode, string $sId = null)
public static function MakeForCode(string $sCode, ?string $sId = null)
{
$sCode = str_replace("\n", '<br>', \utils::HtmlEntities($sCode));
@@ -65,7 +65,7 @@ class UIContentBlockUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
*/
public static function MakeForPreformatted(string $sCode, string $sId = null)
public static function MakeForPreformatted(string $sCode, ?string $sId = null)
{
$sCode = '<pre>'.\utils::HtmlEntities($sCode).'</pre>';

View File

@@ -12,7 +12,7 @@ use Throwable;
class UIException extends Exception
{
public function __construct(iUIBlock $oBlock, string $message = "", int $code = 0, Throwable $previous = null)
public function __construct(iUIBlock $oBlock, string $message = "", int $code = 0, ?Throwable $previous = null)
{
parent::__construct($oBlock->GetId().': '.$message, $code, $previous);
}

View File

@@ -139,7 +139,7 @@ abstract class AbstractBlockLinkSetViewTable extends UIContentBlock
* @throws \ArchivedObjectException
* @throws \CoreException
*/
public function GetDictionaryEntry(string $sKey, DBObject $oDBObject = null)
public function GetDictionaryEntry(string $sKey, ?DBObject $oDBObject = null)
{
return $this->oAttDef->SearchSpecificLabel(
$sKey,

View File

@@ -51,7 +51,7 @@ class LinkSetUIBlockFactory extends SetUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\Input\Set\Set
*/
public static function MakeForLinkSet(string $sId, AttributeLinkedSet $oAttDef, iDBObjectSetIterator $oDbObjectSet, string $sWizardHelperJsVarName, DBObject $oHostDbObject = null): Set
public static function MakeForLinkSet(string $sId, AttributeLinkedSet $oAttDef, iDBObjectSetIterator $oDbObjectSet, string $sWizardHelperJsVarName, ?DBObject $oHostDbObject = null): Set
{
$sTargetClass = LinkSetModel::GetTargetClass($oAttDef);
$sTargetField = LinkSetModel::GetTargetField($oAttDef);

View File

@@ -92,7 +92,7 @@ class AjaxPage extends WebPage implements iTabbedPage
* @inheritDoc
* @throws \Exception
*/
public function AddTabContainer($sTabContainer, $sPrefix = '', iUIContentBlock $oParentBlock = null)
public function AddTabContainer($sTabContainer, $sPrefix = '', ?iUIContentBlock $oParentBlock = null)
{
if (is_null($oParentBlock)) {
$oParentBlock = PanelUIBlockFactory::MakeNeutral('');

View File

@@ -248,7 +248,7 @@ class TabManager
* @param string $sTabCode
* @param string|null $sTabContainer
*/
public function RemoveTab(string $sTabCode, string $sTabContainer = null)
public function RemoveTab(string $sTabCode, ?string $sTabContainer = null)
{
if ($sTabContainer == null) {
$sTabContainer = $this->m_sCurrentTabContainer;
@@ -272,7 +272,7 @@ class TabManager
*
* @return mixed The actual name of the tab (as a string) or false if not found
*/
public function FindTab(string $sPattern, string $sTabContainer = null)
public function FindTab(string $sPattern, ?string $sTabContainer = null)
{
$result = false;
if ($sTabContainer == null) {

View File

@@ -18,7 +18,7 @@ interface iTabbedPage
*
* @return mixed
*/
public function AddTabContainer($sTabContainer, $sPrefix = '', iUIContentBlock $oParentBlock = null);
public function AddTabContainer($sTabContainer, $sPrefix = '', ?iUIContentBlock $oParentBlock = null);
/**
* @param string $sTabContainer

View File

@@ -1024,7 +1024,7 @@ HTML;
* @inheritDoc
* @throws \Exception
*/
public function AddTabContainer($sTabContainer, $sPrefix = '', iUIContentBlock $oParentBlock = null)
public function AddTabContainer($sTabContainer, $sPrefix = '', ?iUIContentBlock $oParentBlock = null)
{
if (is_null($oParentBlock)) {
$oParentBlock = PanelUIBlockFactory::MakeNeutral('');

View File

@@ -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;
}

View File

@@ -38,7 +38,7 @@ class DateTimeField extends StringField
/**
* @inheritDoc
*/
public function __construct(string $sId, Closure $onFinalizeCallback = null)
public function __construct(string $sId, ?Closure $onFinalizeCallback = null)
{
parent::__construct($sId, $onFinalizeCallback);
$this->bDateOnly = false;

View File

@@ -89,7 +89,7 @@ abstract class Field
* @param string $sId
* @param \Closure|null $onFinalizeCallback (Used in the $oForm->AddField($sId, ..., function() use ($oManager, $oForm, '...') { ... } ); )
*/
public function __construct(string $sId, Closure $onFinalizeCallback = null)
public function __construct(string $sId, ?Closure $onFinalizeCallback = null)
{
$this->sId = $sId;
// No space in such an id, that could be used as a DOM node id

View File

@@ -60,7 +60,7 @@ class FileUploadField extends AbstractSimpleField
/**
* @inheritDoc
*/
public function __construct(string $sId, Closure $onFinalizeCallback = null)
public function __construct(string $sId, ?Closure $onFinalizeCallback = null)
{
$this->sTransactionId = null;
$this->oObject = null;

View File

@@ -64,7 +64,7 @@ class LinkedSetField extends AbstractSimpleField
/**
* @inheritDoc
*/
public function __construct(string $sId, Closure $onFinalizeCallback = null)
public function __construct(string $sId, ?Closure $onFinalizeCallback = null)
{
$this->sTargetClass = null;
$this->sExtKeyToRemote = null;

View File

@@ -44,7 +44,7 @@ abstract class MultipleChoicesField extends AbstractSimpleField
/**
* @inheritDoc
*/
public function __construct(string $sId, Closure $onFinalizeCallback = null)
public function __construct(string $sId, ?Closure $onFinalizeCallback = null)
{
parent::__construct($sId, $onFinalizeCallback);
$this->bMultipleValuesEnabled = static::DEFAULT_MULTIPLE_VALUES_ENABLED;

View File

@@ -45,7 +45,7 @@ class SelectField extends MultipleChoicesField
/**
* @inheritDoc
*/
public function __construct(string $sId, Closure $onFinalizeCallback = null)
public function __construct(string $sId, ?Closure $onFinalizeCallback = null)
{
parent::__construct($sId, $onFinalizeCallback);
$this->bStartsWithNullChoice = static::DEFAULT_STARTS_WITH_NULL_CHOICE;

View File

@@ -70,7 +70,7 @@ class SelectObjectField extends AbstractSimpleField
/**
* @inheritDoc
*/
public function __construct(string $sId, Closure $onFinalizeCallback = null)
public function __construct(string $sId, ?Closure $onFinalizeCallback = null)
{
parent::__construct($sId, $onFinalizeCallback);
$this->oSearch = null;

View File

@@ -34,7 +34,7 @@ class SubFormField extends Field
/**
* @inheritDoc
*/
public function __construct(string $sId, Closure $onFinalizeCallback = null)
public function __construct(string $sId, ?Closure $onFinalizeCallback = null)
{
$this->oForm = new Form('subform_'.$sId);
parent::__construct($sId, $onFinalizeCallback);

View File

@@ -53,7 +53,7 @@ class TextAreaField extends TextField
* @param \Closure|null $onFinalizeCallback
* @param \DBObject|null $oObject
*/
public function __construct(string $sId, Closure $onFinalizeCallback = null, DBObject $oObject = null)
public function __construct(string $sId, ?Closure $onFinalizeCallback = null, ?DBObject $oObject = null)
{
parent::__construct($sId, $onFinalizeCallback);
$this->sFormat = static::DEFAULT_FORMAT;

View File

@@ -39,7 +39,7 @@ class UrlField extends StringField
/**
* @inheritDoc
*/
public function __construct(string $sId, Closure $onFinalizeCallback = null)
public function __construct(string $sId, ?Closure $onFinalizeCallback = null)
{
parent::__construct($sId, $onFinalizeCallback);

View File

@@ -48,7 +48,7 @@ abstract class FormRenderer
*
* @param \Combodo\iTop\Form\Form $oForm
*/
public function __construct(Form $oForm = null)
public function __construct(?Form $oForm = null)
{
if ($oForm !== null) {
$this->oForm = $oForm;

View File

@@ -84,7 +84,7 @@ class ObjectRepository
* @return array|null
* @since 3.2.0 Add $iLimit parameter
*/
public static function SearchFromOql(string $sObjectClass, array $aFieldsToLoad, string $sOql, string $sSearch, DBObject $oThisObject = null, int $iLimit = 0): ?array
public static function SearchFromOql(string $sObjectClass, array $aFieldsToLoad, string $sOql, string $sSearch, ?DBObject $oThisObject = null, int $iLimit = 0): ?array
{
try {
@@ -280,7 +280,7 @@ class ObjectRepository
* @throws \CoreException
* @throws \DictExceptionMissingString
*/
public static function ConvertObjectToArray(DBObject $oObject, string $sObjectClass, array $aFieldsToLoad = null, array $aComplementAttributeSpec = null, string $sObjectImageAttCode = null): array
public static function ConvertObjectToArray(DBObject $oObject, string $sObjectClass, ?array $aFieldsToLoad = null, ?array $aComplementAttributeSpec = null, ?string $sObjectImageAttCode = null): array
{
// Retrieve friendly name complementary specification
if ($aComplementAttributeSpec === null) {

View File

@@ -49,7 +49,7 @@ class LinkSetDataTransformer
*
* @return array
*/
public static function Decode(iDBObjectSetIterator $oDbObjectSet, string $sTargetClass, string $sTargetField = null): array
public static function Decode(iDBObjectSetIterator $oDbObjectSet, string $sTargetClass, ?string $sTargetField = null): array
{
try {
// Prepare result
@@ -96,7 +96,7 @@ class LinkSetDataTransformer
*
* @return array{to_be_created: array, to_be_deleted: array, to_be_added: array, to_be_removed: array}
*/
public static function Encode(array $aElements, string $sLinkClass, string $sExtKeyToRemote = null): array
public static function Encode(array $aElements, string $sLinkClass, ?string $sExtKeyToRemote = null): array
{
// Result arrays
$aToBeCreate = [];

View File

@@ -36,7 +36,7 @@ class LinkSetRepository
*
* @return array|null
*/
public static function LinksDbSetToTargetObjectArray(iDBObjectSetIterator $oDbObjectSet, bool $bForce, array &$aInitialOptions, string $sTargetClass, string $sTargetField = null): ?array
public static function LinksDbSetToTargetObjectArray(iDBObjectSetIterator $oDbObjectSet, bool $bForce, array &$aInitialOptions, string $sTargetClass, ?string $sTargetField = null): ?array
{
try {

View File

@@ -188,7 +188,7 @@ class NotificationsRepository
*
* @return DBObjectSet The set of subscriptions matching the given trigger, contact, and action.
*/
public function SearchUnsubscribedSubscriptionsByTriggerContactAndAction(int $iTriggerId, int $iActionId, int $iContactId = null): DBObjectSet
public function SearchUnsubscribedSubscriptionsByTriggerContactAndAction(int $iTriggerId, int $iActionId, ?int $iContactId = null): DBObjectSet
{
$oSearch = $this->PrepareSearchForSubscriptionsByTriggerContactAndAction($iTriggerId, $iActionId, $iContactId);
$oSearch->AddCondition("subscribed", "0");

View File

@@ -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)
;
;

View File

@@ -0,0 +1,129 @@
# PHP static analysis
- [Installation](#installation)
- [Usages](#usages)
- [Analysing a package](#analysing-a-package)
- [Analysing a module](#analysing-a-module)
- [Configuration](#configuration)
- [Adjust local configuration to your needs](#adjust-local-configuration-to-your-needs)
- [Adjust configuration for a particular CI repository / job](#adjust-configuration-for-a-particular-ci-repository--job)
## Installation
- Install dependencies by running `composer install` in this folder
- You should be all set! 🚀
## Usages
### Analysing a package
_Do this if you want to analyse the whole iTop package (iTop core, extensions, third-party libs, ...)_
- Make sure you ran a setup on your iTop as it will analyse the `env-production` folder
- Open a prompt in your iTop folder
- Run the following command
```bash
tests/php-static-analysis/vendor/bin/phpstan analyse \
--configuration ./tests/php-static-analysis/config/for-package.dist.neon \
--error-format raw
```
You will then have an output like this listing all errors:
```bash
tests/php-static-analysis/vendor/bin/phpstan analyse \
--configuration ./tests/php-static-analysis/config/for-package.dist.neon \
--error-format raw
1049/1049 [============================] 100%
<ITOP>\addons\userrights\userrightsprofile.class.inc.php:552:Call to static method InitSharedClassProperties() on an unknown class SharedObject.
<ITOP>\addons\userrights\userrightsprofile.db.class.inc.php:927:Call to static method GetSharedClassProperties() on an unknown class SharedObject.
<ITOP>\addons\userrights\userrightsprojection.class.inc.php:722:Access to an undefined property UserRightsProjection::$m_aClassProjs.
<ITOP>\application\applicationextension.inc.php:295:Method AbstractPreferencesExtension::ApplyPreferences() should return bool but return statement is missing.
<ITOP>\application\cmdbabstract.class.inc.php:1010:Class utils referenced with incorrect case: Utils.
[...]
```
### Analysing a module
_Do this if you only want to analyse one or more modules within this iTop but not the whole package_
- Make sure you ran a setup on your iTop as it will analyse the `env-production` folder
- Open a prompt in your iTop folder
- Run the following command
```
tests/php-static-analysis/vendor/bin/phpstan analyse \
--configuration ./tests/php-static-analysis/config/for-module.dist.neon \
--error-format raw \
env-production/<MODULE_CODE_1> [env-production/<MODULE_CODE_2> ...]
```
You will then have an output like this listing all errors:
```
tests/php-static-analysis/vendor/bin/phpstan analyse \
--configuration ./tests/php-static-analysis/config/for-module.dist.neon \
--error-format raw \
env-production/authent-ldap env-production/itop-oauth-client
49/49 [============================] 100%
<ITOP>\env-production\authent-ldap\model.authent-ldap.php:79:Undefined variable: $hDS
<ITOP>\env-production\authent-ldap\model.authent-ldap.php:80:Undefined variable: $name
<ITOP>\env-production\authent-ldap\model.authent-ldap.php:80:Undefined variable: $value
<ITOP>\env-production\itop-oauth-client\vendor\composer\InstalledVersions.php:105:Parameter $parser of method Composer\InstalledVersions::satisfies() has invalid type Composer\Semver\VersionParser.
[...]
```
## Configuration
### Adjust local configuration to your needs
#### Define which PHP version to run the analysis for
The way we configured PHPStan in this project changes how it will find the PHP version to run the analysis for. \
By default PHPStan check the information from the composer.json file, but we changed that (via the `config/php-includes/set-php-version-from-process.php` include) so it used the PHP
version currently ran by the CLI.
So all you have to do is either:
- Prepend your command line with the path of the executable of the desired PHP version
- Change the default PHP interpreter in your IDE settings
#### Change some parameters for a local run
If you want to change some particular settings (eg. the memory limit, the rules level, ...) for a local run of the analysis you have 2 choices.
##### Method 1: CLI parameter
For most parameters there is a good chance you can just add the parameter and its value in your command, which will override the one defined in the configuration file. \
Below are some example, but your can find the complete reference [here](https://phpstan.org/user-guide/command-line-usage).
```bash
--memory-limit 1G
--level 5
--error-format raw
[...]
```
**Pros** Quick and easy to try different parameters \
**Cons** Parameters aren't saved, so you'll have to remember them and put them again next time
##### Method 2: Configuration file
Crafting your own configuration file gives you the ability to fine tune any parameters, it's way more powerful but can also quickly lead to crashes if you mess with the symbols discovery (classes, ...). \
But mostly it can be saved, shared, re-used; which is it's main purpose.
It is recommended that you create your configuration file from scratch and that you include the `base.dist.neon` so you are bootstrapped for the symbols discovery. Then you can override any parameter. \
Check [the documentation](https://phpstan.org/config-reference#multiple-files) for more information.
```neon
includes:
- base.dist.neon
parameters:
# Override parameters here
```
#### Analyse only one (or some) folder(s) quicker
It's pretty easy and good news you don't need to create a new configuration file or change an existing one. \
Just adapt and use command lines from the [usages section](#usages) and add the folders you want to analyse at the end of the command, exactly like when analysing modules.
For example if you want to analyse just `<ITOP>/setup` and `<ITOP>/sources`, use something like:
```
tests/php-static-analysis/vendor/bin/phpstan analyse \
--configuration ./tests/php-static-analysis/config/for-package.dist.neon \
--error-format raw \
setup sources
```
### Adjust configuration for a particular CI repository / job
TODO

View File

@@ -0,0 +1,5 @@
{
"require": {
"phpstan/phpstan": "^2.1"
}
}

72
tests/php-static-analysis/composer.lock generated Normal file
View File

@@ -0,0 +1,72 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "cc6d7580a5e98236d68d8b91de9ddebb",
"packages": [
{
"name": "phpstan/phpstan",
"version": "2.1.33",
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f",
"reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f",
"shasum": ""
},
"require": {
"php": "^7.4|^8.0"
},
"conflict": {
"phpstan/phpstan-shim": "*"
},
"bin": [
"phpstan",
"phpstan.phar"
],
"type": "library",
"autoload": {
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "PHPStan - PHP Static Analysis Tool",
"keywords": [
"dev",
"static analysis"
],
"support": {
"docs": "https://phpstan.org/user-guide/getting-started",
"forum": "https://github.com/phpstan/phpstan/discussions",
"issues": "https://github.com/phpstan/phpstan/issues",
"security": "https://github.com/phpstan/phpstan/security/policy",
"source": "https://github.com/phpstan/phpstan-src"
},
"funding": [
{
"url": "https://github.com/ondrejmirtes",
"type": "github"
},
{
"url": "https://github.com/phpstan",
"type": "github"
}
],
"time": "2025-12-05T10:24:31+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.6.0"
}

View File

@@ -0,0 +1,29 @@
## Disclaimer
DON'T modify the following files without knowledge and discussing with the team:
- base.dist.neon
- for-package.dist.neon
- for-module.dist.neon
## Purpose of these files
### base.dist.neon
This configuration file contains the common parameters for all analysis, whereas it is a package, a module or something specific. Among others:
- Rules level for analysis
- PHP version to compare
- Necessary files for autoloaders discovery and such
- ...
This file should not be modified for your specific needs, you should always include it and override the desired parameters. \
See how it is done in `for-package.dist.neon` and `for-module.dist.neon` or on the documentation [here](https://phpstan.org/config-reference#multiple-files).
### for-package.dist.neon
This configuration file contains the parameters to analyse a package (iTop core, modules, third-party libs).
### for-module.dist.neon
This configuration file contains the parameters to analyse one or more modules only.
## How / when can I modify these files?
**You CAN'T!** \
Well, unless there is a good reason and you talked about it with the team. But you should never modify them for a specific need on your local environment.
- If you have a particular need for your local environment (eg. increase memory limit, change rules levels, analyse only a specific folder), check the [Configuration section](../#configuration) of the main README.md.
- If you feel like there is need for an adjustment in the default configurations, discuss it with th team and make a PR.

View File

@@ -0,0 +1,40 @@
includes:
- php-includes/set-php-version-from-process.php # Workaround to set PHP version to the on running the CLI
# for an explanation of the baseline concept, see: https://phpstan.org/user-guide/baseline
#baseline HERE DO NOT REMOVE FOR CI
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 as it's Combodo's default IDE
bootstrapFiles:
- ../../../approot.inc.php
- ../../../bootstrap.inc.php
scanFiles:
# Files necessary as they contain some declarations (constants, classes, functions, ...)
- ../../../approot.inc.php
- ../../../bootstrap.inc.php
excludePaths:
analyse:
# For third-party libs we should analyse them in a dedicated configuration as we can't improve / clean them which would
# prevent us from raising the rules level as we improve / clean our codebase
- ../../../lib # Irrelevant as we only want to analyze our codebase
- ../../../node_modules # Irrelevant as we only want to analyze our codebase
analyseAndScan:
# 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)
- ../../../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

View File

@@ -0,0 +1,15 @@
includes:
- base.dist.neon
parameters:
paths:
# We just want to analyse the module folder(s), either:
# - Create your own `for-module.neon` file, include this one and override this parameter (see https://phpstan.org/config-reference#multiple-files)
# - Pass the module folder(s) in the commande line (see https://phpstan.org/config-reference#analysed-files)
scanDirectories:
# Unlike for `for-package.dist.neon`, here we need to scan all the folders to discover symbols, but we only want to analyse the module folder.
# We initially thought of doing it through the `excludePaths` param. by excluding everything but the module folder, but it doesn't seem to be possible, because it uses the `fnmatch()` function.
# As a workaround, we list here all the folders to scan.
#
# Scan the whole project and rely on the `excludePaths` param. to filter the unnecessary
- ../../..

View File

@@ -0,0 +1,7 @@
includes:
- base.dist.neon
parameters:
paths:
# We want to analyse almost the whole project, so we do a negative selection between the `paths` and `excludePaths` (see base.dist.neon) parameters
- ../../../

View File

@@ -0,0 +1,25 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
declare(strict_types=1);
/**
* This file is only here to allow setting a specific PHP version to run the analysis for without
* having to explicitly set it in the .neon file. This is the best way we found so far.
*
* @link https://phpstan.org/config-reference#phpversion
*
* Usage: Uses the CLI PHP version by default, which would work fine for
* - The CI as the docker image has the target PHP version in both CLI and web
* - The developer's IDE as PHPStorm also has a default PHP version configured which can be changed on the fly
*/
// Default PHP version to analyse is the one running in CLI
$config = [];
$config['parameters']['phpVersion'] = PHP_VERSION_ID;
return $config;

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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>";

View File

@@ -230,7 +230,7 @@ class TestMyBizModel extends TestBizModel
public function test_error()
{
trigger_error("Stop requested", E_USER_ERROR);
trigger_error("Stop requested", E_USER_WARNING);
}
public function test_changetracking()

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -73,7 +73,7 @@ class QueryTest extends ItopDataTestCase
* @param string $sOql query oql phrase
* @param string|null $sFields fields to export
*/
private function CreateQueryOQL(string $sName, string $sDescription, string $sOql, string $sFields = null): QueryOQL
private function CreateQueryOQL(string $sName, string $sDescription, string $sOql, ?string $sFields = null): QueryOQL
{
$oQuery = new QueryOQL();
$oQuery->Set('name', $sName);
@@ -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 */

Some files were not shown because too many files have changed in this diff Show More