mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-24 02:58:43 +02:00
Compare commits
144 Commits
3.1.0-3
...
issue/6716
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bd72409f1 | ||
|
|
3eb06f8ada | ||
|
|
44c189223e | ||
|
|
3e258f32cc | ||
|
|
3c51d6fb98 | ||
|
|
7cfe1389aa | ||
|
|
7292a8540b | ||
|
|
f65c690462 | ||
|
|
ecf8bc42fa | ||
|
|
e76728b2bf | ||
|
|
faba812fc1 | ||
|
|
add433d702 | ||
|
|
9c99cb35e5 | ||
|
|
9d392ad167 | ||
|
|
ea8509db1f | ||
|
|
df25ce76b6 | ||
|
|
e946fc65fc | ||
|
|
d203e075a8 | ||
|
|
dbe2f66539 | ||
|
|
0d8ff7bbac | ||
|
|
48eb022824 | ||
|
|
03c9ffc033 | ||
|
|
61a9a4ac65 | ||
|
|
38962e68ee | ||
|
|
483dbb4a5d | ||
|
|
1f4dcc4f9e | ||
|
|
e86309669e | ||
|
|
6d6f55acf7 | ||
|
|
6ebcd44bb1 | ||
|
|
f8fb51fea0 | ||
|
|
bf768311c2 | ||
|
|
d797436786 | ||
|
|
b508c0d983 | ||
|
|
351893bbdd | ||
|
|
59e4bb028f | ||
|
|
6d895371ec | ||
|
|
ab91631e68 | ||
|
|
3366bae0ab | ||
|
|
03b484c349 | ||
|
|
70081ecf33 | ||
|
|
575ba1cd7b | ||
|
|
d130959692 | ||
|
|
a8c689c6c0 | ||
|
|
1990ccb5d8 | ||
|
|
e107be56e4 | ||
|
|
0f8e87e001 | ||
|
|
d92d2b5e9e | ||
|
|
ebd0136773 | ||
|
|
f6653e1594 | ||
|
|
65bb76b9e3 | ||
|
|
f238593966 | ||
|
|
d951d3b872 | ||
|
|
ccceb870e3 | ||
|
|
ed6df77cbb | ||
|
|
1ad28312ec | ||
|
|
f002aa04cd | ||
|
|
b86d70623e | ||
|
|
fe3467309d | ||
|
|
851ab9c356 | ||
|
|
aef3c2e609 | ||
|
|
5212e15cc4 | ||
|
|
f04fc546b5 | ||
|
|
caf3076b12 | ||
|
|
c4c400d852 | ||
|
|
6cc4cc4fb6 | ||
|
|
d7495af207 | ||
|
|
13ad98b9b3 | ||
|
|
4be54fdd65 | ||
|
|
6d13397ba1 | ||
|
|
48e7e0309a | ||
|
|
2ce9b2afaf | ||
|
|
d64a91d4ce | ||
|
|
c0c8a13864 | ||
|
|
5ffa41bc16 | ||
|
|
d2eef06276 | ||
|
|
77b14c516e | ||
|
|
880a824f2f | ||
|
|
f7f1b5f399 | ||
|
|
18efbfa803 | ||
|
|
7aa478d6ff | ||
|
|
97700dbf15 | ||
|
|
c25c69d746 | ||
|
|
734a788340 | ||
|
|
eb1eb15791 | ||
|
|
a84077782d | ||
|
|
26048150d3 | ||
|
|
e5b6e2eb8c | ||
|
|
87b6ea4def | ||
|
|
72873a3343 | ||
|
|
5ef25ccb77 | ||
|
|
1682a85cc0 | ||
|
|
cd9beec313 | ||
|
|
8295eaed90 | ||
|
|
86a7cefa68 | ||
|
|
829b648dd2 | ||
|
|
5475b9fbbe | ||
|
|
6f8e7c7002 | ||
|
|
67ca554261 | ||
|
|
f89953f39e | ||
|
|
772368ef8a | ||
|
|
2e049aa244 | ||
|
|
a57b6471c9 | ||
|
|
bc7c1b4744 | ||
|
|
12c78697f4 | ||
|
|
046e857768 | ||
|
|
4d8246c4d8 | ||
|
|
5c61d725e1 | ||
|
|
0c7195f1a3 | ||
|
|
00b070b3cf | ||
|
|
2c4cad4dac | ||
|
|
9c37d5c23e | ||
|
|
89145593ef | ||
|
|
b2e80d37dd | ||
|
|
6432678de9 | ||
|
|
952194b385 | ||
|
|
bfb452dd69 | ||
|
|
64baeba1c7 | ||
|
|
71ed784c60 | ||
|
|
da45651121 | ||
|
|
d388ce9a06 | ||
|
|
47e71d8838 | ||
|
|
2b5973ec67 | ||
|
|
e58918f53e | ||
|
|
125715af3f | ||
|
|
ea8e7c5131 | ||
|
|
06e5e0b102 | ||
|
|
5247f5b3ea | ||
|
|
78396d8e4a | ||
|
|
f1ee22cbed | ||
|
|
39305468f8 | ||
|
|
32fd75bc4b | ||
|
|
efadf2cc79 | ||
|
|
40d63a2fa4 | ||
|
|
baa6dedbcf | ||
|
|
556b9ad89a | ||
|
|
9afc22bd8f | ||
|
|
ef0b0f88c9 | ||
|
|
a010239efb | ||
|
|
264a8cd70a | ||
|
|
aa1834170b | ||
|
|
f94d67ab35 | ||
|
|
3048c8c41f | ||
|
|
246e4a9f50 | ||
|
|
0001e8ffc4 |
@@ -62,6 +62,12 @@ gitGraph
|
|||||||
commit id: "2022-12-28" tag: "2.7.8"
|
commit id: "2022-12-28" tag: "2.7.8"
|
||||||
checkout support/3.0
|
checkout support/3.0
|
||||||
commit id: "2023-04-12" tag: "3.0.3"
|
commit id: "2023-04-12" tag: "3.0.3"
|
||||||
|
checkout develop
|
||||||
|
commit id: "2023-06-19" tag: "3.1.0-beta" type: REVERSE
|
||||||
|
commit id: "2023-07-26" tag: "3.1.0-1" type: HIGHLIGHT
|
||||||
|
branch support/3.1 order: 840
|
||||||
|
checkout support/3.1
|
||||||
|
commit id: "2023-08-09" tag: "3.1.0-2"
|
||||||
```
|
```
|
||||||
|
|
||||||
To learn more, check the [iTop community versions history on the official wiki](https://www.itophub.io/wiki/page?id=latest:release:start).
|
To learn more, check the [iTop community versions history on the official wiki](https://www.itophub.io/wiki/page?id=latest:release:start).
|
||||||
|
|||||||
@@ -2246,3 +2246,27 @@ interface iModuleExtension
|
|||||||
*/
|
*/
|
||||||
public function __construct();
|
public function __construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KPI logging extensibility point
|
||||||
|
*
|
||||||
|
* KPI Logger extension
|
||||||
|
*/
|
||||||
|
interface iKPILoggerExtension
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Init the statistics collected
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function InitStats();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new KPI to the stats
|
||||||
|
*
|
||||||
|
* @param \Combodo\iTop\Core\Kpi\KpiLogData $oKpiLogData
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function LogOperation($oKpiLogData);
|
||||||
|
}
|
||||||
@@ -34,15 +34,15 @@ class AuditCategory extends cmdbAbstractObject
|
|||||||
{
|
{
|
||||||
$aParams = array
|
$aParams = array
|
||||||
(
|
(
|
||||||
"category" => "application, grant_by_profile",
|
"category" => "application,grant_by_profile",
|
||||||
"key_type" => "autoincrement",
|
"key_type" => "autoincrement",
|
||||||
"name_attcode" => "name",
|
"name_attcode" => "name",
|
||||||
"state_attcode" => "",
|
"state_attcode" => "",
|
||||||
"reconc_keys" => array('name'),
|
"reconc_keys" => array('name'),
|
||||||
"db_table" => "priv_auditcategory",
|
"db_table" => "priv_auditcategory",
|
||||||
"db_key_field" => "id",
|
"db_key_field" => "id",
|
||||||
"db_finalclass_field" => "",
|
"db_finalclass_field" => "",
|
||||||
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-audit-folder.svg'),
|
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-audit-folder.svg'),
|
||||||
);
|
);
|
||||||
MetaModel::Init_Params($aParams);
|
MetaModel::Init_Params($aParams);
|
||||||
MetaModel::Init_AddAttribute(new AttributeString("name", array("description"=>"Short name for this category", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
|
MetaModel::Init_AddAttribute(new AttributeString("name", array("description"=>"Short name for this category", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class AuditDomain extends cmdbAbstractObject
|
|||||||
{
|
{
|
||||||
$aParams = array
|
$aParams = array
|
||||||
(
|
(
|
||||||
"category" => "application, grant_by_profile",
|
"category" => "application,grant_by_profile",
|
||||||
"key_type" => "autoincrement",
|
"key_type" => "autoincrement",
|
||||||
"name_attcode" => "name",
|
"name_attcode" => "name",
|
||||||
"complementary_name_attcode" => array('description'),
|
"complementary_name_attcode" => array('description'),
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class AuditRule extends cmdbAbstractObject
|
|||||||
{
|
{
|
||||||
$aParams = array
|
$aParams = array
|
||||||
(
|
(
|
||||||
"category" => "application, grant_by_profile",
|
"category" => "application,grant_by_profile",
|
||||||
"key_type" => "autoincrement",
|
"key_type" => "autoincrement",
|
||||||
"name_attcode" => "name",
|
"name_attcode" => "name",
|
||||||
"state_attcode" => "",
|
"state_attcode" => "",
|
||||||
|
|||||||
@@ -1166,7 +1166,7 @@ HTML
|
|||||||
/**
|
/**
|
||||||
* @param \WebPage $oPage
|
* @param \WebPage $oPage
|
||||||
* @param \CMDBObjectSet $oSet
|
* @param \CMDBObjectSet $oSet
|
||||||
* @param array $aExtraParams
|
* @param array $aExtraParams See possible values in {@see DataTableUIBlockFactory::RenderDataTable()}
|
||||||
*
|
*
|
||||||
* @throws \ApplicationException
|
* @throws \ApplicationException
|
||||||
* @throws \CoreException
|
* @throws \CoreException
|
||||||
@@ -4547,7 +4547,9 @@ HTML;
|
|||||||
foreach (MetaModel::EnumPlugins(iApplicationObjectExtension::class) as $oExtensionInstance) {
|
foreach (MetaModel::EnumPlugins(iApplicationObjectExtension::class) as $oExtensionInstance) {
|
||||||
$sExtensionClass = get_class($oExtensionInstance);
|
$sExtensionClass = get_class($oExtensionInstance);
|
||||||
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBInsert()");
|
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBInsert()");
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
$oExtensionInstance->OnDBInsert($this, self::GetCurrentChange());
|
$oExtensionInstance->OnDBInsert($this, self::GetCurrentChange());
|
||||||
|
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBInsert');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4562,13 +4564,16 @@ HTML;
|
|||||||
|
|
||||||
protected function DBCloneTracked_Internal($newKey = null)
|
protected function DBCloneTracked_Internal($newKey = null)
|
||||||
{
|
{
|
||||||
$oNewObj = parent::DBCloneTracked_Internal($newKey);
|
/** @var cmdbAbstractObject $oNewObj */
|
||||||
|
$oNewObj = MetaModel::GetObject(get_class($this), parent::DBCloneTracked_Internal($newKey));
|
||||||
|
|
||||||
// Invoke extensions after insertion (the object must exist, have an id, etc.)
|
// Invoke extensions after insertion (the object must exist, have an id, etc.)
|
||||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||||
{
|
{
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
$oExtensionInstance->OnDBInsert($oNewObj, self::GetCurrentChange());
|
$oExtensionInstance->OnDBInsert($oNewObj, self::GetCurrentChange());
|
||||||
|
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBInsert');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $oNewObj;
|
return $oNewObj;
|
||||||
@@ -4605,7 +4610,9 @@ HTML;
|
|||||||
foreach (MetaModel::EnumPlugins(iApplicationObjectExtension::class) as $oExtensionInstance) {
|
foreach (MetaModel::EnumPlugins(iApplicationObjectExtension::class) as $oExtensionInstance) {
|
||||||
$sExtensionClass = get_class($oExtensionInstance);
|
$sExtensionClass = get_class($oExtensionInstance);
|
||||||
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBUpdate()");
|
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBUpdate()");
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
$oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange());
|
$oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange());
|
||||||
|
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBUpdate');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4649,7 +4656,9 @@ HTML;
|
|||||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||||
{
|
{
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
$oExtensionInstance->OnDBDelete($this, self::GetCurrentChange());
|
$oExtensionInstance->OnDBDelete($this, self::GetCurrentChange());
|
||||||
|
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBDelete');
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::DBDeleteTracked_Internal($oDeletionPlan);
|
return parent::DBDeleteTracked_Internal($oDeletionPlan);
|
||||||
@@ -4668,7 +4677,10 @@ HTML;
|
|||||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||||
{
|
{
|
||||||
$sExtensionClass = get_class($oExtensionInstance);
|
$sExtensionClass = get_class($oExtensionInstance);
|
||||||
if ($oExtensionInstance->OnIsModified($this)) {
|
$oKPI = new ExecutionKPI();
|
||||||
|
$bIsModified = $oExtensionInstance->OnIsModified($this);
|
||||||
|
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnIsModified');
|
||||||
|
if ($bIsModified) {
|
||||||
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnIsModified() -> true");
|
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnIsModified() -> true");
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@@ -4724,7 +4736,9 @@ HTML;
|
|||||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||||
{
|
{
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
$aNewIssues = $oExtensionInstance->OnCheckToWrite($this);
|
$aNewIssues = $oExtensionInstance->OnCheckToWrite($this);
|
||||||
|
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToWrite');
|
||||||
if (is_array($aNewIssues) && (count($aNewIssues) > 0)) // Some extensions return null instead of an empty array
|
if (is_array($aNewIssues) && (count($aNewIssues) > 0)) // Some extensions return null instead of an empty array
|
||||||
{
|
{
|
||||||
$this->m_aCheckIssues = array_merge($this->m_aCheckIssues, $aNewIssues);
|
$this->m_aCheckIssues = array_merge($this->m_aCheckIssues, $aNewIssues);
|
||||||
@@ -4772,7 +4786,9 @@ HTML;
|
|||||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||||
{
|
{
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
$aNewIssues = $oExtensionInstance->OnCheckToDelete($this);
|
$aNewIssues = $oExtensionInstance->OnCheckToDelete($this);
|
||||||
|
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToDelete');
|
||||||
if (is_array($aNewIssues) && count($aNewIssues) > 0)
|
if (is_array($aNewIssues) && count($aNewIssues) > 0)
|
||||||
{
|
{
|
||||||
$this->m_aDeleteIssues = array_merge($this->m_aDeleteIssues, $aNewIssues);
|
$this->m_aDeleteIssues = array_merge($this->m_aDeleteIssues, $aNewIssues);
|
||||||
|
|||||||
@@ -918,6 +918,11 @@ class RuntimeDashboard extends Dashboard
|
|||||||
{
|
{
|
||||||
$bCustomized = false;
|
$bCustomized = false;
|
||||||
|
|
||||||
|
$sDashboardFileSanitized = utils::RealPath($sDashboardFile, APPROOT);
|
||||||
|
if (false === $sDashboardFileSanitized) {
|
||||||
|
throw new SecurityException('Invalid dashboard file !');
|
||||||
|
}
|
||||||
|
|
||||||
// Search for an eventual user defined dashboard
|
// Search for an eventual user defined dashboard
|
||||||
$oUDSearch = new DBObjectSearch('UserDashboard');
|
$oUDSearch = new DBObjectSearch('UserDashboard');
|
||||||
$oUDSearch->AddCondition('user_id', UserRights::GetUserId(), '=');
|
$oUDSearch->AddCondition('user_id', UserRights::GetUserId(), '=');
|
||||||
@@ -929,7 +934,7 @@ class RuntimeDashboard extends Dashboard
|
|||||||
$sDashboardDefinition = $oUserDashboard->Get('contents');
|
$sDashboardDefinition = $oUserDashboard->Get('contents');
|
||||||
$bCustomized = true;
|
$bCustomized = true;
|
||||||
} else {
|
} else {
|
||||||
$sDashboardDefinition = @file_get_contents($sDashboardFile);
|
$sDashboardDefinition = @file_get_contents($sDashboardFileSanitized);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -937,7 +942,7 @@ class RuntimeDashboard extends Dashboard
|
|||||||
$oDashboard = new RuntimeDashboard($sDashBoardId);
|
$oDashboard = new RuntimeDashboard($sDashBoardId);
|
||||||
$oDashboard->FromXml($sDashboardDefinition);
|
$oDashboard->FromXml($sDashboardDefinition);
|
||||||
$oDashboard->SetCustomFlag($bCustomized);
|
$oDashboard->SetCustomFlag($bCustomized);
|
||||||
$oDashboard->SetDefinitionFile($sDashboardFile);
|
$oDashboard->SetDefinitionFile($sDashboardFileSanitized);
|
||||||
} else {
|
} else {
|
||||||
$oDashboard = null;
|
$oDashboard = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1246,6 +1246,10 @@ JS
|
|||||||
} else {
|
} else {
|
||||||
$oBlock = DashletFactory::MakeForDashletBadge($sClassIconUrl, $sHyperlink, $iCount, $sClassLabel, null, null, $aRefreshParams);
|
$oBlock = DashletFactory::MakeForDashletBadge($sClassIconUrl, $sHyperlink, $iCount, $sClassLabel, null, null, $aRefreshParams);
|
||||||
}
|
}
|
||||||
|
$sClassDescription = MetaModel::GetClassDescription($sClass);
|
||||||
|
if (utils::IsNotNullOrEmptyString($sClassDescription)) {
|
||||||
|
$oBlock->SetClassDescription($sClassDescription);
|
||||||
|
}
|
||||||
|
|
||||||
return $oBlock;
|
return $oBlock;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ class QueryOQL extends Query
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
(OQLException $e) {
|
(OQLException $e) {
|
||||||
$oAlert = AlertUIBlockFactory::MakeForFailure(Dict::Format('UI:RunQuery:Error'), $e->getHtmlDesc())
|
$oAlert = AlertUIBlockFactory::MakeForFailure(Dict::S('UI:RunQuery:Error'), $e->getHtmlDesc())
|
||||||
->SetIsClosable(false)
|
->SetIsClosable(false)
|
||||||
->SetIsCollapsible(false);
|
->SetIsCollapsible(false);
|
||||||
$oAlert->AddCSSClass('mb-5');
|
$oAlert->AddCSSClass('mb-5');
|
||||||
|
|||||||
@@ -99,4 +99,10 @@ else
|
|||||||
Session::Set('itop_env', ITOP_DEFAULT_ENV);
|
Session::Set('itop_env', ITOP_DEFAULT_ENV);
|
||||||
}
|
}
|
||||||
$sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE;
|
$sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE;
|
||||||
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv);
|
try {
|
||||||
|
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv);
|
||||||
|
}
|
||||||
|
catch (MySQLException $e) {
|
||||||
|
IssueLog::Debug($e->getMessage());
|
||||||
|
throw new MySQLException('Could not connect to the DB server', []);
|
||||||
|
}
|
||||||
@@ -178,17 +178,18 @@ class UILinksWidget
|
|||||||
|
|
||||||
$oDisplayBlock = new DisplayBlock($oFilter, 'search', false);
|
$oDisplayBlock = new DisplayBlock($oFilter, 'search', false);
|
||||||
$oBlock->AddSubBlock($oDisplayBlock->GetDisplay($oPage, "SearchFormToAdd_{$sLinkedSetId}",
|
$oBlock->AddSubBlock($oDisplayBlock->GetDisplay($oPage, "SearchFormToAdd_{$sLinkedSetId}",
|
||||||
array(
|
[
|
||||||
'menu' => false,
|
'menu' => false,
|
||||||
'result_list_outer_selector' => "SearchResultsToAdd_{$sLinkedSetId}",
|
'result_list_outer_selector' => "SearchResultsToAdd_{$sLinkedSetId}",
|
||||||
'table_id' => "add_{$sLinkedSetId}",
|
'table_id' => "add_{$sLinkedSetId}",
|
||||||
'table_inner_id' => "ResultsToAdd_{$sLinkedSetId}",
|
'table_inner_id' => "ResultsToAdd_{$sLinkedSetId}",
|
||||||
'selection_mode' => true,
|
'selection_mode' => true,
|
||||||
'json' => $sJson,
|
'json' => $sJson,
|
||||||
'cssCount' => '#count_'.$this->m_sAttCode.$this->m_sNameSuffix,
|
'cssCount' => '#count_'.$this->m_sAttCode.$this->m_sNameSuffix,
|
||||||
'query_params' => $oFilter->GetInternalParams(),
|
'query_params' => $oFilter->GetInternalParams(),
|
||||||
'hidden_criteria' => $sAlreadyLinkedExpression,
|
'hidden_criteria' => $sAlreadyLinkedExpression,
|
||||||
)));
|
'submit_on_load' => false,
|
||||||
|
]));
|
||||||
|
|
||||||
$oBlock->AddForm();
|
$oBlock->AddForm();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
use Combodo\iTop\Application\Helper\Session;
|
use Combodo\iTop\Application\Helper\Session;
|
||||||
use Combodo\iTop\Application\UI\Base\iUIBlock;
|
use Combodo\iTop\Application\UI\Base\iUIBlock;
|
||||||
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
|
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
|
||||||
|
use Combodo\iTop\Service\Module\ModuleService;
|
||||||
use ScssPhp\ScssPhp\Compiler;
|
use ScssPhp\ScssPhp\Compiler;
|
||||||
use ScssPhp\ScssPhp\OutputStyle;
|
use ScssPhp\ScssPhp\OutputStyle;
|
||||||
use ScssPhp\ScssPhp\ValueConverter;
|
use ScssPhp\ScssPhp\ValueConverter;
|
||||||
@@ -1396,13 +1397,23 @@ class utils
|
|||||||
return APPROOT . 'env-' . MetaModel::GetEnvironment() . '/';
|
return APPROOT . 'env-' . MetaModel::GetEnvironment() . '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string A path to the folder into which data can be written
|
||||||
|
* @internal
|
||||||
|
* @since N°6097 2.7.10 3.0.4 3.1.1
|
||||||
|
*/
|
||||||
|
public static function GetDataPath(): string
|
||||||
|
{
|
||||||
|
return APPROOT.'data/';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string A path to a folder into which any module can store cache data
|
* @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
|
* The corresponding folder is created or cleaned upon code compilation
|
||||||
*/
|
*/
|
||||||
public static function GetCachePath()
|
public static function GetCachePath()
|
||||||
{
|
{
|
||||||
return APPROOT.'data/cache-'.MetaModel::GetEnvironment().'/';
|
return static::GetDataPath().'cache-'.MetaModel::GetEnvironment().'/';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2265,24 +2276,7 @@ SQL;
|
|||||||
*/
|
*/
|
||||||
public static function GetCurrentModuleName($iCallDepth = 0)
|
public static function GetCurrentModuleName($iCallDepth = 0)
|
||||||
{
|
{
|
||||||
$sCurrentModuleName = '';
|
return ModuleService::GetInstance()->GetCurrentModuleName($iCallDepth + 1);
|
||||||
$aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
|
||||||
$sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
|
|
||||||
|
|
||||||
foreach(GetModulesInfo() as $sModuleName => $aInfo)
|
|
||||||
{
|
|
||||||
if ($aInfo['root_dir'] !== '')
|
|
||||||
{
|
|
||||||
$sRootDir = realpath(APPROOT.$aInfo['root_dir']);
|
|
||||||
|
|
||||||
if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
|
|
||||||
{
|
|
||||||
$sCurrentModuleName = $sModuleName;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $sCurrentModuleName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2304,24 +2298,7 @@ SQL;
|
|||||||
*/
|
*/
|
||||||
public static function GetCurrentModuleDir($iCallDepth)
|
public static function GetCurrentModuleDir($iCallDepth)
|
||||||
{
|
{
|
||||||
$sCurrentModuleDir = '';
|
return ModuleService::GetInstance()->GetCurrentModuleDir($iCallDepth);
|
||||||
$aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
|
||||||
$sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
|
|
||||||
|
|
||||||
foreach(GetModulesInfo() as $sModuleName => $aInfo)
|
|
||||||
{
|
|
||||||
if ($aInfo['root_dir'] !== '')
|
|
||||||
{
|
|
||||||
$sRootDir = realpath(APPROOT.$aInfo['root_dir']);
|
|
||||||
|
|
||||||
if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
|
|
||||||
{
|
|
||||||
$sCurrentModuleDir = basename($sRootDir);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $sCurrentModuleDir;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2336,12 +2313,7 @@ SQL;
|
|||||||
*/
|
*/
|
||||||
public static function GetCurrentModuleUrl()
|
public static function GetCurrentModuleUrl()
|
||||||
{
|
{
|
||||||
$sDir = static::GetCurrentModuleDir(1);
|
return ModuleService::GetInstance()->GetCurrentModuleUrl(1);
|
||||||
if ( $sDir !== '')
|
|
||||||
{
|
|
||||||
return static::GetAbsoluteUrlModulesRoot().'/'.$sDir;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2351,8 +2323,7 @@ SQL;
|
|||||||
*/
|
*/
|
||||||
public static function GetCurrentModuleSetting($sProperty, $defaultvalue = null)
|
public static function GetCurrentModuleSetting($sProperty, $defaultvalue = null)
|
||||||
{
|
{
|
||||||
$sModuleName = static::GetCurrentModuleName(1);
|
return ModuleService::GetInstance()->GetCurrentModuleSetting($sProperty, $defaultvalue);
|
||||||
return MetaModel::GetModuleSetting($sModuleName, $sProperty, $defaultvalue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2361,12 +2332,7 @@ SQL;
|
|||||||
*/
|
*/
|
||||||
public static function GetCompiledModuleVersion($sModuleName)
|
public static function GetCompiledModuleVersion($sModuleName)
|
||||||
{
|
{
|
||||||
$aModulesInfo = GetModulesInfo();
|
return ModuleService::GetInstance()->GetCompiledModuleVersion($sModuleName);
|
||||||
if (array_key_exists($sModuleName, $aModulesInfo))
|
|
||||||
{
|
|
||||||
return $aModulesInfo[$sModuleName]['version'];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2691,24 +2657,26 @@ SQL;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the local path relative to the iTop installation of an existing file
|
* Returns the local path relative to the iTop installation (APPROOT or the given base path)
|
||||||
* Dir separator is changed to '/' for consistency among the different OS
|
* Dir separator is changed to '/' for consistency among the different OS
|
||||||
*
|
*
|
||||||
* @param string $sAbsolutePath absolute path
|
* @param string $sAbsolutePath absolute path
|
||||||
|
* @param string $sBasePath Base path for the resulting local path (default APPROOT)
|
||||||
*
|
*
|
||||||
* @return false|string
|
* @return false|string The generated local path or false if absolute path is not under the base path
|
||||||
|
* @since 3.1.1 Added base path defaulted to previous version APPROOT
|
||||||
*/
|
*/
|
||||||
final public static function LocalPath($sAbsolutePath)
|
final public static function LocalPath($sAbsolutePath, string $sBasePath = APPROOT)
|
||||||
{
|
{
|
||||||
$sRootPath = realpath(APPROOT);
|
$sRootPath = realpath($sBasePath);
|
||||||
$sFullPath = realpath($sAbsolutePath);
|
$sFullPath = realpath($sAbsolutePath);
|
||||||
if (($sFullPath === false) || !self::StartsWith($sFullPath, $sRootPath))
|
if (($sFullPath === false) || !self::StartsWith($sFullPath, $sRootPath))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$sLocalPath = substr($sFullPath, strlen($sRootPath.DIRECTORY_SEPARATOR));
|
$sLocalPath = substr($sFullPath, strlen($sRootPath.DIRECTORY_SEPARATOR));
|
||||||
$sLocalPath = str_replace(DIRECTORY_SEPARATOR, '/', $sLocalPath);
|
|
||||||
return $sLocalPath;
|
return str_replace(DIRECTORY_SEPARATOR, '/', $sLocalPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2900,7 +2868,7 @@ HTML;
|
|||||||
|
|
||||||
// Add already loaded classes
|
// Add already loaded classes
|
||||||
$aCurrentClasses = array_fill_keys(get_declared_classes(), '');
|
$aCurrentClasses = array_fill_keys(get_declared_classes(), '');
|
||||||
$aClassMap = array_merge($aClassMap, $aCurrentClasses);
|
$aClassMap = array_merge($aCurrentClasses, $aClassMap);
|
||||||
|
|
||||||
foreach ($aClassMap as $sPHPClass => $sPHPFile) {
|
foreach ($aClassMap as $sPHPClass => $sPHPFile) {
|
||||||
$bSkipped = false;
|
$bSkipped = false;
|
||||||
@@ -2925,11 +2893,12 @@ HTML;
|
|||||||
$bSkipped = true; // file not found
|
$bSkipped = true; // file not found
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!$bSkipped){
|
if(!$bSkipped){
|
||||||
try {
|
try {
|
||||||
$oRefClass = new ReflectionClass($sPHPClass);
|
$oRefClass = new ReflectionClass($sPHPClass);
|
||||||
if ($oRefClass->implementsInterface($sInterface) && $oRefClass->isInstantiable()) {
|
if ($oRefClass->implementsInterface($sInterface) &&
|
||||||
|
!$oRefClass->isInterface() && !$oRefClass->isAbstract() && !$oRefClass->isTrait()) {
|
||||||
$aMatchingClasses[] = $sPHPClass;
|
$aMatchingClasses[] = $sPHPClass;
|
||||||
}
|
}
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ define('MAINTENANCE_MODE_FILE', APPROOT.'data/.maintenance');
|
|||||||
define('READONLY_MODE_FILE', APPROOT.'data/.readonly');
|
define('READONLY_MODE_FILE', APPROOT.'data/.readonly');
|
||||||
|
|
||||||
$fItopStarted = microtime(true);
|
$fItopStarted = microtime(true);
|
||||||
|
$iItopInitialMemory = memory_get_usage(true);
|
||||||
|
|
||||||
if (!isset($GLOBALS['bBypassAutoload']) || $GLOBALS['bBypassAutoload'] == false) {
|
if (!isset($GLOBALS['bBypassAutoload']) || $GLOBALS['bBypassAutoload'] == false) {
|
||||||
require_once APPROOT.'/lib/autoload.php';
|
require_once APPROOT.'/lib/autoload.php';
|
||||||
|
|||||||
@@ -59,9 +59,16 @@ class DbConnectionWrapper
|
|||||||
* Use this to register a mock that will handle {@see mysqli::query()}
|
* Use this to register a mock that will handle {@see mysqli::query()}
|
||||||
*
|
*
|
||||||
* @param \mysqli|null $oMysqli
|
* @param \mysqli|null $oMysqli
|
||||||
|
* @since 3.0.4 3.1.1 3.2.0 Param $oMysqli becomes nullable
|
||||||
*/
|
*/
|
||||||
public static function SetDbConnectionMockForQuery(?mysqli $oMysqli): void
|
public static function SetDbConnectionMockForQuery(?mysqli $oMysqli = null): void
|
||||||
{
|
{
|
||||||
static::$oDbCnxMockableForQuery = $oMysqli;
|
if (is_null($oMysqli)) {
|
||||||
|
// Reset to standard connection
|
||||||
|
static::$oDbCnxMockableForQuery = static::$oDbCnxStandard;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
static::$oDbCnxMockableForQuery = $oMysqli;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -419,6 +419,7 @@ class MyHelpers
|
|||||||
//}
|
//}
|
||||||
return $sOutput;
|
return $sOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -523,5 +524,3 @@ class Str
|
|||||||
return (strtolower($sString) == $sString);
|
return (strtolower($sString) == $sString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
|
||||||
|
|||||||
@@ -650,6 +650,9 @@ class ActionEmail extends ActionNotification
|
|||||||
$aMessageContent['subject'] = 'TEST['.$aMessageContent['subject'].']';
|
$aMessageContent['subject'] = 'TEST['.$aMessageContent['subject'].']';
|
||||||
$aMessageContent['body'] = $sTestBody;
|
$aMessageContent['body'] = $sTestBody;
|
||||||
$aMessageContent['to'] = $this->Get('test_recipient');
|
$aMessageContent['to'] = $this->Get('test_recipient');
|
||||||
|
// N°6677 Ensure emails in test are never sent to cc'd and bcc'd addresses
|
||||||
|
$aMessageContent['cc'] = '';
|
||||||
|
$aMessageContent['bcc'] = '';
|
||||||
}
|
}
|
||||||
// Note: N°4849 We pass the "References" identifier instead of the "Message-ID" on purpose as we want notifications emails to group around the triggering iTop object, not just the users' replies to the notification
|
// Note: N°4849 We pass the "References" identifier instead of the "Message-ID" on purpose as we want notifications emails to group around the triggering iTop object, not just the users' replies to the notification
|
||||||
$aMessageContent['in_reply_to'] = $aMessageContent['references'];
|
$aMessageContent['in_reply_to'] = $aMessageContent['references'];
|
||||||
|
|||||||
@@ -431,6 +431,7 @@ class CMDBSource
|
|||||||
{
|
{
|
||||||
self::$m_sDBName = '';
|
self::$m_sDBName = '';
|
||||||
}
|
}
|
||||||
|
self::_TablesInfoCacheReset(); // reset the table info cache!
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function CreateTable($sQuery)
|
public static function CreateTable($sQuery)
|
||||||
@@ -607,8 +608,9 @@ class CMDBSource
|
|||||||
{
|
{
|
||||||
self::LogDeadLock($e, true);
|
self::LogDeadLock($e, true);
|
||||||
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
|
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
|
||||||
}
|
} finally {
|
||||||
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
|
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
|
||||||
|
}
|
||||||
if ($oResult === false) {
|
if ($oResult === false) {
|
||||||
$aContext = array('query' => $sSql);
|
$aContext = array('query' => $sSql);
|
||||||
|
|
||||||
@@ -626,18 +628,24 @@ class CMDBSource
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Exception $e
|
* @param Exception $e
|
||||||
* @param bool $bForQuery to get the proper DB connection
|
* @param bool $bForQuery to get the proper DB connection
|
||||||
|
* @param bool $bCheckMysqliErrno if false won't try to check for mysqli::errno value
|
||||||
*
|
*
|
||||||
* @since 2.7.1
|
* @since 2.7.1
|
||||||
* @since 3.0.0 N°4325 add new optional parameter to use the correct DB connection
|
* @since 3.0.0 N°4325 add new optional parameter to use the correct DB connection
|
||||||
|
* @since 3.0.4 3.1.1 3.2.0 N°6643 new bCheckMysqliErrno parameter as a workaround for mysqli::errno cannot be mocked
|
||||||
*/
|
*/
|
||||||
private static function LogDeadLock(Exception $e, $bForQuery = false)
|
private static function LogDeadLock(Exception $e, $bForQuery = false, $bCheckMysqliErrno = true)
|
||||||
{
|
{
|
||||||
// checks MySQL error code
|
// checks MySQL error code
|
||||||
$iMySqlErrorNo = DbConnectionWrapper::GetDbConnection($bForQuery)->errno;
|
if ($bCheckMysqliErrno) {
|
||||||
if (!in_array($iMySqlErrorNo, array(self::MYSQL_ERRNO_WAIT_TIMEOUT, self::MYSQL_ERRNO_DEADLOCK))) {
|
$iMySqlErrorNo = DbConnectionWrapper::GetDbConnection($bForQuery)->errno;
|
||||||
return;
|
if (!in_array($iMySqlErrorNo, array(self::MYSQL_ERRNO_WAIT_TIMEOUT, self::MYSQL_ERRNO_DEADLOCK))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$iMySqlErrorNo = "N/A";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get error info
|
// Get error info
|
||||||
@@ -664,7 +672,10 @@ class CMDBSource
|
|||||||
);
|
);
|
||||||
DeadLockLog::Info($sMessage, $iMySqlErrorNo, $aLogContext);
|
DeadLockLog::Info($sMessage, $iMySqlErrorNo, $aLogContext);
|
||||||
|
|
||||||
IssueLog::Error($sMessage, LogChannels::DEADLOCK, $e->getMessage());
|
IssueLog::Error($sMessage, LogChannels::DEADLOCK, [
|
||||||
|
'exception.class' => get_class($e),
|
||||||
|
'exception.message' => $e->getMessage(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ define('ITOP_APPLICATION_SHORT', 'iTop');
|
|||||||
*
|
*
|
||||||
* @see ITOP_CORE_VERSION to get iTop core version
|
* @see ITOP_CORE_VERSION to get iTop core version
|
||||||
*/
|
*/
|
||||||
define('ITOP_VERSION', '3.1.0-dev');
|
define('ITOP_VERSION', '3.1.1-dev');
|
||||||
|
|
||||||
define('ITOP_VERSION_NAME', 'Fullmoon');
|
define('ITOP_VERSION_NAME', 'Fullmoon');
|
||||||
define('ITOP_REVISION', 'svn');
|
define('ITOP_REVISION', 'svn');
|
||||||
@@ -656,22 +656,22 @@ class Config
|
|||||||
'source_of_value' => '',
|
'source_of_value' => '',
|
||||||
'show_in_conf_sample' => false,
|
'show_in_conf_sample' => false,
|
||||||
],
|
],
|
||||||
'email_transport_smtp.allow_self_signed' => array(
|
'email_transport_smtp.allow_self_signed' => [
|
||||||
'type' => 'bool',
|
'type' => 'bool',
|
||||||
'description' => 'Allow self signed peer certificates',
|
'description' => 'Allow self signed peer certificates',
|
||||||
'default' => false,
|
'default' => false,
|
||||||
'value' => false,
|
'value' => false,
|
||||||
'source_of_value' => '',
|
'source_of_value' => '',
|
||||||
'show_in_conf_sample' => false,
|
'show_in_conf_sample' => false,
|
||||||
),
|
],
|
||||||
'email_transport_smtp.verify_peer' => array(
|
'email_transport_smtp.verify_peer' => [
|
||||||
'type' => 'bool',
|
'type' => 'bool',
|
||||||
'description' => 'Verify peer certificate',
|
'description' => 'Verify peer certificate',
|
||||||
'default' => true,
|
'default' => true,
|
||||||
'value' => true,
|
'value' => true,
|
||||||
'source_of_value' => '',
|
'source_of_value' => '',
|
||||||
'show_in_conf_sample' => false,
|
'show_in_conf_sample' => false,
|
||||||
),
|
],
|
||||||
'email_css' => [
|
'email_css' => [
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'description' => 'CSS that will override the standard stylesheet used for the notifications',
|
'description' => 'CSS that will override the standard stylesheet used for the notifications',
|
||||||
@@ -1069,6 +1069,14 @@ class Config
|
|||||||
'source_of_value' => '',
|
'source_of_value' => '',
|
||||||
'show_in_conf_sample' => false,
|
'show_in_conf_sample' => false,
|
||||||
],
|
],
|
||||||
|
'log_kpi_generate_legacy_report' => [
|
||||||
|
'type' => 'bool',
|
||||||
|
'description' => 'Generate the legacy KPI report (kpi.html)',
|
||||||
|
'default' => true,
|
||||||
|
'value' => '',
|
||||||
|
'source_of_value' => '',
|
||||||
|
'show_in_conf_sample' => false,
|
||||||
|
],
|
||||||
'max_linkset_output' => [
|
'max_linkset_output' => [
|
||||||
'type' => 'integer',
|
'type' => 'integer',
|
||||||
'description' => 'Maximum number of items shown when getting a list of related items in an email, using the form $this->some_list$. 0 means no limit.',
|
'description' => 'Maximum number of items shown when getting a list of related items in an email, using the form $this->some_list$. 0 means no limit.',
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* This file is only here for compatibility issues. Will be removed in iTop 3.1.0 (N°3664)
|
* This file is only here for compatibility reasons.
|
||||||
|
* It will be removed in future iTop versions (N°6533)
|
||||||
*
|
*
|
||||||
* @deprecated 3.0.0 N°3663 Exception classes were moved to `/application/exceptions`, use autoloader instead of require !
|
* @deprecated 3.0.0 N°3663 Exception classes were moved to `/application/exceptions`, use autoloader instead of require !
|
||||||
*/
|
*/
|
||||||
require_once '../approot.inc.php';
|
|
||||||
DeprecatedCallsLog::NotifyDeprecatedFile('Classes were moved to /application/exceptions');
|
require_once __DIR__ . '/../approot.inc.php';
|
||||||
|
|
||||||
|
DeprecatedCallsLog::NotifyDeprecatedFile('Classes were moved to /application/exceptions and can be used directly with the autoloader');
|
||||||
|
|||||||
@@ -188,8 +188,8 @@ final class ItopCounter
|
|||||||
|
|
||||||
if (!$hDBLink)
|
if (!$hDBLink)
|
||||||
{
|
{
|
||||||
throw new Exception("Could not connect to the DB server (host=$sDBHost, user=$sDBUser): ".mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno().')');
|
throw new MySQLException('Could not connect to the DB server '.mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno(), array('host' => $sDBHost, 'user' => $sDBUser));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $hDBLink;
|
return $hDBLink;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,6 +219,19 @@
|
|||||||
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
|
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
|
||||||
</fields>
|
</fields>
|
||||||
</class>
|
</class>
|
||||||
|
<class id="AuditDomain" _delta="define">
|
||||||
|
<parent>cmdbAbstractObject</parent>
|
||||||
|
<properties>
|
||||||
|
<category>application, grant_by_profile</category>
|
||||||
|
</properties>
|
||||||
|
<fields>
|
||||||
|
<field id="name" xsi:type="AttributeString"/>
|
||||||
|
<field id="description" xsi:type="AttributeString"/>
|
||||||
|
<field id="icon" xsi:type="AttributeImage"/>
|
||||||
|
<field id="categories_list" xsi:type="AttributeLinkedSet"/>
|
||||||
|
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
|
||||||
|
</fields>
|
||||||
|
</class>
|
||||||
<class id="Query" _delta="define">
|
<class id="Query" _delta="define">
|
||||||
<!-- Generated by toolkit/export-class-to-meta.php -->
|
<!-- Generated by toolkit/export-class-to-meta.php -->
|
||||||
<parent>cmdbAbstractObject</parent>
|
<parent>cmdbAbstractObject</parent>
|
||||||
|
|||||||
@@ -1152,7 +1152,9 @@ abstract class DBObject implements iDisplay
|
|||||||
return; //skip!
|
return; //skip!
|
||||||
}
|
}
|
||||||
$this->FireEventComputeValues();
|
$this->FireEventComputeValues();
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
$this->ComputeValues();
|
$this->ComputeValues();
|
||||||
|
$oKPI->ComputeStatsForExtension($this, 'ComputeValues');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2488,7 +2490,6 @@ abstract class DBObject implements iDisplay
|
|||||||
{
|
{
|
||||||
$this->m_aCheckIssues = array();
|
$this->m_aCheckIssues = array();
|
||||||
|
|
||||||
$oKPI = new ExecutionKPI();
|
|
||||||
if ($bDoComputeValues) {
|
if ($bDoComputeValues) {
|
||||||
$this->DoComputeValues();
|
$this->DoComputeValues();
|
||||||
}
|
}
|
||||||
@@ -2498,8 +2499,9 @@ abstract class DBObject implements iDisplay
|
|||||||
$this->FireEventCheckToWrite();
|
$this->FireEventCheckToWrite();
|
||||||
$this->SetReadWrite();
|
$this->SetReadWrite();
|
||||||
|
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
$this->DoCheckToWrite();
|
$this->DoCheckToWrite();
|
||||||
$oKPI->ComputeStats('CheckToWrite', get_class($this));
|
$oKPI->ComputeStatsForExtension($this, 'DoCheckToWrite');
|
||||||
if (count($this->m_aCheckIssues) == 0)
|
if (count($this->m_aCheckIssues) == 0)
|
||||||
{
|
{
|
||||||
$this->m_bCheckStatus = true;
|
$this->m_bCheckStatus = true;
|
||||||
@@ -3122,7 +3124,9 @@ abstract class DBObject implements iDisplay
|
|||||||
|
|
||||||
// Ensure the update of the values (we are accessing the data directly)
|
// Ensure the update of the values (we are accessing the data directly)
|
||||||
$this->DoComputeValues();
|
$this->DoComputeValues();
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
$this->OnInsert();
|
$this->OnInsert();
|
||||||
|
$oKPI->ComputeStatsForExtension($this, 'OnInsert');
|
||||||
|
|
||||||
$this->FireEventBeforeWrite();
|
$this->FireEventBeforeWrite();
|
||||||
|
|
||||||
@@ -3178,7 +3182,9 @@ abstract class DBObject implements iDisplay
|
|||||||
$this->DBInsertSingleTable($sParentClass);
|
$this->DBInsertSingleTable($sParentClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
$this->OnObjectKeyReady();
|
$this->OnObjectKeyReady();
|
||||||
|
$oKPI->ComputeStatsForExtension($this, 'OnObjectKeyReady');
|
||||||
$this->UpdateCurrentObjectInCrudStack();
|
$this->UpdateCurrentObjectInCrudStack();
|
||||||
|
|
||||||
$this->DBWriteLinks();
|
$this->DBWriteLinks();
|
||||||
@@ -3255,7 +3261,9 @@ abstract class DBObject implements iDisplay
|
|||||||
public function PostInsertActions(): void
|
public function PostInsertActions(): void
|
||||||
{
|
{
|
||||||
$this->FireEventAfterWrite([], true);
|
$this->FireEventAfterWrite([], true);
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
$this->AfterInsert();
|
$this->AfterInsert();
|
||||||
|
$oKPI->ComputeStatsForExtension($this, 'AfterInsert');
|
||||||
|
|
||||||
// Activate any existing trigger
|
// Activate any existing trigger
|
||||||
$sClass = get_class($this);
|
$sClass = get_class($this);
|
||||||
@@ -3353,7 +3361,9 @@ abstract class DBObject implements iDisplay
|
|||||||
try {
|
try {
|
||||||
$this->DoComputeValues();
|
$this->DoComputeValues();
|
||||||
$this->ComputeStopWatchesDeadline(false);
|
$this->ComputeStopWatchesDeadline(false);
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
$this->OnUpdate();
|
$this->OnUpdate();
|
||||||
|
$oKPI->ComputeStatsForExtension($this, 'OnUpdate');
|
||||||
|
|
||||||
$this->FireEventBeforeWrite();
|
$this->FireEventBeforeWrite();
|
||||||
|
|
||||||
@@ -3567,7 +3577,9 @@ abstract class DBObject implements iDisplay
|
|||||||
public function PostUpdateActions(array $aChanges): void
|
public function PostUpdateActions(array $aChanges): void
|
||||||
{
|
{
|
||||||
$this->FireEventAfterWrite($aChanges, false);
|
$this->FireEventAfterWrite($aChanges, false);
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
$this->AfterUpdate();
|
$this->AfterUpdate();
|
||||||
|
$oKPI->ComputeStatsForExtension($this, 'AfterUpdate');
|
||||||
|
|
||||||
// - TriggerOnObjectUpdate
|
// - TriggerOnObjectUpdate
|
||||||
$aParams = array('class_list' => MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL));
|
$aParams = array('class_list' => MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL));
|
||||||
@@ -3794,7 +3806,9 @@ abstract class DBObject implements iDisplay
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
$this->OnDelete();
|
$this->OnDelete();
|
||||||
|
$oKPI->ComputeStatsForExtension($this, 'OnDelete');
|
||||||
|
|
||||||
// Activate any existing trigger
|
// Activate any existing trigger
|
||||||
$sClass = get_class($this);
|
$sClass = get_class($this);
|
||||||
@@ -3902,7 +3916,9 @@ abstract class DBObject implements iDisplay
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->FireEventAfterDelete();
|
$this->FireEventAfterDelete();
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
$this->AfterDelete();
|
$this->AfterDelete();
|
||||||
|
$oKPI->ComputeStatsForExtension($this, 'AfterDelete');
|
||||||
|
|
||||||
|
|
||||||
$this->m_bIsInDB = false;
|
$this->m_bIsInDB = false;
|
||||||
|
|||||||
@@ -767,7 +767,10 @@ class DBObjectSet implements iDBObjectSetIterator
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
$this->m_oSQLResult = CMDBSource::Query($sSQL);
|
$this->m_oSQLResult = CMDBSource::Query($sSQL);
|
||||||
|
$sOQL = $this->GetPseudoOQL($this->m_oFilter, $this->GetRealSortOrder(), $this->m_iLimitCount, $this->m_iLimitStart, false);
|
||||||
|
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
|
||||||
} catch (MySQLException $e)
|
} catch (MySQLException $e)
|
||||||
{
|
{
|
||||||
// 1116 = ER_TOO_MANY_TABLES
|
// 1116 = ER_TOO_MANY_TABLES
|
||||||
@@ -847,8 +850,11 @@ class DBObjectSet implements iDBObjectSetIterator
|
|||||||
{
|
{
|
||||||
if (is_null($this->m_iNumTotalDBRows))
|
if (is_null($this->m_iNumTotalDBRows))
|
||||||
{
|
{
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, 0, 0, true);
|
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, 0, 0, true);
|
||||||
$resQuery = CMDBSource::Query($sSQL);
|
$resQuery = CMDBSource::Query($sSQL);
|
||||||
|
$sOQL = $this->GetPseudoOQL($this->m_oFilter, array(), 0, 0, true);
|
||||||
|
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
|
||||||
if (!$resQuery) return 0;
|
if (!$resQuery) return 0;
|
||||||
|
|
||||||
$aRow = CMDBSource::FetchArray($resQuery);
|
$aRow = CMDBSource::FetchArray($resQuery);
|
||||||
@@ -859,6 +865,42 @@ class DBObjectSet implements iDBObjectSetIterator
|
|||||||
return $this->m_iNumTotalDBRows + count($this->m_aAddedObjects); // Does it fix Trac #887 ??
|
return $this->m_iNumTotalDBRows + count($this->m_aAddedObjects); // Does it fix Trac #887 ??
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \DBSearch $oFilter
|
||||||
|
* @param array $aOrder
|
||||||
|
* @param int $iLimitCount
|
||||||
|
* @param int $iLimitStart
|
||||||
|
* @param bool $bCount
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function GetPseudoOQL($oFilter, $aOrder, $iLimitCount, $iLimitStart, $bCount)
|
||||||
|
{
|
||||||
|
$sOQL = '';
|
||||||
|
if ($bCount) {
|
||||||
|
$sOQL .= 'COUNT ';
|
||||||
|
}
|
||||||
|
$sOQL .= $oFilter->ToOQL();
|
||||||
|
|
||||||
|
if ($iLimitCount > 0) {
|
||||||
|
$sOQL .= ' LIMIT ';
|
||||||
|
if ($iLimitStart > 0) {
|
||||||
|
$sOQL .= "$iLimitStart, ";
|
||||||
|
}
|
||||||
|
$sOQL .= "$iLimitCount";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($aOrder) > 0) {
|
||||||
|
$sOQL .= ' ORDER BY ';
|
||||||
|
$aOrderBy = [];
|
||||||
|
foreach ($aOrder as $sAttCode => $bAsc) {
|
||||||
|
$aOrderBy[] = $sAttCode.' '.($bAsc ? 'ASC' : 'DESC');
|
||||||
|
}
|
||||||
|
$sOQL .= implode(', ', $aOrderBy);
|
||||||
|
}
|
||||||
|
return $sOQL;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the count exceeds a given limit
|
* Check if the count exceeds a given limit
|
||||||
*
|
*
|
||||||
@@ -875,8 +917,11 @@ class DBObjectSet implements iDBObjectSetIterator
|
|||||||
{
|
{
|
||||||
if (is_null($this->m_iNumTotalDBRows))
|
if (is_null($this->m_iNumTotalDBRows))
|
||||||
{
|
{
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true);
|
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true);
|
||||||
$resQuery = CMDBSource::Query($sSQL);
|
$resQuery = CMDBSource::Query($sSQL);
|
||||||
|
$sOQL = $this->GetPseudoOQL($this->m_oFilter, array(), $iLimit + 2, 0, true);
|
||||||
|
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
|
||||||
if ($resQuery)
|
if ($resQuery)
|
||||||
{
|
{
|
||||||
$aRow = CMDBSource::FetchArray($resQuery);
|
$aRow = CMDBSource::FetchArray($resQuery);
|
||||||
@@ -887,7 +932,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
|||||||
{
|
{
|
||||||
$iCount = 0;
|
$iCount = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$iCount = $this->m_iNumTotalDBRows;
|
$iCount = $this->m_iNumTotalDBRows;
|
||||||
@@ -912,8 +957,11 @@ class DBObjectSet implements iDBObjectSetIterator
|
|||||||
{
|
{
|
||||||
if (is_null($this->m_iNumTotalDBRows))
|
if (is_null($this->m_iNumTotalDBRows))
|
||||||
{
|
{
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true);
|
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true);
|
||||||
$resQuery = CMDBSource::Query($sSQL);
|
$resQuery = CMDBSource::Query($sSQL);
|
||||||
|
$sOQL = $this->GetPseudoOQL($this->m_oFilter, array(), $iLimit + 2, 0, true);
|
||||||
|
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
|
||||||
if ($resQuery)
|
if ($resQuery)
|
||||||
{
|
{
|
||||||
$aRow = CMDBSource::FetchArray($resQuery);
|
$aRow = CMDBSource::FetchArray($resQuery);
|
||||||
@@ -924,7 +972,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
|||||||
{
|
{
|
||||||
$iCount = 0;
|
$iCount = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$iCount = $this->m_iNumTotalDBRows;
|
$iCount = $this->m_iNumTotalDBRows;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
//
|
//
|
||||||
// This file is part of iTop.
|
// This file is part of iTop.
|
||||||
//
|
//
|
||||||
// iTop is free software; you can redistribute it and/or modify
|
// iTop is free software; you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
@@ -56,10 +56,11 @@ class Dict
|
|||||||
* @param $sLanguageCode
|
* @param $sLanguageCode
|
||||||
*
|
*
|
||||||
* @throws \DictExceptionUnknownLanguage
|
* @throws \DictExceptionUnknownLanguage
|
||||||
|
* @since 3.0.4 3.1.1 3.2.0 Param $sLanguageCode becomes nullable
|
||||||
*/
|
*/
|
||||||
public static function SetUserLanguage($sLanguageCode)
|
public static function SetUserLanguage($sLanguageCode = null)
|
||||||
{
|
{
|
||||||
if (!array_key_exists($sLanguageCode, self::$m_aLanguages))
|
if (!is_null($sLanguageCode) && !array_key_exists($sLanguageCode, self::$m_aLanguages))
|
||||||
{
|
{
|
||||||
throw new DictExceptionUnknownLanguage($sLanguageCode);
|
throw new DictExceptionUnknownLanguage($sLanguageCode);
|
||||||
}
|
}
|
||||||
@@ -115,33 +116,50 @@ class Dict
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function S($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
|
public static function S($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
|
||||||
|
{
|
||||||
|
$aInfo = self::GetLabelAndLangCode($sStringCode, $sDefault, $bUserLanguageOnly);
|
||||||
|
return $aInfo['label'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a localised string from the dictonary with its associated lang code
|
||||||
|
*
|
||||||
|
* @param string $sStringCode The code identifying the dictionary entry
|
||||||
|
* @param string $sDefault Default value if there is no match in the dictionary
|
||||||
|
* @param bool $bUserLanguageOnly True to allow the use of the default language as a fallback, false otherwise
|
||||||
|
*
|
||||||
|
* @return array{
|
||||||
|
* lang: string, label: string
|
||||||
|
* } with localized label string and used lang code
|
||||||
|
*/
|
||||||
|
private static function GetLabelAndLangCode($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
|
||||||
{
|
{
|
||||||
// Attempt to find the string in the user language
|
// Attempt to find the string in the user language
|
||||||
//
|
//
|
||||||
$sLangCode = self::GetUserLanguage();
|
$sLangCode = self::GetUserLanguage();
|
||||||
self::InitLangIfNeeded($sLangCode);
|
self::InitLangIfNeeded($sLangCode);
|
||||||
|
|
||||||
if (!array_key_exists($sLangCode, self::$m_aData))
|
if (! array_key_exists($sLangCode, self::$m_aData))
|
||||||
{
|
{
|
||||||
IssueLog::Warning("Cannot find $sLangCode in dictionnaries. default labels displayed");
|
IssueLog::Warning("Cannot find $sLangCode in all registered dictionaries.");
|
||||||
// It may happen, when something happens before the dictionaries get loaded
|
// It may happen, when something happens before the dictionaries get loaded
|
||||||
return $sStringCode;
|
return [ 'label' => $sStringCode, 'lang' => $sLangCode ];
|
||||||
}
|
}
|
||||||
$aCurrentDictionary = self::$m_aData[$sLangCode];
|
$aCurrentDictionary = self::$m_aData[$sLangCode];
|
||||||
if (is_array($aCurrentDictionary) && array_key_exists($sStringCode, $aCurrentDictionary))
|
if (is_array($aCurrentDictionary) && array_key_exists($sStringCode, $aCurrentDictionary))
|
||||||
{
|
{
|
||||||
return $aCurrentDictionary[$sStringCode];
|
return [ 'label' => $aCurrentDictionary[$sStringCode], 'lang' => $sLangCode ];
|
||||||
}
|
}
|
||||||
if (!$bUserLanguageOnly)
|
if (!$bUserLanguageOnly)
|
||||||
{
|
{
|
||||||
// Attempt to find the string in the default language
|
// Attempt to find the string in the default language
|
||||||
//
|
//
|
||||||
self::InitLangIfNeeded(self::$m_sDefaultLanguage);
|
self::InitLangIfNeeded(self::$m_sDefaultLanguage);
|
||||||
|
|
||||||
$aDefaultDictionary = self::$m_aData[self::$m_sDefaultLanguage];
|
$aDefaultDictionary = self::$m_aData[self::$m_sDefaultLanguage];
|
||||||
if (is_array($aDefaultDictionary) && array_key_exists($sStringCode, $aDefaultDictionary))
|
if (is_array($aDefaultDictionary) && array_key_exists($sStringCode, $aDefaultDictionary))
|
||||||
{
|
{
|
||||||
return $aDefaultDictionary[$sStringCode];
|
return [ 'label' => $aDefaultDictionary[$sStringCode], 'lang' => self::$m_sDefaultLanguage ];
|
||||||
}
|
}
|
||||||
// Attempt to find the string in english
|
// Attempt to find the string in english
|
||||||
//
|
//
|
||||||
@@ -150,17 +168,17 @@ class Dict
|
|||||||
$aDefaultDictionary = self::$m_aData['EN US'];
|
$aDefaultDictionary = self::$m_aData['EN US'];
|
||||||
if (is_array($aDefaultDictionary) && array_key_exists($sStringCode, $aDefaultDictionary))
|
if (is_array($aDefaultDictionary) && array_key_exists($sStringCode, $aDefaultDictionary))
|
||||||
{
|
{
|
||||||
return $aDefaultDictionary[$sStringCode];
|
return [ 'label' => $aDefaultDictionary[$sStringCode], 'lang' => 'EN US' ];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Could not find the string...
|
// Could not find the string...
|
||||||
//
|
//
|
||||||
if (is_null($sDefault))
|
if (is_null($sDefault))
|
||||||
{
|
{
|
||||||
return $sStringCode;
|
return [ 'label' => $sStringCode, 'lang' => null ];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $sDefault;
|
return [ 'label' => $sDefault, 'lang' => null ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -176,19 +194,25 @@ class Dict
|
|||||||
*/
|
*/
|
||||||
public static function Format($sFormatCode /*, ... arguments ... */)
|
public static function Format($sFormatCode /*, ... arguments ... */)
|
||||||
{
|
{
|
||||||
$sLocalizedFormat = self::S($sFormatCode);
|
['label' => $sLocalizedFormat, 'lang' => $sLangCode] = self::GetLabelAndLangCode($sFormatCode);
|
||||||
|
|
||||||
$aArguments = func_get_args();
|
$aArguments = func_get_args();
|
||||||
array_shift($aArguments);
|
array_shift($aArguments);
|
||||||
|
|
||||||
if ($sLocalizedFormat == $sFormatCode)
|
if ($sLocalizedFormat == $sFormatCode)
|
||||||
{
|
{
|
||||||
// Make sure the information will be displayed (ex: an error occuring before the dictionary gets loaded)
|
// Make sure the information will be displayed (ex: an error occuring before the dictionary gets loaded)
|
||||||
return $sFormatCode.' - '.implode(', ', $aArguments);
|
return $sFormatCode.' - '.implode(', ', $aArguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
return vsprintf($sLocalizedFormat, $aArguments);
|
try{
|
||||||
|
return vsprintf($sLocalizedFormat, $aArguments);
|
||||||
|
} catch(\Throwable $e){
|
||||||
|
\IssueLog::Error("Cannot format dict key", null, ["sFormatCode" => $sFormatCode, "sLangCode" => $sLangCode, 'exception_msg' => $e->getMessage() ]);
|
||||||
|
return $sFormatCode.' - '.implode(', ', $aArguments);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a the entries for a given language (replaces the former Add() method)
|
* Initialize a the entries for a given language (replaces the former Add() method)
|
||||||
* @param string $sLanguageCode Code identifying the language i.e. 'FR-FR', 'EN-US'
|
* @param string $sLanguageCode Code identifying the language i.e. 'FR-FR', 'EN-US'
|
||||||
@@ -198,7 +222,7 @@ class Dict
|
|||||||
{
|
{
|
||||||
self::$m_aData[$sLanguageCode] = $aEntries;
|
self::$m_aData[$sLanguageCode] = $aEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the list of available languages
|
* Set the list of available languages
|
||||||
* @param hash $aLanguagesList
|
* @param hash $aLanguagesList
|
||||||
@@ -259,7 +283,7 @@ class Dict
|
|||||||
{
|
{
|
||||||
$sDictFile = APPROOT.'env-'.utils::GetCurrentEnvironment().'/dictionaries/'.str_replace(' ', '-', strtolower($sLangCode)).'.dict.php';
|
$sDictFile = APPROOT.'env-'.utils::GetCurrentEnvironment().'/dictionaries/'.str_replace(' ', '-', strtolower($sLangCode)).'.dict.php';
|
||||||
require_once($sDictFile);
|
require_once($sDictFile);
|
||||||
|
|
||||||
if (self::GetApcService()->function_exists('apc_store')
|
if (self::GetApcService()->function_exists('apc_store')
|
||||||
&& (self::$m_sApplicationPrefix !== null))
|
&& (self::$m_sApplicationPrefix !== null))
|
||||||
{
|
{
|
||||||
@@ -269,7 +293,7 @@ class Dict
|
|||||||
}
|
}
|
||||||
return $bResult;
|
return $bResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable caching (cached using APC)
|
* Enable caching (cached using APC)
|
||||||
* @param string $sApplicationPrefix The prefix for uniquely identiying this iTop instance
|
* @param string $sApplicationPrefix The prefix for uniquely identiying this iTop instance
|
||||||
@@ -312,14 +336,14 @@ class Dict
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function MakeStats($sLanguageCode, $sLanguageRef = 'EN US')
|
public static function MakeStats($sLanguageCode, $sLanguageRef = 'EN US')
|
||||||
{
|
{
|
||||||
$aMissing = array(); // Strings missing for the target language
|
$aMissing = array(); // Strings missing for the target language
|
||||||
$aUnexpected = array(); // Strings defined for the target language, but not found in the reference dictionary
|
$aUnexpected = array(); // Strings defined for the target language, but not found in the reference dictionary
|
||||||
$aNotTranslated = array(); // Strings having the same value in both dictionaries
|
$aNotTranslated = array(); // Strings having the same value in both dictionaries
|
||||||
$aOK = array(); // Strings having different values in both dictionaries
|
$aOK = array(); // Strings having different values in both dictionaries
|
||||||
|
|
||||||
foreach (self::$m_aData[$sLanguageRef] as $sStringCode => $sValue)
|
foreach (self::$m_aData[$sLanguageRef] as $sStringCode => $sValue)
|
||||||
{
|
{
|
||||||
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageCode]))
|
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageCode]))
|
||||||
@@ -327,7 +351,7 @@ class Dict
|
|||||||
$aMissing[$sStringCode] = $sValue;
|
$aMissing[$sStringCode] = $sValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (self::$m_aData[$sLanguageCode] as $sStringCode => $sValue)
|
foreach (self::$m_aData[$sLanguageCode] as $sStringCode => $sValue)
|
||||||
{
|
{
|
||||||
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageRef]))
|
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageRef]))
|
||||||
@@ -350,7 +374,7 @@ class Dict
|
|||||||
}
|
}
|
||||||
return array($aMissing, $aUnexpected, $aNotTranslated, $aOK);
|
return array($aMissing, $aUnexpected, $aNotTranslated, $aOK);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function Dump()
|
public static function Dump()
|
||||||
{
|
{
|
||||||
MyHelpers::var_dump_html(self::$m_aData);
|
MyHelpers::var_dump_html(self::$m_aData);
|
||||||
@@ -373,7 +397,7 @@ class Dict
|
|||||||
// No need to actually load the strings since it's only used to know the list of languages
|
// No need to actually load the strings since it's only used to know the list of languages
|
||||||
// at setup time !!
|
// at setup time !!
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export all the dictionary entries - of the given language - whose code matches the given prefix
|
* Export all the dictionary entries - of the given language - whose code matches the given prefix
|
||||||
* missing entries in the current language will be replaced by entries in the default language
|
* missing entries in the current language will be replaced by entries in the default language
|
||||||
@@ -386,7 +410,7 @@ class Dict
|
|||||||
self::InitLangIfNeeded(self::$m_sDefaultLanguage);
|
self::InitLangIfNeeded(self::$m_sDefaultLanguage);
|
||||||
$aEntries = array();
|
$aEntries = array();
|
||||||
$iLength = strlen($sStartingWith);
|
$iLength = strlen($sStartingWith);
|
||||||
|
|
||||||
// First prefill the array with entries from the default language
|
// First prefill the array with entries from the default language
|
||||||
foreach(self::$m_aData[self::$m_sDefaultLanguage] as $sCode => $sEntry)
|
foreach(self::$m_aData[self::$m_sDefaultLanguage] as $sCode => $sEntry)
|
||||||
{
|
{
|
||||||
@@ -395,7 +419,7 @@ class Dict
|
|||||||
$aEntries[$sCode] = $sEntry;
|
$aEntries[$sCode] = $sEntry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now put (overwrite) the entries for the user language
|
// Now put (overwrite) the entries for the user language
|
||||||
foreach(self::$m_aData[self::GetUserLanguage()] as $sCode => $sEntry)
|
foreach(self::$m_aData[self::GetUserLanguage()] as $sCode => $sEntry)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,27 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
// Copyright (C) 2010-2023 Combodo SARL
|
/**
|
||||||
//
|
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||||
// This file is part of iTop.
|
* @license http://opensource.org/licenses/AGPL-3.0
|
||||||
//
|
*/
|
||||||
// iTop is free software; you can redistribute it and/or modify
|
use Combodo\iTop\Core\Kpi\KpiLogData;
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
use Combodo\iTop\Service\Module\ModuleService;
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// iTop is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Measures operations duration, memory usage, etc. (and some other KPIs)
|
* Measures operations duration, memory usage, etc. (and some other KPIs)
|
||||||
*
|
|
||||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
|
||||||
* @license http://opensource.org/licenses/AGPL-3.0
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class ExecutionKPI
|
class ExecutionKPI
|
||||||
@@ -30,6 +17,8 @@ class ExecutionKPI
|
|||||||
static protected $m_bEnabled_Memory = false;
|
static protected $m_bEnabled_Memory = false;
|
||||||
static protected $m_bBlameCaller = false;
|
static protected $m_bBlameCaller = false;
|
||||||
static protected $m_sAllowedUser = '*';
|
static protected $m_sAllowedUser = '*';
|
||||||
|
static protected $m_bGenerateLegacyReport = true;
|
||||||
|
static protected $m_fSlowQueries = 0;
|
||||||
|
|
||||||
static protected $m_aStats = []; // Recurrent operations
|
static protected $m_aStats = []; // Recurrent operations
|
||||||
static protected $m_aExecData = []; // One shot operations
|
static protected $m_aExecData = []; // One shot operations
|
||||||
@@ -86,14 +75,39 @@ class ExecutionKPI
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public function SetGenerateLegacyReport($bReportExtensionsOnly)
|
||||||
|
{
|
||||||
|
self::$m_bGenerateLegacyReport = $bReportExtensionsOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function SetSlowQueries($fSlowQueries)
|
||||||
|
{
|
||||||
|
self::$m_fSlowQueries = $fSlowQueries;
|
||||||
|
}
|
||||||
|
|
||||||
static public function GetDescription()
|
static public function GetDescription()
|
||||||
{
|
{
|
||||||
$aFeatures = array();
|
$aFeatures = array();
|
||||||
if (self::$m_bEnabled_Duration) $aFeatures[] = 'Duration';
|
if (self::$m_bEnabled_Duration) $aFeatures[] = 'Duration';
|
||||||
if (self::$m_bEnabled_Memory) $aFeatures[] = 'Memory usage';
|
if (self::$m_bEnabled_Memory) $aFeatures[] = 'Memory usage';
|
||||||
$sFeatures = implode(', ', $aFeatures);
|
$sFeatures = 'Measures: '.implode(', ', $aFeatures);
|
||||||
$sFor = self::$m_sAllowedUser == '*' ? 'EVERYBODY' : "'".trim(self::$m_sAllowedUser)."'";
|
$sFor = self::$m_sAllowedUser == '*' ? 'EVERYBODY' : "'".trim(self::$m_sAllowedUser)."'";
|
||||||
return "KPI logging is active for $sFor. Measures: $sFeatures";
|
$sSlowQueries = '';
|
||||||
|
if (self::$m_fSlowQueries > 0) {
|
||||||
|
$sSlowQueries = ". Slow Queries: ".self::$m_fSlowQueries."s";
|
||||||
|
}
|
||||||
|
|
||||||
|
$aExtensions = [];
|
||||||
|
/** @var \iKPILoggerExtension $oExtensionInstance */
|
||||||
|
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
|
||||||
|
$aExtensions[] = ModuleService::GetInstance()->GetModuleNameFromObject($oExtensionInstance);
|
||||||
|
}
|
||||||
|
$sExtensions = '';
|
||||||
|
if (count($aExtensions) > 0) {
|
||||||
|
$sExtensions = '. KPI Extensions: ['.implode(', ', $aExtensions).']';
|
||||||
|
}
|
||||||
|
|
||||||
|
return "KPI logging is active for $sFor. $sFeatures$sSlowQueries$sExtensions";
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function ReportStats()
|
static public function ReportStats()
|
||||||
@@ -101,7 +115,28 @@ class ExecutionKPI
|
|||||||
if (!self::IsEnabled()) return;
|
if (!self::IsEnabled()) return;
|
||||||
|
|
||||||
global $fItopStarted;
|
global $fItopStarted;
|
||||||
|
global $iItopInitialMemory;
|
||||||
$sExecId = microtime(); // id to differentiate the hrefs!
|
$sExecId = microtime(); // id to differentiate the hrefs!
|
||||||
|
$sRequest = $_SERVER['REQUEST_URI'].' ('.$_SERVER['REQUEST_METHOD'].')';
|
||||||
|
if (isset($_POST['operation'])) {
|
||||||
|
$sRequest .= ' operation: '.$_POST['operation'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$fStop = MyHelpers::getmicrotime();
|
||||||
|
if (($fStop - $fItopStarted) > self::$m_fSlowQueries) {
|
||||||
|
// Invoke extensions to log the KPI operation
|
||||||
|
/** @var \iKPILoggerExtension $oExtensionInstance */
|
||||||
|
$iCurrentMemory = self::memory_get_usage();
|
||||||
|
$iPeakMemory = self::memory_get_peak_usage();
|
||||||
|
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
|
||||||
|
$oKPILogData = new KpiLogData(KpiLogData::TYPE_REQUEST, 'Page', $sRequest, $fItopStarted, $fStop, '', $iItopInitialMemory, $iCurrentMemory, $iPeakMemory);
|
||||||
|
$oExtensionInstance->LogOperation($oKPILogData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self::$m_bGenerateLegacyReport) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$aBeginTimes = array();
|
$aBeginTimes = array();
|
||||||
foreach (self::$m_aExecData as $aOpStats)
|
foreach (self::$m_aExecData as $aOpStats)
|
||||||
@@ -114,9 +149,9 @@ class ExecutionKPI
|
|||||||
|
|
||||||
$sHtml = "<hr/>";
|
$sHtml = "<hr/>";
|
||||||
$sHtml .= "<div style=\"background-color: grey; padding: 10px;\">";
|
$sHtml .= "<div style=\"background-color: grey; padding: 10px;\">";
|
||||||
$sHtml .= "<h3><a name=\"".md5($sExecId)."\">KPIs</a> - ".$_SERVER['REQUEST_URI']." (".$_SERVER['REQUEST_METHOD'].")</h3>";
|
$sHtml .= "<h3><a name=\"".md5($sExecId)."\">KPIs</a> - $sRequest</h3>";
|
||||||
$oStarted = DateTime::createFromFormat('U.u', $fItopStarted);
|
$oStarted = DateTime::createFromFormat('U.u', $fItopStarted);
|
||||||
$sHtml .= "<p>".$oStarted->format('Y-m-d H:i:s.u')."</p>";
|
$sHtml .= '<p>'.$oStarted->format('Y-m-d H:i:s.u').'</p>';
|
||||||
$sHtml .= "<p>log_kpi_user_id: ".UserRights::GetUserId()."</p>";
|
$sHtml .= "<p>log_kpi_user_id: ".UserRights::GetUserId()."</p>";
|
||||||
$sHtml .= "<div>";
|
$sHtml .= "<div>";
|
||||||
$sHtml .= "<table border=\"1\" style=\"$sTableStyle\">";
|
$sHtml .= "<table border=\"1\" style=\"$sTableStyle\">";
|
||||||
@@ -257,7 +292,7 @@ class ExecutionKPI
|
|||||||
$sTotalInter = round($fTotalInter, 3);
|
$sTotalInter = round($fTotalInter, 3);
|
||||||
$sMinInter = round($fMinInter, 3);
|
$sMinInter = round($fMinInter, 3);
|
||||||
$sMaxInter = round($fMaxInter, 3);
|
$sMaxInter = round($fMaxInter, 3);
|
||||||
if (($fTotalInter >= $fSlowQueries))
|
if (($fTotalInter >= self::$m_fSlowQueries))
|
||||||
{
|
{
|
||||||
if ($bDisplayHeader)
|
if ($bDisplayHeader)
|
||||||
{
|
{
|
||||||
@@ -285,37 +320,19 @@ class ExecutionKPI
|
|||||||
self::Report($sHtml);
|
self::Report($sHtml);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function InitStats()
|
||||||
|
{
|
||||||
|
// Invoke extensions to initialize the KPI statistics
|
||||||
|
/** @var \iKPILoggerExtension $oExtensionInstance */
|
||||||
|
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
|
||||||
|
$oExtensionInstance->InitStats();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->ResetCounters();
|
$this->ResetCounters();
|
||||||
self::Push($this);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stack executions to remove children duration from stats
|
|
||||||
*
|
|
||||||
* @param \ExecutionKPI $oExecutionKPI
|
|
||||||
*/
|
|
||||||
private static function Push(ExecutionKPI $oExecutionKPI)
|
|
||||||
{
|
|
||||||
self::$m_aExecutionStack[] = $oExecutionKPI;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pop current child and count its duration in its parent
|
|
||||||
*
|
|
||||||
* @param float|int $fChildDuration
|
|
||||||
*/
|
|
||||||
private static function Pop(float $fChildDuration = 0)
|
|
||||||
{
|
|
||||||
array_pop(self::$m_aExecutionStack);
|
|
||||||
// Update the parent's children duration
|
|
||||||
$oPrevExecutionKPI = end(self::$m_aExecutionStack);
|
|
||||||
if ($oPrevExecutionKPI) {
|
|
||||||
$oPrevExecutionKPI->m_fChildrenDuration += $fChildDuration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the duration since startup, and reset the counter for the next measure
|
// Get the duration since startup, and reset the counter for the next measure
|
||||||
//
|
//
|
||||||
@@ -323,9 +340,15 @@ class ExecutionKPI
|
|||||||
{
|
{
|
||||||
global $fItopStarted;
|
global $fItopStarted;
|
||||||
|
|
||||||
|
if (!self::IsEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$aNewEntry = null;
|
$aNewEntry = null;
|
||||||
|
|
||||||
if (self::$m_bEnabled_Duration) {
|
$fStarted = $this->m_fStarted;
|
||||||
|
$fStopped = $this->m_fStarted;
|
||||||
|
if (self::$m_bEnabled_Duration) {
|
||||||
$fStopped = MyHelpers::getmicrotime();
|
$fStopped = MyHelpers::getmicrotime();
|
||||||
$aNewEntry = array(
|
$aNewEntry = array(
|
||||||
'op' => $sOperationDesc,
|
'op' => $sOperationDesc,
|
||||||
@@ -336,6 +359,9 @@ class ExecutionKPI
|
|||||||
$this->m_fStarted = $fStopped;
|
$this->m_fStarted = $fStopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$iInitialMemory = is_null($this->m_iInitialMemory) ? 0 : $this->m_iInitialMemory;
|
||||||
|
$iCurrentMemory = 0;
|
||||||
|
$iPeakMemory = 0;
|
||||||
if (self::$m_bEnabled_Memory)
|
if (self::$m_bEnabled_Memory)
|
||||||
{
|
{
|
||||||
$iCurrentMemory = self::memory_get_usage();
|
$iCurrentMemory = self::memory_get_usage();
|
||||||
@@ -345,40 +371,103 @@ class ExecutionKPI
|
|||||||
}
|
}
|
||||||
$aNewEntry['mem_begin'] = $this->m_iInitialMemory;
|
$aNewEntry['mem_begin'] = $this->m_iInitialMemory;
|
||||||
$aNewEntry['mem_end'] = $iCurrentMemory;
|
$aNewEntry['mem_end'] = $iCurrentMemory;
|
||||||
if (function_exists('memory_get_peak_usage'))
|
$iPeakMemory = self::memory_get_peak_usage();
|
||||||
{
|
$aNewEntry['mem_peak'] = $iPeakMemory;
|
||||||
$aNewEntry['mem_peak'] = memory_get_peak_usage();
|
|
||||||
}
|
|
||||||
// Reset for the next operation (if the object is recycled)
|
// Reset for the next operation (if the object is recycled)
|
||||||
$this->m_iInitialMemory = $iCurrentMemory;
|
$this->m_iInitialMemory = $iCurrentMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_null($aNewEntry))
|
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)
|
||||||
|
{
|
||||||
|
$sExtension = ModuleService::GetInstance()->GetModuleNameFromCallStack(1);
|
||||||
|
$oKPILogData = new KpiLogData(
|
||||||
|
KpiLogData::TYPE_REPORT,
|
||||||
|
'Step',
|
||||||
|
$sOperationDesc,
|
||||||
|
$fStarted,
|
||||||
|
$fStopped,
|
||||||
|
$sExtension,
|
||||||
|
$iInitialMemory,
|
||||||
|
$iCurrentMemory,
|
||||||
|
$iPeakMemory);
|
||||||
|
$oExtensionInstance->LogOperation($oKPILogData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_null($aNewEntry) && self::$m_bGenerateLegacyReport)
|
||||||
{
|
{
|
||||||
self::$m_aExecData[] = $aNewEntry;
|
self::$m_aExecData[] = $aNewEntry;
|
||||||
}
|
}
|
||||||
$this->ResetCounters();
|
$this->ResetCounters();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function ComputeStatsForExtension($object, $sMethod)
|
||||||
|
{
|
||||||
|
if (!self::IsEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sSignature = ModuleService::GetInstance()->GetModuleMethodSignature($object, $sMethod);
|
||||||
|
if (utils::StartsWith($sSignature, '[')) {
|
||||||
|
$this->ComputeStats('Extension', $sSignature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function ComputeStats($sOperation, $sArguments)
|
public function ComputeStats($sOperation, $sArguments)
|
||||||
{
|
{
|
||||||
|
if (!self::IsEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$fDuration = 0;
|
$fDuration = 0;
|
||||||
if (self::$m_bEnabled_Duration) {
|
if (self::$m_bEnabled_Duration) {
|
||||||
$fStopped = MyHelpers::getmicrotime();
|
$fStopped = MyHelpers::getmicrotime();
|
||||||
$fDuration = $fStopped - $this->m_fStarted;
|
$fDuration = $fStopped - $this->m_fStarted;
|
||||||
$fSelfDuration = $fDuration - $this->m_fChildrenDuration;
|
$aCallstack = [];
|
||||||
if (self::$m_bBlameCaller) {
|
if (self::$m_bGenerateLegacyReport) {
|
||||||
self::$m_aStats[$sOperation][$sArguments][] = array(
|
if (self::$m_bBlameCaller) {
|
||||||
'time' => $fSelfDuration,
|
$aCallstack = MyHelpers::get_callstack(1);
|
||||||
'callers' => MyHelpers::get_callstack(1),
|
self::$m_aStats[$sOperation][$sArguments][] = [
|
||||||
);
|
'time' => $fDuration,
|
||||||
} else {
|
'callers' => $aCallstack,
|
||||||
self::$m_aStats[$sOperation][$sArguments][] = array(
|
];
|
||||||
'time' => $fSelfDuration,
|
} else {
|
||||||
);
|
self::$m_aStats[$sOperation][$sArguments][] = [
|
||||||
}
|
'time' => $fDuration
|
||||||
}
|
];
|
||||||
self::Pop($fDuration);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$iInitialMemory = is_null($this->m_iInitialMemory) ? 0 : $this->m_iInitialMemory;
|
||||||
|
$iCurrentMemory = 0;
|
||||||
|
$iPeakMemory = 0;
|
||||||
|
if (self::$m_bEnabled_Memory)
|
||||||
|
{
|
||||||
|
$iCurrentMemory = self::memory_get_usage();
|
||||||
|
$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);
|
||||||
|
$oKPILogData = new KpiLogData(
|
||||||
|
KpiLogData::TYPE_STATS,
|
||||||
|
$sOperation,
|
||||||
|
$sArguments,
|
||||||
|
$this->m_fStarted,
|
||||||
|
$fStopped,
|
||||||
|
$sExtension,
|
||||||
|
$iInitialMemory,
|
||||||
|
$iCurrentMemory,
|
||||||
|
$iPeakMemory,
|
||||||
|
$aCallstack);
|
||||||
|
$oExtensionInstance->LogOperation($oKPILogData);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function ResetCounters()
|
protected function ResetCounters()
|
||||||
@@ -408,35 +497,7 @@ class ExecutionKPI
|
|||||||
|
|
||||||
static protected function memory_get_usage()
|
static protected function memory_get_usage()
|
||||||
{
|
{
|
||||||
if (function_exists('memory_get_usage'))
|
return memory_get_usage(true);
|
||||||
{
|
|
||||||
return memory_get_usage(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copied from the PHP manual
|
|
||||||
//
|
|
||||||
//If its Windows
|
|
||||||
//Tested on Win XP Pro SP2. Should work on Win 2003 Server too
|
|
||||||
//Doesn't work for 2000
|
|
||||||
//If you need it to work for 2000 look at http://us2.php.net/manual/en/function.memory-get-usage.php#54642
|
|
||||||
if (substr(PHP_OS,0,3) == 'WIN')
|
|
||||||
{
|
|
||||||
$output = array();
|
|
||||||
exec('tasklist /FI "PID eq ' . getmypid() . '" /FO LIST', $output);
|
|
||||||
|
|
||||||
return preg_replace( '/[\D]/', '', $output[5] ) * 1024;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//We now assume the OS is UNIX
|
|
||||||
//Tested on Mac OS X 10.4.6 and Linux Red Hat Enterprise 4
|
|
||||||
//This should work on most UNIX systems
|
|
||||||
$pid = getmypid();
|
|
||||||
exec("ps -eo%mem,rss,pid | grep $pid", $output);
|
|
||||||
$output = explode(" ", $output[0]);
|
|
||||||
//rss is given in 1024 byte units
|
|
||||||
return $output[1] * 1024;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function memory_get_peak_usage($bRealUsage = false)
|
static public function memory_get_peak_usage($bRealUsage = false)
|
||||||
|
|||||||
@@ -1138,7 +1138,7 @@ class DeprecatedCallsLog extends LogAPI
|
|||||||
parent::Enable($sTargetFile);
|
parent::Enable($sTargetFile);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(false === defined(ITOP_PHPUNIT_RUNNING_CONSTANT_NAME))
|
(false === defined('ITOP_PHPUNIT_RUNNING_CONSTANT_NAME'))
|
||||||
&& static::IsLogLevelEnabledSafe(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_LIBMETHOD)
|
&& static::IsLogLevelEnabledSafe(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_LIBMETHOD)
|
||||||
) {
|
) {
|
||||||
set_error_handler([static::class, 'DeprecatedNoticesErrorHandler'], E_DEPRECATED | E_USER_DEPRECATED);
|
set_error_handler([static::class, 'DeprecatedNoticesErrorHandler'], E_DEPRECATED | E_USER_DEPRECATED);
|
||||||
|
|||||||
@@ -6298,6 +6298,13 @@ abstract class MetaModel
|
|||||||
*/
|
*/
|
||||||
public static function Startup($config, $bModelOnly = false, $bAllowCache = true, $bTraceSourceFiles = false, $sEnvironment = 'production')
|
public static function Startup($config, $bModelOnly = false, $bAllowCache = true, $bTraceSourceFiles = false, $sEnvironment = 'production')
|
||||||
{
|
{
|
||||||
|
// Startup on a new environment is not supported
|
||||||
|
static $bStarted = false;
|
||||||
|
if ($bStarted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$bStarted = true;
|
||||||
|
|
||||||
self::$m_sEnvironment = $sEnvironment;
|
self::$m_sEnvironment = $sEnvironment;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -6376,7 +6383,9 @@ abstract class MetaModel
|
|||||||
|
|
||||||
ExecutionKPI::EnableDuration(self::$m_oConfig->Get('log_kpi_duration'));
|
ExecutionKPI::EnableDuration(self::$m_oConfig->Get('log_kpi_duration'));
|
||||||
ExecutionKPI::EnableMemory(self::$m_oConfig->Get('log_kpi_memory'));
|
ExecutionKPI::EnableMemory(self::$m_oConfig->Get('log_kpi_memory'));
|
||||||
ExecutionKPI::SetAllowedUser(self::$m_oConfig->Get('log_kpi_user_id'));
|
ExecutionKPI::SetAllowedUser(self::$m_oConfig->Get('log_kpi_user_id'));
|
||||||
|
ExecutionKPI::SetGenerateLegacyReport(self::$m_oConfig->Get('log_kpi_generate_legacy_report'));
|
||||||
|
ExecutionKPI::SetSlowQueries(self::$m_oConfig->Get('log_kpi_slow_queries'));
|
||||||
|
|
||||||
self::$m_bSkipCheckToWrite = self::$m_oConfig->Get('skip_check_to_write');
|
self::$m_bSkipCheckToWrite = self::$m_oConfig->Get('skip_check_to_write');
|
||||||
self::$m_bSkipCheckExtKeys = self::$m_oConfig->Get('skip_check_ext_keys');
|
self::$m_bSkipCheckExtKeys = self::$m_oConfig->Get('skip_check_ext_keys');
|
||||||
@@ -6495,6 +6504,7 @@ abstract class MetaModel
|
|||||||
|
|
||||||
CMDBSource::InitFromConfig(self::$m_oConfig);
|
CMDBSource::InitFromConfig(self::$m_oConfig);
|
||||||
// Later when timezone implementation is correctly done: CMDBSource::SetTimezone($sDBTimezone);
|
// Later when timezone implementation is correctly done: CMDBSource::SetTimezone($sDBTimezone);
|
||||||
|
ExecutionKPI::InitStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -6526,6 +6536,19 @@ abstract class MetaModel
|
|||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal Used for resetting the configuration during automated tests
|
||||||
|
|
||||||
|
* @param \Config $oConfiguration
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @since 3.0.4 3.1.1 3.2.0
|
||||||
|
*/
|
||||||
|
public static function SetConfig(Config $oConfiguration)
|
||||||
|
{
|
||||||
|
self::$m_oConfig = $oConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Config
|
* @return Config
|
||||||
*/
|
*/
|
||||||
@@ -6807,25 +6830,21 @@ abstract class MetaModel
|
|||||||
* $bMustBeFound=false)
|
* $bMustBeFound=false)
|
||||||
* @throws CoreException if no result found and $bMustBeFound=true
|
* @throws CoreException if no result found and $bMustBeFound=true
|
||||||
* @throws ArchivedObjectException if archive mode disabled and result is archived and $bMustBeFound=true
|
* @throws ArchivedObjectException if archive mode disabled and result is archived and $bMustBeFound=true
|
||||||
* @throws \Exception
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public static function GetObject($sClass, $iKey, $bMustBeFound = true, $bAllowAllData = false, $aModifierProperties = null)
|
public static function GetObject($sClass, $iKey, $bMustBeFound = true, $bAllowAllData = false, $aModifierProperties = null)
|
||||||
{
|
{
|
||||||
$oObject = self::GetObjectWithArchive($sClass, $iKey, $bMustBeFound, $bAllowAllData, $aModifierProperties);
|
$oObject = self::GetObjectWithArchive($sClass, $iKey, $bMustBeFound, $bAllowAllData, $aModifierProperties);
|
||||||
|
|
||||||
if (empty($oObject))
|
if (empty($oObject)) {
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!utils::IsArchiveMode() && $oObject->IsArchived())
|
if (!utils::IsArchiveMode() && $oObject->IsArchived()) {
|
||||||
{
|
|
||||||
if ($bMustBeFound) {
|
if ($bMustBeFound) {
|
||||||
throw new ArchivedObjectException("The object $sClass::$iKey is archived");
|
throw new ArchivedObjectException("The object $sClass::$iKey is archived");
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $oObject;
|
return $oObject;
|
||||||
@@ -7608,14 +7627,12 @@ abstract class MetaModel
|
|||||||
// Build the list of available extensions
|
// Build the list of available extensions
|
||||||
//
|
//
|
||||||
$aInterfaces = [
|
$aInterfaces = [
|
||||||
'iApplicationUIExtension',
|
|
||||||
'iPreferencesExtension',
|
|
||||||
'iApplicationObjectExtension',
|
|
||||||
'iLoginFSMExtension',
|
'iLoginFSMExtension',
|
||||||
'iLoginUIExtension',
|
|
||||||
'iLogoutExtension',
|
'iLogoutExtension',
|
||||||
'iQueryModifier',
|
'iLoginUIExtension',
|
||||||
'iOnClassInitialization',
|
'iPreferencesExtension',
|
||||||
|
'iApplicationUIExtension',
|
||||||
|
'iApplicationObjectExtension',
|
||||||
'iPopupMenuExtension',
|
'iPopupMenuExtension',
|
||||||
'iPageUIExtension',
|
'iPageUIExtension',
|
||||||
'iPageUIBlockExtension',
|
'iPageUIBlockExtension',
|
||||||
@@ -7629,9 +7646,12 @@ abstract class MetaModel
|
|||||||
'iBackofficeDictEntriesExtension',
|
'iBackofficeDictEntriesExtension',
|
||||||
'iBackofficeDictEntriesPrefixesExtension',
|
'iBackofficeDictEntriesPrefixesExtension',
|
||||||
'iPortalUIExtension',
|
'iPortalUIExtension',
|
||||||
|
'iQueryModifier',
|
||||||
|
'iOnClassInitialization',
|
||||||
|
'iModuleExtension',
|
||||||
|
'iKPILoggerExtension',
|
||||||
'ModuleHandlerApiInterface',
|
'ModuleHandlerApiInterface',
|
||||||
'iNewsroomProvider',
|
'iNewsroomProvider',
|
||||||
'iModuleExtension',
|
|
||||||
];
|
];
|
||||||
foreach ($aInterfaces as $sInterface) {
|
foreach ($aInterfaces as $sInterface) {
|
||||||
self::$m_aExtensionClassNames[$sInterface] = array();
|
self::$m_aExtensionClassNames[$sInterface] = array();
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ class iTopMutex
|
|||||||
$this->hDBLink = CMDBSource::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, false);
|
$this->hDBLink = CMDBSource::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, false);
|
||||||
|
|
||||||
if (!$this->hDBLink) {
|
if (!$this->hDBLink) {
|
||||||
throw new Exception("Could not connect to the DB server (host=$sServer, user=$sUser): ".mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno().')');
|
throw new MySQLException('Could not connect to the DB server '.mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno(), array('host' => $sDBHost, 'user' => $sDBUser));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure that the server variable `wait_timeout` is at least 86400 seconds for this connection,
|
// Make sure that the server variable `wait_timeout` is at least 86400 seconds for this connection,
|
||||||
|
|||||||
@@ -121,7 +121,9 @@ abstract class Trigger extends cmdbAbstractObject
|
|||||||
$oAction = MetaModel::GetObject('Action', $iActionId);
|
$oAction = MetaModel::GetObject('Action', $iActionId);
|
||||||
if ($oAction->IsActive())
|
if ($oAction->IsActive())
|
||||||
{
|
{
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
$oAction->DoExecute($this, $aContextArgs);
|
$oAction->DoExecute($this, $aContextArgs);
|
||||||
|
$oKPI->ComputeStatsForExtension($oAction, 'DoExecute');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -761,14 +761,25 @@ class UserRights
|
|||||||
protected static $m_aCacheContactPictureAbsUrl = [];
|
protected static $m_aCacheContactPictureAbsUrl = [];
|
||||||
/** @var UserRightsAddOnAPI $m_oAddOn */
|
/** @var UserRightsAddOnAPI $m_oAddOn */
|
||||||
protected static $m_oAddOn;
|
protected static $m_oAddOn;
|
||||||
protected static $m_oUser;
|
protected static $m_oUser = null;
|
||||||
protected static $m_oRealUser;
|
protected static $m_oRealUser = null;
|
||||||
protected static $m_sSelfRegisterAddOn = null;
|
protected static $m_sSelfRegisterAddOn = null;
|
||||||
protected static $m_aAdmins = array();
|
protected static $m_aAdmins = array();
|
||||||
protected static $m_aPortalUsers = array();
|
protected static $m_aPortalUsers = array();
|
||||||
/** @var array array('sName' => $sName, 'bSuccess' => $bSuccess); */
|
/** @var array array('sName' => $sName, 'bSuccess' => $bSuccess); */
|
||||||
private static $m_sLastLoginStatus = null;
|
private static $m_sLastLoginStatus = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
* @since 3.0.4 3.1.1 3.2.0
|
||||||
|
*/
|
||||||
|
protected static function ResetCurrentUserData()
|
||||||
|
{
|
||||||
|
self::$m_oUser = null;
|
||||||
|
self::$m_oRealUser = null;
|
||||||
|
self::$m_sLastLoginStatus = null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $sModuleName
|
* @param string $sModuleName
|
||||||
*
|
*
|
||||||
@@ -787,8 +798,7 @@ class UserRights
|
|||||||
}
|
}
|
||||||
self::$m_oAddOn = new $sModuleName;
|
self::$m_oAddOn = new $sModuleName;
|
||||||
self::$m_oAddOn->Init();
|
self::$m_oAddOn->Init();
|
||||||
self::$m_oUser = null;
|
self::ResetCurrentUserData();
|
||||||
self::$m_oRealUser = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -855,6 +865,8 @@ class UserRights
|
|||||||
*/
|
*/
|
||||||
public static function Login($sLogin, $sAuthentication = 'any')
|
public static function Login($sLogin, $sAuthentication = 'any')
|
||||||
{
|
{
|
||||||
|
static::Logoff();
|
||||||
|
|
||||||
$oUser = self::FindUser($sLogin, $sAuthentication);
|
$oUser = self::FindUser($sLogin, $sAuthentication);
|
||||||
if (is_null($oUser))
|
if (is_null($oUser))
|
||||||
{
|
{
|
||||||
@@ -872,6 +884,17 @@ class UserRights
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
* @since 3.0.4 3.1.1 3.2.0
|
||||||
|
*/
|
||||||
|
public static function Logoff()
|
||||||
|
{
|
||||||
|
self::ResetCurrentUserData();
|
||||||
|
Dict::SetUserLanguage(null);
|
||||||
|
self::_ResetSessionCache();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $sLogin Login of the user to check the credentials for
|
* @param string $sLogin Login of the user to check the credentials for
|
||||||
* @param string $sPassword
|
* @param string $sPassword
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ $ibo-dashlet-badge--icon--size: 48px !default;
|
|||||||
|
|
||||||
$ibo-dashlet-badge--action-icon--margin-right: $ibo-spacing-300 !default;
|
$ibo-dashlet-badge--action-icon--margin-right: $ibo-spacing-300 !default;
|
||||||
|
|
||||||
|
$ibo-dashlet-badge--body--tooltip-title--margin-bottom: $ibo-spacing-500 !default;
|
||||||
|
|
||||||
/* CSS variables (can be changed directly from the browser) */
|
/* CSS variables (can be changed directly from the browser) */
|
||||||
:root {
|
:root {
|
||||||
--ibo-dashlet-badge--min-width: #{$ibo-dashlet-badge--min-width};
|
--ibo-dashlet-badge--min-width: #{$ibo-dashlet-badge--min-width};
|
||||||
@@ -74,18 +76,27 @@ $ibo-dashlet-badge--action-icon--margin-right: $ibo-spacing-300 !default;
|
|||||||
@extend %ibo-hyperlink-inherited-colors;
|
@extend %ibo-hyperlink-inherited-colors;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ibo-dashlet-badge--action-list-count{
|
|
||||||
margin-right: $ibo-dashlet-badge--action-list-count--margin-right;
|
.ibo-dashlet-badge--action-list-count {
|
||||||
@extend %ibo-font-ral-bol-450;
|
margin-right: $ibo-dashlet-badge--action-list-count--margin-right;
|
||||||
|
@extend %ibo-font-ral-bol-450;
|
||||||
}
|
}
|
||||||
.ibo-dashlet-badge--action-list-label{
|
|
||||||
display: inline-block;
|
.ibo-dashlet-badge--action-list-label {
|
||||||
@extend %ibo-text-truncated-with-ellipsis;
|
display: inline-block;
|
||||||
|
@extend %ibo-text-truncated-with-ellipsis;
|
||||||
}
|
}
|
||||||
.ibo-dashlet-badge--action-create{
|
|
||||||
@extend %ibo-baseline-centered-content;
|
.ibo-dashlet-badge--action-create {
|
||||||
@extend %ibo-font-size-150;
|
@extend %ibo-baseline-centered-content;
|
||||||
|
@extend %ibo-font-size-150;
|
||||||
}
|
}
|
||||||
.ibo-dashlet-badge--action-create-icon{
|
|
||||||
margin-right: $ibo-dashlet-badge--action-icon--margin-right;
|
.ibo-dashlet-badge--action-create-icon {
|
||||||
|
margin-right: $ibo-dashlet-badge--action-icon--margin-right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ibo-dashlet-badge--body--tooltip-title {
|
||||||
|
@extend %ibo-font-weight-600;
|
||||||
|
margin-bottom: $ibo-dashlet-badge--body--tooltip-title--margin-bottom;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -344,6 +344,7 @@ class AttachmentPlugIn implements iApplicationUIExtension, iApplicationObjectExt
|
|||||||
while ($oAttachment = $oSet->Fetch())
|
while ($oAttachment = $oSet->Fetch())
|
||||||
{
|
{
|
||||||
$oTempAttachment = clone $oAttachment;
|
$oTempAttachment = clone $oAttachment;
|
||||||
|
$oTempAttachment->Set('expire', time() + utils::GetConfig()->Get('draft_attachments_lifetime'));
|
||||||
$oTempAttachment->Set('item_id', null);
|
$oTempAttachment->Set('item_id', null);
|
||||||
$oTempAttachment->Set('temp_id', $sTempId);
|
$oTempAttachment->Set('temp_id', $sTempId);
|
||||||
$oTempAttachment->DBInsert();
|
$oTempAttachment->DBInsert();
|
||||||
|
|||||||
@@ -235,13 +235,16 @@ class DBRestore extends DBBackup
|
|||||||
if (in_array($oFileInfo->getFilename(), $aStandardFiles)) {
|
if (in_array($oFileInfo->getFilename(), $aStandardFiles)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (strncmp($oFileInfo->getPathname(), $sDataDir.'/production-modules', strlen($sDataDir.'/production-modules')) == 0) {
|
// Normalize filenames to cope with Windows backslashes
|
||||||
|
$sPath = str_replace('\\', '/', $oFileInfo->getPathname());
|
||||||
|
$sRefPath = str_replace('\\', '/', $sDataDir.'/production-modules');
|
||||||
|
if (strncmp($sPath, $sRefPath, strlen($sRefPath)) == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$aExtraFiles[$oFileInfo->getPathname()] = APPROOT.substr($oFileInfo->getPathname(), strlen($sDataDir));
|
$aExtraFiles[$oFileInfo->getPathname()] = APPROOT.substr($oFileInfo->getPathname(), strlen($sDataDir));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $aExtraFiles;
|
return $aExtraFiles;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:FAQ/Attribute:category_id+' => '',
|
'Class:FAQ/Attribute:category_id+' => '',
|
||||||
'Class:FAQ/Attribute:category_name' => '类别名称',
|
'Class:FAQ/Attribute:category_name' => '类别名称',
|
||||||
'Class:FAQ/Attribute:category_name+' => '',
|
'Class:FAQ/Attribute:category_name+' => '',
|
||||||
'Class:FAQ/Attribute:error_code' => '错误代码',
|
'Class:FAQ/Attribute:error_code' => '错误编码',
|
||||||
'Class:FAQ/Attribute:error_code+' => '',
|
'Class:FAQ/Attribute:error_code+' => '',
|
||||||
'Class:FAQ/Attribute:key_words' => '关键字',
|
'Class:FAQ/Attribute:key_words' => '关键字',
|
||||||
'Class:FAQ/Attribute:key_words+' => '',
|
'Class:FAQ/Attribute:key_words+' => '',
|
||||||
|
|||||||
@@ -66,11 +66,11 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:Incident/Attribute:status+' => '',
|
'Class:Incident/Attribute:status+' => '',
|
||||||
'Class:Incident/Attribute:status/Value:new' => '新建',
|
'Class:Incident/Attribute:status/Value:new' => '新建',
|
||||||
'Class:Incident/Attribute:status/Value:new+' => '',
|
'Class:Incident/Attribute:status/Value:new+' => '',
|
||||||
'Class:Incident/Attribute:status/Value:escalated_tto' => '已升级响应时间',
|
'Class:Incident/Attribute:status/Value:escalated_tto' => '已升级TTO',
|
||||||
'Class:Incident/Attribute:status/Value:escalated_tto+' => '',
|
'Class:Incident/Attribute:status/Value:escalated_tto+' => '',
|
||||||
'Class:Incident/Attribute:status/Value:assigned' => '已分配',
|
'Class:Incident/Attribute:status/Value:assigned' => '已分配',
|
||||||
'Class:Incident/Attribute:status/Value:assigned+' => '',
|
'Class:Incident/Attribute:status/Value:assigned+' => '',
|
||||||
'Class:Incident/Attribute:status/Value:escalated_ttr' => '已升级解决时间',
|
'Class:Incident/Attribute:status/Value:escalated_ttr' => '已升级TTR',
|
||||||
'Class:Incident/Attribute:status/Value:escalated_ttr+' => '',
|
'Class:Incident/Attribute:status/Value:escalated_ttr+' => '',
|
||||||
'Class:Incident/Attribute:status/Value:waiting_for_approval' => '等待批准',
|
'Class:Incident/Attribute:status/Value:waiting_for_approval' => '等待批准',
|
||||||
'Class:Incident/Attribute:status/Value:waiting_for_approval+' => '',
|
'Class:Incident/Attribute:status/Value:waiting_for_approval+' => '',
|
||||||
@@ -90,8 +90,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:Incident/Attribute:impact/Value:3+' => '',
|
'Class:Incident/Attribute:impact/Value:3+' => '',
|
||||||
'Class:Incident/Attribute:priority' => '优先级',
|
'Class:Incident/Attribute:priority' => '优先级',
|
||||||
'Class:Incident/Attribute:priority+' => '',
|
'Class:Incident/Attribute:priority+' => '',
|
||||||
'Class:Incident/Attribute:priority/Value:1' => '非常高',
|
'Class:Incident/Attribute:priority/Value:1' => '紧急',
|
||||||
'Class:Incident/Attribute:priority/Value:1+' => '非常高',
|
'Class:Incident/Attribute:priority/Value:1+' => '紧急',
|
||||||
'Class:Incident/Attribute:priority/Value:2' => '高',
|
'Class:Incident/Attribute:priority/Value:2' => '高',
|
||||||
'Class:Incident/Attribute:priority/Value:2+' => '高',
|
'Class:Incident/Attribute:priority/Value:2+' => '高',
|
||||||
'Class:Incident/Attribute:priority/Value:3' => '中',
|
'Class:Incident/Attribute:priority/Value:3' => '中',
|
||||||
@@ -100,8 +100,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:Incident/Attribute:priority/Value:4+' => '低',
|
'Class:Incident/Attribute:priority/Value:4+' => '低',
|
||||||
'Class:Incident/Attribute:urgency' => '紧急度',
|
'Class:Incident/Attribute:urgency' => '紧急度',
|
||||||
'Class:Incident/Attribute:urgency+' => '',
|
'Class:Incident/Attribute:urgency+' => '',
|
||||||
'Class:Incident/Attribute:urgency/Value:1' => '非常高',
|
'Class:Incident/Attribute:urgency/Value:1' => '紧急',
|
||||||
'Class:Incident/Attribute:urgency/Value:1+' => '非常高',
|
'Class:Incident/Attribute:urgency/Value:1+' => '紧急',
|
||||||
'Class:Incident/Attribute:urgency/Value:2' => '高',
|
'Class:Incident/Attribute:urgency/Value:2' => '高',
|
||||||
'Class:Incident/Attribute:urgency/Value:2+' => '高',
|
'Class:Incident/Attribute:urgency/Value:2+' => '高',
|
||||||
'Class:Incident/Attribute:urgency/Value:3' => '中',
|
'Class:Incident/Attribute:urgency/Value:3' => '中',
|
||||||
@@ -136,7 +136,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:Incident/Attribute:escalation_flag/Value:no+' => '否',
|
'Class:Incident/Attribute:escalation_flag/Value:no+' => '否',
|
||||||
'Class:Incident/Attribute:escalation_flag/Value:yes' => '是',
|
'Class:Incident/Attribute:escalation_flag/Value:yes' => '是',
|
||||||
'Class:Incident/Attribute:escalation_flag/Value:yes+' => '是',
|
'Class:Incident/Attribute:escalation_flag/Value:yes+' => '是',
|
||||||
'Class:Incident/Attribute:escalation_reason' => '热门',
|
'Class:Incident/Attribute:escalation_reason' => '升级原因',
|
||||||
'Class:Incident/Attribute:escalation_reason+' => '',
|
'Class:Incident/Attribute:escalation_reason+' => '',
|
||||||
'Class:Incident/Attribute:assignment_date' => '分配日期',
|
'Class:Incident/Attribute:assignment_date' => '分配日期',
|
||||||
'Class:Incident/Attribute:assignment_date+' => '',
|
'Class:Incident/Attribute:assignment_date+' => '',
|
||||||
@@ -146,21 +146,21 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:Incident/Attribute:last_pending_date+' => '',
|
'Class:Incident/Attribute:last_pending_date+' => '',
|
||||||
'Class:Incident/Attribute:cumulatedpending' => '累计待定',
|
'Class:Incident/Attribute:cumulatedpending' => '累计待定',
|
||||||
'Class:Incident/Attribute:cumulatedpending+' => '',
|
'Class:Incident/Attribute:cumulatedpending+' => '',
|
||||||
'Class:Incident/Attribute:tto' => '响应时间',
|
'Class:Incident/Attribute:tto' => 'TTO',
|
||||||
'Class:Incident/Attribute:tto+' => '',
|
'Class:Incident/Attribute:tto+' => '响应时间',
|
||||||
'Class:Incident/Attribute:ttr' => '解决时间',
|
'Class:Incident/Attribute:ttr' => 'TTR',
|
||||||
'Class:Incident/Attribute:ttr+' => '',
|
'Class:Incident/Attribute:ttr+' => '解决时限',
|
||||||
'Class:Incident/Attribute:tto_escalation_deadline' => '响应时间截止',
|
'Class:Incident/Attribute:tto_escalation_deadline' => 'TTO截止日期',
|
||||||
'Class:Incident/Attribute:tto_escalation_deadline+' => '',
|
'Class:Incident/Attribute:tto_escalation_deadline+' => '',
|
||||||
'Class:Incident/Attribute:sla_tto_passed' => '超过SLA响应时间',
|
'Class:Incident/Attribute:sla_tto_passed' => 'SLA TTO 合格',
|
||||||
'Class:Incident/Attribute:sla_tto_passed+' => '',
|
'Class:Incident/Attribute:sla_tto_passed+' => '',
|
||||||
'Class:Incident/Attribute:sla_tto_over' => 'SLA响应时间结束',
|
'Class:Incident/Attribute:sla_tto_over' => 'SLA TTO 超时',
|
||||||
'Class:Incident/Attribute:sla_tto_over+' => '',
|
'Class:Incident/Attribute:sla_tto_over+' => '',
|
||||||
'Class:Incident/Attribute:ttr_escalation_deadline' => '解决时间截止',
|
'Class:Incident/Attribute:ttr_escalation_deadline' => 'TTR截止日期',
|
||||||
'Class:Incident/Attribute:ttr_escalation_deadline+' => '',
|
'Class:Incident/Attribute:ttr_escalation_deadline+' => '',
|
||||||
'Class:Incident/Attribute:sla_ttr_passed' => '超过SLA解决时间',
|
'Class:Incident/Attribute:sla_ttr_passed' => 'SLA TTR 合格',
|
||||||
'Class:Incident/Attribute:sla_ttr_passed+' => '',
|
'Class:Incident/Attribute:sla_ttr_passed+' => '',
|
||||||
'Class:Incident/Attribute:sla_ttr_over' => 'SLA解决时间结束',
|
'Class:Incident/Attribute:sla_ttr_over' => 'SLA TTR 超时',
|
||||||
'Class:Incident/Attribute:sla_ttr_over+' => '',
|
'Class:Incident/Attribute:sla_ttr_over+' => '',
|
||||||
'Class:Incident/Attribute:time_spent' => '耗时',
|
'Class:Incident/Attribute:time_spent' => '耗时',
|
||||||
'Class:Incident/Attribute:time_spent+' => '',
|
'Class:Incident/Attribute:time_spent+' => '',
|
||||||
@@ -182,7 +182,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:Incident/Attribute:resolution_code/Value:training+' => '培训',
|
'Class:Incident/Attribute:resolution_code/Value:training+' => '培训',
|
||||||
'Class:Incident/Attribute:solution' => '解决方案',
|
'Class:Incident/Attribute:solution' => '解决方案',
|
||||||
'Class:Incident/Attribute:solution+' => '',
|
'Class:Incident/Attribute:solution+' => '',
|
||||||
'Class:Incident/Attribute:pending_reason' => '待定的原因',
|
'Class:Incident/Attribute:pending_reason' => '待定原因',
|
||||||
'Class:Incident/Attribute:pending_reason+' => '',
|
'Class:Incident/Attribute:pending_reason+' => '',
|
||||||
'Class:Incident/Attribute:parent_incident_id' => '父级事件',
|
'Class:Incident/Attribute:parent_incident_id' => '父级事件',
|
||||||
'Class:Incident/Attribute:parent_incident_id+' => '',
|
'Class:Incident/Attribute:parent_incident_id+' => '',
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:KnownError/Attribute:workaround+' => '',
|
'Class:KnownError/Attribute:workaround+' => '',
|
||||||
'Class:KnownError/Attribute:solution' => '解决方案',
|
'Class:KnownError/Attribute:solution' => '解决方案',
|
||||||
'Class:KnownError/Attribute:solution+' => '',
|
'Class:KnownError/Attribute:solution+' => '',
|
||||||
'Class:KnownError/Attribute:error_code' => '错误代码',
|
'Class:KnownError/Attribute:error_code' => '错误编码',
|
||||||
'Class:KnownError/Attribute:error_code+' => '',
|
'Class:KnownError/Attribute:error_code+' => '',
|
||||||
'Class:KnownError/Attribute:domain' => '类型',
|
'Class:KnownError/Attribute:domain' => '类型',
|
||||||
'Class:KnownError/Attribute:domain+' => '',
|
'Class:KnownError/Attribute:domain+' => '',
|
||||||
|
|||||||
@@ -230,6 +230,7 @@ HTML
|
|||||||
$this->Set('refresh_token', $oAccessToken->getRefreshToken());
|
$this->Set('refresh_token', $oAccessToken->getRefreshToken());
|
||||||
}
|
}
|
||||||
$this->Set('status', 'active');
|
$this->Set('status', 'active');
|
||||||
|
$this->AllowWrite();
|
||||||
$this->DBUpdate();
|
$this->DBUpdate();
|
||||||
}
|
}
|
||||||
]]></code>
|
]]></code>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use Combodo\iTop\Application\TwigBase\Controller\Controller;
|
|||||||
use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderFactory;
|
use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderFactory;
|
||||||
use Dict;
|
use Dict;
|
||||||
use IssueLog;
|
use IssueLog;
|
||||||
|
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
|
||||||
use MetaModel;
|
use MetaModel;
|
||||||
use utils;
|
use utils;
|
||||||
use WebPage;
|
use WebPage;
|
||||||
@@ -65,13 +66,15 @@ class AjaxOauthClientController extends Controller
|
|||||||
}
|
}
|
||||||
if (isset($aQuery['code'])) {
|
if (isset($aQuery['code'])) {
|
||||||
$sCode = $aQuery['code'];
|
$sCode = $aQuery['code'];
|
||||||
$oAccessToken = OAuthClientProviderFactory::GetAccessTokenFromCode($oOAuthClient, $sCode);
|
try {
|
||||||
|
$oAccessToken = OAuthClientProviderFactory::GetAccessTokenFromCode($oOAuthClient, $sCode);
|
||||||
$oOAuthClient->SetAccessToken($oAccessToken);
|
$oOAuthClient->SetAccessToken($oAccessToken);
|
||||||
|
$aResult['status'] = 'success';
|
||||||
|
}
|
||||||
|
catch (IdentityProviderException $e) {
|
||||||
$aResult['status'] = 'success';
|
$aResult['status'] = 'error';
|
||||||
|
$aResult['error_description'] = $e->getMessage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$aResult['status'] = 'error';
|
$aResult['status'] = 'error';
|
||||||
|
|||||||
@@ -103,8 +103,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:Problem/Attribute:impact/Value:3+' => '',
|
'Class:Problem/Attribute:impact/Value:3+' => '',
|
||||||
'Class:Problem/Attribute:urgency' => '紧急度',
|
'Class:Problem/Attribute:urgency' => '紧急度',
|
||||||
'Class:Problem/Attribute:urgency+' => '',
|
'Class:Problem/Attribute:urgency+' => '',
|
||||||
'Class:Problem/Attribute:urgency/Value:1' => '非常高',
|
'Class:Problem/Attribute:urgency/Value:1' => '紧急',
|
||||||
'Class:Problem/Attribute:urgency/Value:1+' => '非常高',
|
'Class:Problem/Attribute:urgency/Value:1+' => '紧急',
|
||||||
'Class:Problem/Attribute:urgency/Value:2' => '高',
|
'Class:Problem/Attribute:urgency/Value:2' => '高',
|
||||||
'Class:Problem/Attribute:urgency/Value:2+' => '高',
|
'Class:Problem/Attribute:urgency/Value:2+' => '高',
|
||||||
'Class:Problem/Attribute:urgency/Value:3' => '中',
|
'Class:Problem/Attribute:urgency/Value:3' => '中',
|
||||||
@@ -113,8 +113,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:Problem/Attribute:urgency/Value:4+' => '低',
|
'Class:Problem/Attribute:urgency/Value:4+' => '低',
|
||||||
'Class:Problem/Attribute:priority' => '优先级',
|
'Class:Problem/Attribute:priority' => '优先级',
|
||||||
'Class:Problem/Attribute:priority+' => '',
|
'Class:Problem/Attribute:priority+' => '',
|
||||||
'Class:Problem/Attribute:priority/Value:1' => '非常高',
|
'Class:Problem/Attribute:priority/Value:1' => '紧急',
|
||||||
'Class:Problem/Attribute:priority/Value:1+' => '非常高',
|
'Class:Problem/Attribute:priority/Value:1+' => '紧急',
|
||||||
'Class:Problem/Attribute:priority/Value:2' => '高',
|
'Class:Problem/Attribute:priority/Value:2' => '高',
|
||||||
'Class:Problem/Attribute:priority/Value:2+' => '高',
|
'Class:Problem/Attribute:priority/Value:2+' => '高',
|
||||||
'Class:Problem/Attribute:priority/Value:3' => '中',
|
'Class:Problem/Attribute:priority/Value:3' => '中',
|
||||||
|
|||||||
@@ -125,6 +125,7 @@
|
|||||||
<group id="Audit" _delta="define">
|
<group id="Audit" _delta="define">
|
||||||
<classes>
|
<classes>
|
||||||
<!-- This class list is also present in AdminTools group -->
|
<!-- This class list is also present in AdminTools group -->
|
||||||
|
<class id="AuditDomain"/>
|
||||||
<class id="AuditCategory"/>
|
<class id="AuditCategory"/>
|
||||||
<class id="AuditRule"/>
|
<class id="AuditRule"/>
|
||||||
<class id="ResourceRunQueriesMenu"/>
|
<class id="ResourceRunQueriesMenu"/>
|
||||||
@@ -166,6 +167,7 @@
|
|||||||
<class id="URP_UserProfile"/>
|
<class id="URP_UserProfile"/>
|
||||||
<class id="URP_Profiles"/>
|
<class id="URP_Profiles"/>
|
||||||
<!-- Audit group -->
|
<!-- Audit group -->
|
||||||
|
<class id="AuditDomain"/>
|
||||||
<class id="AuditCategory"/>
|
<class id="AuditCategory"/>
|
||||||
<class id="AuditRule"/>
|
<class id="AuditRule"/>
|
||||||
<!-- Query group -->
|
<!-- Query group -->
|
||||||
|
|||||||
@@ -58,11 +58,11 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:UserRequest/Attribute:status+' => '',
|
'Class:UserRequest/Attribute:status+' => '',
|
||||||
'Class:UserRequest/Attribute:status/Value:new' => '新建',
|
'Class:UserRequest/Attribute:status/Value:new' => '新建',
|
||||||
'Class:UserRequest/Attribute:status/Value:new+' => '',
|
'Class:UserRequest/Attribute:status/Value:new+' => '',
|
||||||
'Class:UserRequest/Attribute:status/Value:escalated_tto' => '已升级响应时间',
|
'Class:UserRequest/Attribute:status/Value:escalated_tto' => '已升级TTO',
|
||||||
'Class:UserRequest/Attribute:status/Value:escalated_tto+' => '',
|
'Class:UserRequest/Attribute:status/Value:escalated_tto+' => '',
|
||||||
'Class:UserRequest/Attribute:status/Value:assigned' => '已分配',
|
'Class:UserRequest/Attribute:status/Value:assigned' => '已分配',
|
||||||
'Class:UserRequest/Attribute:status/Value:assigned+' => '',
|
'Class:UserRequest/Attribute:status/Value:assigned+' => '',
|
||||||
'Class:UserRequest/Attribute:status/Value:escalated_ttr' => '已升级解决时间',
|
'Class:UserRequest/Attribute:status/Value:escalated_ttr' => '已升级TTR',
|
||||||
'Class:UserRequest/Attribute:status/Value:escalated_ttr+' => '',
|
'Class:UserRequest/Attribute:status/Value:escalated_ttr+' => '',
|
||||||
'Class:UserRequest/Attribute:status/Value:waiting_for_approval' => '等待批准',
|
'Class:UserRequest/Attribute:status/Value:waiting_for_approval' => '等待批准',
|
||||||
'Class:UserRequest/Attribute:status/Value:waiting_for_approval+' => '',
|
'Class:UserRequest/Attribute:status/Value:waiting_for_approval+' => '',
|
||||||
@@ -90,8 +90,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:UserRequest/Attribute:impact/Value:3+' => '',
|
'Class:UserRequest/Attribute:impact/Value:3+' => '',
|
||||||
'Class:UserRequest/Attribute:priority' => '优先级',
|
'Class:UserRequest/Attribute:priority' => '优先级',
|
||||||
'Class:UserRequest/Attribute:priority+' => '',
|
'Class:UserRequest/Attribute:priority+' => '',
|
||||||
'Class:UserRequest/Attribute:priority/Value:1' => '非常高',
|
'Class:UserRequest/Attribute:priority/Value:1' => '紧急',
|
||||||
'Class:UserRequest/Attribute:priority/Value:1+' => '非常高',
|
'Class:UserRequest/Attribute:priority/Value:1+' => '紧急',
|
||||||
'Class:UserRequest/Attribute:priority/Value:2' => '高',
|
'Class:UserRequest/Attribute:priority/Value:2' => '高',
|
||||||
'Class:UserRequest/Attribute:priority/Value:2+' => '高',
|
'Class:UserRequest/Attribute:priority/Value:2+' => '高',
|
||||||
'Class:UserRequest/Attribute:priority/Value:3' => '中',
|
'Class:UserRequest/Attribute:priority/Value:3' => '中',
|
||||||
@@ -100,8 +100,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:UserRequest/Attribute:priority/Value:4+' => '低',
|
'Class:UserRequest/Attribute:priority/Value:4+' => '低',
|
||||||
'Class:UserRequest/Attribute:urgency' => '紧急度',
|
'Class:UserRequest/Attribute:urgency' => '紧急度',
|
||||||
'Class:UserRequest/Attribute:urgency+' => '',
|
'Class:UserRequest/Attribute:urgency+' => '',
|
||||||
'Class:UserRequest/Attribute:urgency/Value:1' => '非常高',
|
'Class:UserRequest/Attribute:urgency/Value:1' => '紧急',
|
||||||
'Class:UserRequest/Attribute:urgency/Value:1+' => '非常高',
|
'Class:UserRequest/Attribute:urgency/Value:1+' => '紧急',
|
||||||
'Class:UserRequest/Attribute:urgency/Value:2' => '高',
|
'Class:UserRequest/Attribute:urgency/Value:2' => '高',
|
||||||
'Class:UserRequest/Attribute:urgency/Value:2+' => '高',
|
'Class:UserRequest/Attribute:urgency/Value:2+' => '高',
|
||||||
'Class:UserRequest/Attribute:urgency/Value:3' => '中',
|
'Class:UserRequest/Attribute:urgency/Value:3' => '中',
|
||||||
@@ -134,7 +134,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:UserRequest/Attribute:servicesubcategory_id+' => '',
|
'Class:UserRequest/Attribute:servicesubcategory_id+' => '',
|
||||||
'Class:UserRequest/Attribute:servicesubcategory_name' => '子服务名称',
|
'Class:UserRequest/Attribute:servicesubcategory_name' => '子服务名称',
|
||||||
'Class:UserRequest/Attribute:servicesubcategory_name+' => '',
|
'Class:UserRequest/Attribute:servicesubcategory_name+' => '',
|
||||||
'Class:UserRequest/Attribute:escalation_flag' => '是否升级',
|
'Class:UserRequest/Attribute:escalation_flag' => '升级标签',
|
||||||
'Class:UserRequest/Attribute:escalation_flag+' => '',
|
'Class:UserRequest/Attribute:escalation_flag+' => '',
|
||||||
'Class:UserRequest/Attribute:escalation_flag/Value:no' => '否',
|
'Class:UserRequest/Attribute:escalation_flag/Value:no' => '否',
|
||||||
'Class:UserRequest/Attribute:escalation_flag/Value:no+' => '否',
|
'Class:UserRequest/Attribute:escalation_flag/Value:no+' => '否',
|
||||||
@@ -150,30 +150,30 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:UserRequest/Attribute:last_pending_date+' => '',
|
'Class:UserRequest/Attribute:last_pending_date+' => '',
|
||||||
'Class:UserRequest/Attribute:cumulatedpending' => '累计待定',
|
'Class:UserRequest/Attribute:cumulatedpending' => '累计待定',
|
||||||
'Class:UserRequest/Attribute:cumulatedpending+' => '',
|
'Class:UserRequest/Attribute:cumulatedpending+' => '',
|
||||||
'Class:UserRequest/Attribute:tto' => '响应时间',
|
'Class:UserRequest/Attribute:tto' => 'TTO',
|
||||||
'Class:UserRequest/Attribute:tto+' => '',
|
'Class:UserRequest/Attribute:tto+' => '',
|
||||||
'Class:UserRequest/Attribute:ttr' => '解决时间',
|
'Class:UserRequest/Attribute:ttr' => 'TTR',
|
||||||
'Class:UserRequest/Attribute:ttr+' => '',
|
'Class:UserRequest/Attribute:ttr+' => '',
|
||||||
'Class:UserRequest/Attribute:tto_escalation_deadline' => '响应时间截止',
|
'Class:UserRequest/Attribute:tto_escalation_deadline' => 'TTO截止日期',
|
||||||
'Class:UserRequest/Attribute:tto_escalation_deadline+' => '',
|
'Class:UserRequest/Attribute:tto_escalation_deadline+' => '',
|
||||||
'Class:UserRequest/Attribute:sla_tto_passed' => '超过SLA响应时间',
|
'Class:UserRequest/Attribute:sla_tto_passed' => 'SLA TTO 合格',
|
||||||
'Class:UserRequest/Attribute:sla_tto_passed+' => '',
|
'Class:UserRequest/Attribute:sla_tto_passed+' => '',
|
||||||
'Class:UserRequest/Attribute:sla_tto_over' => 'SLA响应时间超过',
|
'Class:UserRequest/Attribute:sla_tto_over' => 'SLA TTO 超时',
|
||||||
'Class:UserRequest/Attribute:sla_tto_over+' => '',
|
'Class:UserRequest/Attribute:sla_tto_over+' => '',
|
||||||
'Class:UserRequest/Attribute:ttr_escalation_deadline' => '解决时间截止',
|
'Class:UserRequest/Attribute:ttr_escalation_deadline' => 'TTR截止日期',
|
||||||
'Class:UserRequest/Attribute:ttr_escalation_deadline+' => '',
|
'Class:UserRequest/Attribute:ttr_escalation_deadline+' => '',
|
||||||
'Class:UserRequest/Attribute:sla_ttr_passed' => '超过SLA解决时间',
|
'Class:UserRequest/Attribute:sla_ttr_passed' => 'SLA TTR 合格',
|
||||||
'Class:UserRequest/Attribute:sla_ttr_passed+' => '',
|
'Class:UserRequest/Attribute:sla_ttr_passed+' => '',
|
||||||
'Class:UserRequest/Attribute:sla_ttr_over' => 'SLA解决时间超过',
|
'Class:UserRequest/Attribute:sla_ttr_over' => 'SLA TTR 超时',
|
||||||
'Class:UserRequest/Attribute:sla_ttr_over+' => '',
|
'Class:UserRequest/Attribute:sla_ttr_over+' => '',
|
||||||
'Class:UserRequest/Attribute:time_spent' => '耗时',
|
'Class:UserRequest/Attribute:time_spent' => '耗时',
|
||||||
'Class:UserRequest/Attribute:time_spent+' => '',
|
'Class:UserRequest/Attribute:time_spent+' => '',
|
||||||
'Class:UserRequest/Attribute:resolution_code' => '解决代码',
|
'Class:UserRequest/Attribute:resolution_code' => '解决编码',
|
||||||
'Class:UserRequest/Attribute:resolution_code+' => '',
|
'Class:UserRequest/Attribute:resolution_code+' => '',
|
||||||
'Class:UserRequest/Attribute:resolution_code/Value:assistance' => '帮助',
|
'Class:UserRequest/Attribute:resolution_code/Value:assistance' => '帮助',
|
||||||
'Class:UserRequest/Attribute:resolution_code/Value:assistance+' => '帮助',
|
'Class:UserRequest/Attribute:resolution_code/Value:assistance+' => '帮助',
|
||||||
'Class:UserRequest/Attribute:resolution_code/Value:bug fixed' => '缺陷修复',
|
'Class:UserRequest/Attribute:resolution_code/Value:bug fixed' => 'bug修复',
|
||||||
'Class:UserRequest/Attribute:resolution_code/Value:bug fixed+' => '缺陷修复',
|
'Class:UserRequest/Attribute:resolution_code/Value:bug fixed+' => 'bug修复',
|
||||||
'Class:UserRequest/Attribute:resolution_code/Value:hardware repair' => '硬件维修',
|
'Class:UserRequest/Attribute:resolution_code/Value:hardware repair' => '硬件维修',
|
||||||
'Class:UserRequest/Attribute:resolution_code/Value:hardware repair+' => '硬件维修',
|
'Class:UserRequest/Attribute:resolution_code/Value:hardware repair+' => '硬件维修',
|
||||||
'Class:UserRequest/Attribute:resolution_code/Value:other' => '其它',
|
'Class:UserRequest/Attribute:resolution_code/Value:other' => '其它',
|
||||||
|
|||||||
@@ -62,11 +62,11 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:UserRequest/Attribute:status+' => '',
|
'Class:UserRequest/Attribute:status+' => '',
|
||||||
'Class:UserRequest/Attribute:status/Value:new' => '新建',
|
'Class:UserRequest/Attribute:status/Value:new' => '新建',
|
||||||
'Class:UserRequest/Attribute:status/Value:new+' => '',
|
'Class:UserRequest/Attribute:status/Value:new+' => '',
|
||||||
'Class:UserRequest/Attribute:status/Value:escalated_tto' => '已升级响应时间',
|
'Class:UserRequest/Attribute:status/Value:escalated_tto' => '已升级TTO',
|
||||||
'Class:UserRequest/Attribute:status/Value:escalated_tto+' => '',
|
'Class:UserRequest/Attribute:status/Value:escalated_tto+' => '',
|
||||||
'Class:UserRequest/Attribute:status/Value:assigned' => '已分配',
|
'Class:UserRequest/Attribute:status/Value:assigned' => '已分配',
|
||||||
'Class:UserRequest/Attribute:status/Value:assigned+' => '',
|
'Class:UserRequest/Attribute:status/Value:assigned+' => '',
|
||||||
'Class:UserRequest/Attribute:status/Value:escalated_ttr' => '已升级解决时间',
|
'Class:UserRequest/Attribute:status/Value:escalated_ttr' => '已升级TTR',
|
||||||
'Class:UserRequest/Attribute:status/Value:escalated_ttr+' => '',
|
'Class:UserRequest/Attribute:status/Value:escalated_ttr+' => '',
|
||||||
'Class:UserRequest/Attribute:status/Value:waiting_for_approval' => '等待批准',
|
'Class:UserRequest/Attribute:status/Value:waiting_for_approval' => '等待批准',
|
||||||
'Class:UserRequest/Attribute:status/Value:waiting_for_approval+' => '',
|
'Class:UserRequest/Attribute:status/Value:waiting_for_approval+' => '',
|
||||||
@@ -156,21 +156,21 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:UserRequest/Attribute:last_pending_date+' => '',
|
'Class:UserRequest/Attribute:last_pending_date+' => '',
|
||||||
'Class:UserRequest/Attribute:cumulatedpending' => '累计待定',
|
'Class:UserRequest/Attribute:cumulatedpending' => '累计待定',
|
||||||
'Class:UserRequest/Attribute:cumulatedpending+' => '',
|
'Class:UserRequest/Attribute:cumulatedpending+' => '',
|
||||||
'Class:UserRequest/Attribute:tto' => '响应时间',
|
'Class:UserRequest/Attribute:tto' => 'TTO',
|
||||||
'Class:UserRequest/Attribute:tto+' => '',
|
'Class:UserRequest/Attribute:tto+' => '响应时间',
|
||||||
'Class:UserRequest/Attribute:ttr' => '解决时间',
|
'Class:UserRequest/Attribute:ttr' => 'TTR',
|
||||||
'Class:UserRequest/Attribute:ttr+' => '',
|
'Class:UserRequest/Attribute:ttr+' => '解决时限',
|
||||||
'Class:UserRequest/Attribute:tto_escalation_deadline' => '响应时间期限',
|
'Class:UserRequest/Attribute:tto_escalation_deadline' => 'TTO截止日期',
|
||||||
'Class:UserRequest/Attribute:tto_escalation_deadline+' => '',
|
'Class:UserRequest/Attribute:tto_escalation_deadline+' => '',
|
||||||
'Class:UserRequest/Attribute:sla_tto_passed' => '超过SLA响应时间',
|
'Class:UserRequest/Attribute:sla_tto_passed' => 'SLA TTO 合格',
|
||||||
'Class:UserRequest/Attribute:sla_tto_passed+' => '',
|
'Class:UserRequest/Attribute:sla_tto_passed+' => '',
|
||||||
'Class:UserRequest/Attribute:sla_tto_over' => 'SLA响应时间结束',
|
'Class:UserRequest/Attribute:sla_tto_over' => 'SLA TTO 超时',
|
||||||
'Class:UserRequest/Attribute:sla_tto_over+' => '',
|
'Class:UserRequest/Attribute:sla_tto_over+' => '',
|
||||||
'Class:UserRequest/Attribute:ttr_escalation_deadline' => '解决时间期限',
|
'Class:UserRequest/Attribute:ttr_escalation_deadline' => 'TTR截止日期',
|
||||||
'Class:UserRequest/Attribute:ttr_escalation_deadline+' => '',
|
'Class:UserRequest/Attribute:ttr_escalation_deadline+' => '',
|
||||||
'Class:UserRequest/Attribute:sla_ttr_passed' => '超过SLA解决时间',
|
'Class:UserRequest/Attribute:sla_ttr_passed' => 'SLA TTR 合格',
|
||||||
'Class:UserRequest/Attribute:sla_ttr_passed+' => '',
|
'Class:UserRequest/Attribute:sla_ttr_passed+' => '',
|
||||||
'Class:UserRequest/Attribute:sla_ttr_over' => 'SLA解决时间结束',
|
'Class:UserRequest/Attribute:sla_ttr_over' => 'SLA TTR 超时',
|
||||||
'Class:UserRequest/Attribute:sla_ttr_over+' => '',
|
'Class:UserRequest/Attribute:sla_ttr_over+' => '',
|
||||||
'Class:UserRequest/Attribute:time_spent' => '耗时',
|
'Class:UserRequest/Attribute:time_spent' => '耗时',
|
||||||
'Class:UserRequest/Attribute:time_spent+' => '',
|
'Class:UserRequest/Attribute:time_spent+' => '',
|
||||||
@@ -192,7 +192,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:UserRequest/Attribute:resolution_code/Value:training+' => '培训',
|
'Class:UserRequest/Attribute:resolution_code/Value:training+' => '培训',
|
||||||
'Class:UserRequest/Attribute:solution' => '解决方案',
|
'Class:UserRequest/Attribute:solution' => '解决方案',
|
||||||
'Class:UserRequest/Attribute:solution+' => '',
|
'Class:UserRequest/Attribute:solution+' => '',
|
||||||
'Class:UserRequest/Attribute:pending_reason' => '待定的原因',
|
'Class:UserRequest/Attribute:pending_reason' => '待定原因',
|
||||||
'Class:UserRequest/Attribute:pending_reason+' => '',
|
'Class:UserRequest/Attribute:pending_reason+' => '',
|
||||||
'Class:UserRequest/Attribute:parent_request_id' => '父级需求',
|
'Class:UserRequest/Attribute:parent_request_id' => '父级需求',
|
||||||
'Class:UserRequest/Attribute:parent_request_id+' => '',
|
'Class:UserRequest/Attribute:parent_request_id+' => '',
|
||||||
|
|||||||
@@ -99,9 +99,9 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
|||||||
'Class:Contract/Attribute:organization_name' => 'Nom client',
|
'Class:Contract/Attribute:organization_name' => 'Nom client',
|
||||||
'Class:Contract/Attribute:organization_name+' => 'Nom commun',
|
'Class:Contract/Attribute:organization_name+' => 'Nom commun',
|
||||||
'Class:Contract/Attribute:contacts_list' => 'Contacts',
|
'Class:Contract/Attribute:contacts_list' => 'Contacts',
|
||||||
'Class:Contract/Attribute:contacts_list+' => 'Tous les contacts for ce contrat client',
|
'Class:Contract/Attribute:contacts_list+' => 'Tous les contacts pour ce contrat client',
|
||||||
'Class:Contract/Attribute:documents_list' => 'Documents',
|
'Class:Contract/Attribute:documents_list' => 'Documents',
|
||||||
'Class:Contract/Attribute:documents_list+' => 'Tous les documents for ce contrat client',
|
'Class:Contract/Attribute:documents_list+' => 'Tous les documents pour ce contrat client',
|
||||||
'Class:Contract/Attribute:description' => 'Description',
|
'Class:Contract/Attribute:description' => 'Description',
|
||||||
'Class:Contract/Attribute:description+' => '',
|
'Class:Contract/Attribute:description+' => '',
|
||||||
'Class:Contract/Attribute:start_date' => 'Date de début',
|
'Class:Contract/Attribute:start_date' => 'Date de début',
|
||||||
|
|||||||
@@ -389,8 +389,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:SLT/Attribute:name+' => '',
|
'Class:SLT/Attribute:name+' => '',
|
||||||
'Class:SLT/Attribute:priority' => '优先级',
|
'Class:SLT/Attribute:priority' => '优先级',
|
||||||
'Class:SLT/Attribute:priority+' => '',
|
'Class:SLT/Attribute:priority+' => '',
|
||||||
'Class:SLT/Attribute:priority/Value:1' => '非常高',
|
'Class:SLT/Attribute:priority/Value:1' => '紧急',
|
||||||
'Class:SLT/Attribute:priority/Value:1+' => '非常高',
|
'Class:SLT/Attribute:priority/Value:1+' => '紧急',
|
||||||
'Class:SLT/Attribute:priority/Value:2' => '高',
|
'Class:SLT/Attribute:priority/Value:2' => '高',
|
||||||
'Class:SLT/Attribute:priority/Value:2+' => '高',
|
'Class:SLT/Attribute:priority/Value:2+' => '高',
|
||||||
'Class:SLT/Attribute:priority/Value:3' => '中',
|
'Class:SLT/Attribute:priority/Value:3' => '中',
|
||||||
@@ -403,15 +403,15 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:SLT/Attribute:request_type/Value:incident+' => '事件',
|
'Class:SLT/Attribute:request_type/Value:incident+' => '事件',
|
||||||
'Class:SLT/Attribute:request_type/Value:service_request' => '服务需求',
|
'Class:SLT/Attribute:request_type/Value:service_request' => '服务需求',
|
||||||
'Class:SLT/Attribute:request_type/Value:service_request+' => '服务需求',
|
'Class:SLT/Attribute:request_type/Value:service_request+' => '服务需求',
|
||||||
'Class:SLT/Attribute:metric' => '指标',
|
'Class:SLT/Attribute:metric' => '衡量指标',
|
||||||
'Class:SLT/Attribute:metric+' => '',
|
'Class:SLT/Attribute:metric+' => '',
|
||||||
'Class:SLT/Attribute:metric/Value:tto' => '响应时间',
|
'Class:SLT/Attribute:metric/Value:tto' => 'TTO',
|
||||||
'Class:SLT/Attribute:metric/Value:tto+' => '响应时间',
|
'Class:SLT/Attribute:metric/Value:tto+' => '响应时间',
|
||||||
'Class:SLT/Attribute:metric/Value:ttr' => '解决时间',
|
'Class:SLT/Attribute:metric/Value:ttr' => 'TTR',
|
||||||
'Class:SLT/Attribute:metric/Value:ttr+' => '解决时间',
|
'Class:SLT/Attribute:metric/Value:ttr+' => '解决时限',
|
||||||
'Class:SLT/Attribute:value' => '值',
|
'Class:SLT/Attribute:value' => '值',
|
||||||
'Class:SLT/Attribute:value+' => '',
|
'Class:SLT/Attribute:value+' => '',
|
||||||
'Class:SLT/Attribute:unit' => '单位',
|
'Class:SLT/Attribute:unit' => '度量单位',
|
||||||
'Class:SLT/Attribute:unit+' => '',
|
'Class:SLT/Attribute:unit+' => '',
|
||||||
'Class:SLT/Attribute:unit/Value:hours' => '小时',
|
'Class:SLT/Attribute:unit/Value:hours' => '小时',
|
||||||
'Class:SLT/Attribute:unit/Value:hours+' => '小时',
|
'Class:SLT/Attribute:unit/Value:hours+' => '小时',
|
||||||
|
|||||||
@@ -157,6 +157,8 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
|
|||||||
'Class:ProviderContract/Attribute:contracttype_id+' => '',
|
'Class:ProviderContract/Attribute:contracttype_id+' => '',
|
||||||
'Class:ProviderContract/Attribute:contracttype_name' => 'Vertragstyp-Name',
|
'Class:ProviderContract/Attribute:contracttype_name' => 'Vertragstyp-Name',
|
||||||
'Class:ProviderContract/Attribute:contracttype_name+' => '',
|
'Class:ProviderContract/Attribute:contracttype_name+' => '',
|
||||||
|
'Class:ProviderContract/Attribute:services_list' => 'Services',
|
||||||
|
'Class:ProviderContract/Attribute:services_list+' => 'Alle für diesen Vertrag erworbenen Services',
|
||||||
));
|
));
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -169,6 +169,8 @@ Dict::Add('EN US', 'English', 'English', array(
|
|||||||
'Class:ProviderContract/Attribute:contracttype_id+' => '',
|
'Class:ProviderContract/Attribute:contracttype_id+' => '',
|
||||||
'Class:ProviderContract/Attribute:contracttype_name' => 'Contract type name',
|
'Class:ProviderContract/Attribute:contracttype_name' => 'Contract type name',
|
||||||
'Class:ProviderContract/Attribute:contracttype_name+' => '',
|
'Class:ProviderContract/Attribute:contracttype_name+' => '',
|
||||||
|
'Class:ProviderContract/Attribute:services_list' => 'Services',
|
||||||
|
'Class:ProviderContract/Attribute:services_list+' => 'All the services purchased with this contract',
|
||||||
));
|
));
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -157,6 +157,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
|||||||
'Class:ProviderContract/Attribute:contracttype_id+' => '',
|
'Class:ProviderContract/Attribute:contracttype_id+' => '',
|
||||||
'Class:ProviderContract/Attribute:contracttype_name' => 'Nom Type de contrat',
|
'Class:ProviderContract/Attribute:contracttype_name' => 'Nom Type de contrat',
|
||||||
'Class:ProviderContract/Attribute:contracttype_name+' => '',
|
'Class:ProviderContract/Attribute:contracttype_name+' => '',
|
||||||
|
'Class:ProviderContract/Attribute:services_list' => 'Services',
|
||||||
|
'Class:ProviderContract/Attribute:services_list+' => 'Tous les services achetés par ce contrat',
|
||||||
));
|
));
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -362,8 +362,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:SLT/Attribute:name+' => '',
|
'Class:SLT/Attribute:name+' => '',
|
||||||
'Class:SLT/Attribute:priority' => '优先级',
|
'Class:SLT/Attribute:priority' => '优先级',
|
||||||
'Class:SLT/Attribute:priority+' => '',
|
'Class:SLT/Attribute:priority+' => '',
|
||||||
'Class:SLT/Attribute:priority/Value:1' => '非常高',
|
'Class:SLT/Attribute:priority/Value:1' => '紧急',
|
||||||
'Class:SLT/Attribute:priority/Value:1+' => '非常高',
|
'Class:SLT/Attribute:priority/Value:1+' => '紧急',
|
||||||
'Class:SLT/Attribute:priority/Value:2' => '高',
|
'Class:SLT/Attribute:priority/Value:2' => '高',
|
||||||
'Class:SLT/Attribute:priority/Value:2+' => '高',
|
'Class:SLT/Attribute:priority/Value:2+' => '高',
|
||||||
'Class:SLT/Attribute:priority/Value:3' => '中',
|
'Class:SLT/Attribute:priority/Value:3' => '中',
|
||||||
@@ -376,15 +376,15 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:SLT/Attribute:request_type/Value:incident+' => '事件',
|
'Class:SLT/Attribute:request_type/Value:incident+' => '事件',
|
||||||
'Class:SLT/Attribute:request_type/Value:service_request' => '服务需求',
|
'Class:SLT/Attribute:request_type/Value:service_request' => '服务需求',
|
||||||
'Class:SLT/Attribute:request_type/Value:service_request+' => '服务需求',
|
'Class:SLT/Attribute:request_type/Value:service_request+' => '服务需求',
|
||||||
'Class:SLT/Attribute:metric' => '指标',
|
'Class:SLT/Attribute:metric' => '衡量指标',
|
||||||
'Class:SLT/Attribute:metric+' => '',
|
'Class:SLT/Attribute:metric+' => '',
|
||||||
'Class:SLT/Attribute:metric/Value:tto' => '响应时间',
|
'Class:SLT/Attribute:metric/Value:tto' => 'TTO',
|
||||||
'Class:SLT/Attribute:metric/Value:tto+' => '响应时间',
|
'Class:SLT/Attribute:metric/Value:tto+' => '响应时间',
|
||||||
'Class:SLT/Attribute:metric/Value:ttr' => '解决时间',
|
'Class:SLT/Attribute:metric/Value:ttr' => 'TTR',
|
||||||
'Class:SLT/Attribute:metric/Value:ttr+' => '解决时间',
|
'Class:SLT/Attribute:metric/Value:ttr+' => '解决时限',
|
||||||
'Class:SLT/Attribute:value' => '值',
|
'Class:SLT/Attribute:value' => '值',
|
||||||
'Class:SLT/Attribute:value+' => '',
|
'Class:SLT/Attribute:value+' => '',
|
||||||
'Class:SLT/Attribute:unit' => '单位',
|
'Class:SLT/Attribute:unit' => '度量单位',
|
||||||
'Class:SLT/Attribute:unit+' => '',
|
'Class:SLT/Attribute:unit+' => '',
|
||||||
'Class:SLT/Attribute:unit/Value:hours' => '小时',
|
'Class:SLT/Attribute:unit/Value:hours' => '小时',
|
||||||
'Class:SLT/Attribute:unit/Value:hours+' => '小时',
|
'Class:SLT/Attribute:unit/Value:hours+' => '小时',
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||||
'Class:DocumentNote' => '文档笔记',
|
'Class:DocumentNote' => '文档笔记',
|
||||||
'Class:DocumentNote+' => '',
|
'Class:DocumentNote+' => '',
|
||||||
'Class:DocumentNote/Attribute:text' => '文本',
|
'Class:DocumentNote/Attribute:text' => '正文',
|
||||||
'Class:DocumentNote/Attribute:text+' => '',
|
'Class:DocumentNote/Attribute:text+' => '',
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|||||||
@@ -240,9 +240,9 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:cmdbAbstractObject/Method:ApplyStimulus+' => 'Apply the specified stimulus to the current object',
|
'Class:cmdbAbstractObject/Method:ApplyStimulus+' => 'Apply the specified stimulus to the current object',
|
||||||
'Class:cmdbAbstractObject/Method:ApplyStimulus/Param:1' => 'Stimulus code',
|
'Class:cmdbAbstractObject/Method:ApplyStimulus/Param:1' => 'Stimulus code',
|
||||||
'Class:cmdbAbstractObject/Method:ApplyStimulus/Param:1+' => 'A valid stimulus code for the current class',
|
'Class:cmdbAbstractObject/Method:ApplyStimulus/Param:1+' => 'A valid stimulus code for the current class',
|
||||||
'Class:ResponseTicketTTO/Interface:iMetricComputer' => '响应时间',
|
'Class:ResponseTicketTTO/Interface:iMetricComputer' => 'TTO',
|
||||||
'Class:ResponseTicketTTO/Interface:iMetricComputer+' => 'SLT 的响应时间',
|
'Class:ResponseTicketTTO/Interface:iMetricComputer+' => 'SLT 的响应时间',
|
||||||
'Class:ResponseTicketTTR/Interface:iMetricComputer' => '解决时间',
|
'Class:ResponseTicketTTR/Interface:iMetricComputer' => 'TTR',
|
||||||
'Class:ResponseTicketTTR/Interface:iMetricComputer+' => 'SLT 的解决时间',
|
'Class:ResponseTicketTTR/Interface:iMetricComputer+' => 'SLT 的解决时限',
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|||||||
@@ -837,7 +837,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
|||||||
'UI:RunQuery:DevelopedOQLCount' => 'Developed OQL for count',
|
'UI:RunQuery:DevelopedOQLCount' => 'Developed OQL for count',
|
||||||
'UI:RunQuery:ResultSQLCount' => 'Resulting SQL for count',
|
'UI:RunQuery:ResultSQLCount' => 'Resulting SQL for count',
|
||||||
'UI:RunQuery:ResultSQL' => 'Resulting SQL',
|
'UI:RunQuery:ResultSQL' => 'Resulting SQL',
|
||||||
'UI:RunQuery:Error' => 'An error occured while running the query',
|
'UI:RunQuery:Error' => 'An error occured while running the query: %1$s',
|
||||||
'UI:Query:UrlForExcel' => 'URL to use for MS-Excel web queries',
|
'UI:Query:UrlForExcel' => 'URL to use for MS-Excel web queries',
|
||||||
'UI:Query:UrlV1' => 'The list of fields has been left unspecified. The page <em>export-V2.php</em> cannot be invoked without this information. Therefore, the URL suggested here below points to the legacy page: <em>export.php</em>. This legacy version of the export has the following limitation: the list of exported fields may vary depending on the output format and the data model of '.ITOP_APPLICATION_SHORT.'. <br/>Should you want to guarantee that the list of exported columns will remain stable on the long run, then you must specify a value for the attribute "Fields" and use the page <em>export-V2.php</em>.',
|
'UI:Query:UrlV1' => 'The list of fields has been left unspecified. The page <em>export-V2.php</em> cannot be invoked without this information. Therefore, the URL suggested here below points to the legacy page: <em>export.php</em>. This legacy version of the export has the following limitation: the list of exported fields may vary depending on the output format and the data model of '.ITOP_APPLICATION_SHORT.'. <br/>Should you want to guarantee that the list of exported columns will remain stable on the long run, then you must specify a value for the attribute "Fields" and use the page <em>export-V2.php</em>.',
|
||||||
'UI:Schema:Title' => ITOP_APPLICATION_SHORT.' objects schema',
|
'UI:Schema:Title' => ITOP_APPLICATION_SHORT.' objects schema',
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
|
Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
|
||||||
'Core:DeletedObjectLabel' => '%1s (törölve)',
|
'Core:DeletedObjectLabel' => '%1$s (törölve)',
|
||||||
'Core:DeletedObjectTip' => 'A %1$s objektum törölve (%2$s)',
|
'Core:DeletedObjectTip' => 'A %1$s objektum törölve (%2$s)',
|
||||||
'Core:UnknownObjectLabel' => 'Objektum nem található (osztály: %1$s, id: %2$d)',
|
'Core:UnknownObjectLabel' => 'Objektum nem található (osztály: %1$s, id: %2$d)',
|
||||||
'Core:UnknownObjectTip' => 'Az objektumot nem sikerült megtalálni. Lehet, hogy már törölték egy ideje, és a naplót azóta törölték.',
|
'Core:UnknownObjectTip' => 'Az objektumot nem sikerült megtalálni. Lehet, hogy már törölték egy ideje, és a naplót azóta törölték.',
|
||||||
|
|||||||
@@ -509,7 +509,7 @@ Reméljük, hogy ezt a verziót ugyanúgy kedvelni fogja, mint ahogy mi élvezt
|
|||||||
'UI:Error:2ParametersMissing' => 'Hiba: a következő paramétereket meg kell adni ennél a műveletnél: %1$s és %2$s.',
|
'UI:Error:2ParametersMissing' => 'Hiba: a következő paramétereket meg kell adni ennél a műveletnél: %1$s és %2$s.',
|
||||||
'UI:Error:3ParametersMissing' => 'Hiba: a következő paramétereket meg kell adni ennél a műveletnél: %1$s, %2$s és %3$s.',
|
'UI:Error:3ParametersMissing' => 'Hiba: a következő paramétereket meg kell adni ennél a műveletnél: %1$s, %2$s és %3$s.',
|
||||||
'UI:Error:4ParametersMissing' => 'Hiba: a következő paramétereket meg kell adni ennél a műveletnél: %1$s, %2$s, %3$s és %4$s.',
|
'UI:Error:4ParametersMissing' => 'Hiba: a következő paramétereket meg kell adni ennél a műveletnél: %1$s, %2$s, %3$s és %4$s.',
|
||||||
'UI:Error:IncorrectOQLQuery_Message' => 'Hiba: nem megfelelő OQL lekérdezés: %1$',
|
'UI:Error:IncorrectOQLQuery_Message' => 'Hiba: nem megfelelő OQL lekérdezés: %1$s',
|
||||||
'UI:Error:AnErrorOccuredWhileRunningTheQuery_Message' => 'Hiba történt a lekérdezés futtatása közben: %1$s',
|
'UI:Error:AnErrorOccuredWhileRunningTheQuery_Message' => 'Hiba történt a lekérdezés futtatása közben: %1$s',
|
||||||
'UI:Error:ObjectAlreadyUpdated' => 'Hiba: az objketum már korábban módosításra került.',
|
'UI:Error:ObjectAlreadyUpdated' => 'Hiba: az objketum már korábban módosításra került.',
|
||||||
'UI:Error:ObjectCannotBeUpdated' => 'Hiba: az objektum nem frissíthető.',
|
'UI:Error:ObjectCannotBeUpdated' => 'Hiba: az objektum nem frissíthető.',
|
||||||
@@ -715,7 +715,7 @@ Reméljük, hogy ezt a verziót ugyanúgy kedvelni fogja, mint ahogy mi élvezt
|
|||||||
'UI:CSVReport-Value-Issue-Null' => 'A nulla nem engedélyezett',
|
'UI:CSVReport-Value-Issue-Null' => 'A nulla nem engedélyezett',
|
||||||
'UI:CSVReport-Value-Issue-NotFound' => 'Az objektum nincs meg',
|
'UI:CSVReport-Value-Issue-NotFound' => 'Az objektum nincs meg',
|
||||||
'UI:CSVReport-Value-Issue-FoundMany' => '%1$d egyezés található',
|
'UI:CSVReport-Value-Issue-FoundMany' => '%1$d egyezés található',
|
||||||
'UI:CSVReport-Value-Issue-Readonly' => 'A \'%1$\'s attribútum csak olvasható (jelenlegi érték: %2$s, várható érték: %3$s)',
|
'UI:CSVReport-Value-Issue-Readonly' => 'A \'%1$s attribútum csak olvasható (jelenlegi érték: %2$s, várható érték: %3$s)',
|
||||||
'UI:CSVReport-Value-Issue-Format' => 'A bevitel feldolgozása sikertelen: %1$s',
|
'UI:CSVReport-Value-Issue-Format' => 'A bevitel feldolgozása sikertelen: %1$s',
|
||||||
'UI:CSVReport-Value-Issue-NoMatch' => 'A \'%1$s\' attribútum nem várt értéket kapott: nincs egyezés, ellenőrizze a beírást',
|
'UI:CSVReport-Value-Issue-NoMatch' => 'A \'%1$s\' attribútum nem várt értéket kapott: nincs egyezés, ellenőrizze a beírást',
|
||||||
'UI:CSVReport-Value-Issue-AllowedValues' => 'Allowed \'%1$s\' value(s): %2$s~~',
|
'UI:CSVReport-Value-Issue-AllowedValues' => 'Allowed \'%1$s\' value(s): %2$s~~',
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
* @licence http://opensource.org/licenses/AGPL-3.0
|
* @licence http://opensource.org/licenses/AGPL-3.0
|
||||||
*/
|
*/
|
||||||
Dict::Add('JA JP', 'Japanese', '日本語', array(
|
Dict::Add('JA JP', 'Japanese', '日本語', array(
|
||||||
'Core:DeletedObjectLabel' => '%1s (削除されました)',
|
'Core:DeletedObjectLabel' => '%1$s (削除されました)',
|
||||||
'Core:DeletedObjectTip' => 'オブジェクトは削除されました %1$s (%2$s)',
|
'Core:DeletedObjectTip' => 'オブジェクトは削除されました %1$s (%2$s)',
|
||||||
'Core:UnknownObjectLabel' => 'オブジェクトは見つかりません (クラス: %1$s, id: %2$d)',
|
'Core:UnknownObjectLabel' => 'オブジェクトは見つかりません (クラス: %1$s, id: %2$d)',
|
||||||
'Core:UnknownObjectTip' => 'オブジェクトは見つかりません。しばらく前に削除され、その後ログが削除されたかもしれません。',
|
'Core:UnknownObjectTip' => 'オブジェクトは見つかりません。しばらく前に削除され、その後ログが削除されたかもしれません。',
|
||||||
|
|||||||
@@ -915,7 +915,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
|||||||
'UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class' => '%2$sクラスの%1$dオブジェクトの削除',
|
'UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class' => '%2$sクラスの%1$dオブジェクトの削除',
|
||||||
'UI:Delete:CannotDeleteBecause' => '削除できません: %1$s',
|
'UI:Delete:CannotDeleteBecause' => '削除できません: %1$s',
|
||||||
'UI:Delete:ShouldBeDeletedAtomaticallyButNotPossible' => '自動的に削除されるべきですが、出来ません。: %1$s',
|
'UI:Delete:ShouldBeDeletedAtomaticallyButNotPossible' => '自動的に削除されるべきですが、出来ません。: %1$s',
|
||||||
'UI:Delete:MustBeDeletedManuallyButNotPossible' => '手動で削除されるべきですが、出来ません。: %1$',
|
'UI:Delete:MustBeDeletedManuallyButNotPossible' => '手動で削除されるべきですが、出来ません。: %1$s',
|
||||||
'UI:Delete:WillBeDeletedAutomatically' => '自動的に削除されます。',
|
'UI:Delete:WillBeDeletedAutomatically' => '自動的に削除されます。',
|
||||||
'UI:Delete:MustBeDeletedManually' => '手動で削除されるべきです。',
|
'UI:Delete:MustBeDeletedManually' => '手動で削除されるべきです。',
|
||||||
'UI:Delete:CannotUpdateBecause_Issue' => '自動的に更新されるべきですが、しかし: %1$s',
|
'UI:Delete:CannotUpdateBecause_Issue' => '自動的に更新されるべきですが、しかし: %1$s',
|
||||||
@@ -1159,8 +1159,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
|||||||
'Enum:Undefined' => '未定義',
|
'Enum:Undefined' => '未定義',
|
||||||
'UI:DurationForm_Days_Hours_Minutes_Seconds' => '%1$s 日 %2$s 時 %3$s 分 %4$s 秒',
|
'UI:DurationForm_Days_Hours_Minutes_Seconds' => '%1$s 日 %2$s 時 %3$s 分 %4$s 秒',
|
||||||
'UI:ModifyAllPageTitle' => '全てを修正',
|
'UI:ModifyAllPageTitle' => '全てを修正',
|
||||||
'UI:Modify_ObjectsOf_Class' => 'Modifying objects of class %1$s~~',
|
'UI:Modify_N_ObjectsOf_Class' => 'クラス%2$sの%1$dオブジェクトを修正',
|
||||||
'UI:Modify_N_ObjectsOf_Class' => 'クラス%2$Sの%1$dオブジェクトを修正',
|
|
||||||
'UI:Modify_M_ObjectsOf_Class_OutOf_N' => 'クラス%2$sの%3$d中%1$dを修正',
|
'UI:Modify_M_ObjectsOf_Class_OutOf_N' => 'クラス%2$sの%3$d中%1$dを修正',
|
||||||
'UI:Menu:ModifyAll' => '修正...',
|
'UI:Menu:ModifyAll' => '修正...',
|
||||||
'UI:Menu:ModifyAll_Class' => 'Modify %1$s objects...~~',
|
'UI:Menu:ModifyAll_Class' => 'Modify %1$s objects...~~',
|
||||||
@@ -1180,7 +1179,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
|||||||
'UI:BulkModify_Count_DistinctValues' => '%1$d 個の個別の値:',
|
'UI:BulkModify_Count_DistinctValues' => '%1$d 個の個別の値:',
|
||||||
'UI:BulkModify:Value_Exists_N_Times' => '%1$s, %2$d 回存在',
|
'UI:BulkModify:Value_Exists_N_Times' => '%1$s, %2$d 回存在',
|
||||||
'UI:BulkModify:N_MoreValues' => '%1$d 個以上の値...',
|
'UI:BulkModify:N_MoreValues' => '%1$d 個以上の値...',
|
||||||
'UI:AttemptingToSetAReadOnlyAttribute_Name' => '読み込み専用フィールド %1$にセットしょうとしています。',
|
'UI:AttemptingToSetAReadOnlyAttribute_Name' => '読み込み専用フィールド %1$sにセットしょうとしています。',
|
||||||
'UI:FailedToApplyStimuli' => 'アクションは失敗しました。',
|
'UI:FailedToApplyStimuli' => 'アクションは失敗しました。',
|
||||||
'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: クラス%3$sの%2$dオブジェクトを修正',
|
'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: クラス%3$sの%2$dオブジェクトを修正',
|
||||||
'UI:CaseLogTypeYourTextHere' => 'テキストを入力ください:',
|
'UI:CaseLogTypeYourTextHere' => 'テキストを入力ください:',
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
Dict::Add('RU RU', 'Russian', 'Русский', array(
|
Dict::Add('RU RU', 'Russian', 'Русский', array(
|
||||||
'Core:DeletedObjectLabel' => '%1ы (удален)',
|
'Core:DeletedObjectLabel' => '%1$sы (удален)',
|
||||||
'Core:DeletedObjectTip' => 'Объект был удален %1$s (%2$s)',
|
'Core:DeletedObjectTip' => 'Объект был удален %1$s (%2$s)',
|
||||||
'Core:UnknownObjectLabel' => 'Объект не найден (class: %1$s, id: %2$d)',
|
'Core:UnknownObjectLabel' => 'Объект не найден (class: %1$s, id: %2$d)',
|
||||||
'Core:UnknownObjectTip' => 'Объект не удается найти. Возможно, он был удален некоторое время назад, и журнал с тех пор был очищен.',
|
'Core:UnknownObjectTip' => 'Объект не удается найти. Возможно, он был удален некоторое время назад, и журнал с тех пор был очищен.',
|
||||||
|
|||||||
@@ -497,7 +497,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
|||||||
'UI:Error:MandatoryTemplateParameter_group_by' => 'Parameter group_by je povinný. Skontrolujte definíciu šablóny zobrazenia.',
|
'UI:Error:MandatoryTemplateParameter_group_by' => 'Parameter group_by je povinný. Skontrolujte definíciu šablóny zobrazenia.',
|
||||||
'UI:Error:InvalidGroupByFields' => 'Neplatný zoznam polí pre skupinu podľa: "%1$s".',
|
'UI:Error:InvalidGroupByFields' => 'Neplatný zoznam polí pre skupinu podľa: "%1$s".',
|
||||||
'UI:Error:UnsupportedStyleOfBlock' => 'Chyba: nepodporovaný štýl bloku: "%1$s".',
|
'UI:Error:UnsupportedStyleOfBlock' => 'Chyba: nepodporovaný štýl bloku: "%1$s".',
|
||||||
'UI:Error:IncorrectLinkDefinition_LinkedClass_Class' => 'Nesprávna definícia spojenia : trieda objektov na manažovanie : %l$s nebol nájdený ako externý kľúč v triede %2$s',
|
'UI:Error:IncorrectLinkDefinition_LinkedClass_Class' => 'Nesprávna definícia spojenia : trieda objektov na manažovanie : %1$s nebol nájdený ako externý kľúč v triede %2$s',
|
||||||
'UI:Error:Object_Class_Id_NotFound' => 'Objekt: %1$s:%2$d nebol nájdený.',
|
'UI:Error:Object_Class_Id_NotFound' => 'Objekt: %1$s:%2$d nebol nájdený.',
|
||||||
'UI:Error:WizardCircularReferenceInDependencies' => 'Chyba: Cyklický odkaz v závislostiach medzi poliami, skontrolujte dátový model.',
|
'UI:Error:WizardCircularReferenceInDependencies' => 'Chyba: Cyklický odkaz v závislostiach medzi poliami, skontrolujte dátový model.',
|
||||||
'UI:Error:UploadedFileTooBig' => 'Nahraný súbor je príliš veľký. (Max povolená veľkosť je %1$s). Ak chcete zmeniť tento limit, obráťte sa na správcu ITOP . (Skontrolujte, PHP konfiguráciu pre upload_max_filesize a post_max_size na serveri).',
|
'UI:Error:UploadedFileTooBig' => 'Nahraný súbor je príliš veľký. (Max povolená veľkosť je %1$s). Ak chcete zmeniť tento limit, obráťte sa na správcu ITOP . (Skontrolujte, PHP konfiguráciu pre upload_max_filesize a post_max_size na serveri).',
|
||||||
@@ -1301,7 +1301,7 @@ Keď sú priradené spúštačom, každej akcii je dané číslo "príkazu", šp
|
|||||||
'UI:DashletGroupBy:Prop-GroupBy:DayOfMonth' => 'Deň v mesiaci pre %1$s',
|
'UI:DashletGroupBy:Prop-GroupBy:DayOfMonth' => 'Deň v mesiaci pre %1$s',
|
||||||
'UI:DashletGroupBy:Prop-GroupBy:Select-Hour' => '%1$s (hodina)',
|
'UI:DashletGroupBy:Prop-GroupBy:Select-Hour' => '%1$s (hodina)',
|
||||||
'UI:DashletGroupBy:Prop-GroupBy:Select-Month' => '%1$s (mesiac)',
|
'UI:DashletGroupBy:Prop-GroupBy:Select-Month' => '%1$s (mesiac)',
|
||||||
'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek' => '%1$ (deň v týžni)',
|
'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek' => '%1$s (deň v týžni)',
|
||||||
'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfMonth' => '%1$s (deň v mesiaci)',
|
'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfMonth' => '%1$s (deň v mesiaci)',
|
||||||
'UI:DashletGroupBy:MissingGroupBy' => 'Prosím zvoľte pole na ktorom objekty budú zoskupené spolu',
|
'UI:DashletGroupBy:MissingGroupBy' => 'Prosím zvoľte pole na ktorom objekty budú zoskupené spolu',
|
||||||
'UI:DashletGroupByPie:Label' => 'Koláčový graf',
|
'UI:DashletGroupByPie:Label' => 'Koláčový graf',
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
|
|||||||
'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s\'nin değeri %2$s olarak atandı (önceki değer: %3$s)',
|
'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s\'nin değeri %2$s olarak atandı (önceki değer: %3$s)',
|
||||||
'Change:AttName_SetTo' => '%1$s\'nin değeri %2$s olarak atandı',
|
'Change:AttName_SetTo' => '%1$s\'nin değeri %2$s olarak atandı',
|
||||||
'Change:Text_AppendedTo_AttName' => '%2$s\'ye %1$s eklendi',
|
'Change:Text_AppendedTo_AttName' => '%2$s\'ye %1$s eklendi',
|
||||||
'Change:AttName_Changed_PreviousValue_OldValue' => '%1$\'nin değeri deiştirildi, önceki değer: %2$s',
|
'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s nin değeri deiştirildi, önceki değer: %2$s',
|
||||||
'Change:AttName_Changed' => '%1$s değiştirildi',
|
'Change:AttName_Changed' => '%1$s değiştirildi',
|
||||||
'Change:AttName_EntryAdded' => '%1$s değiştirilmiş, yeni giriş eklendi.',
|
'Change:AttName_EntryAdded' => '%1$s değiştirilmiş, yeni giriş eklendi.',
|
||||||
'Change:State_Changed_NewValue_OldValue' => 'Changed from %2$s to %1$s~~',
|
'Change:State_Changed_NewValue_OldValue' => 'Changed from %2$s to %1$s~~',
|
||||||
|
|||||||
@@ -19,9 +19,9 @@
|
|||||||
// Display DataTable
|
// Display DataTable
|
||||||
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||||
'UI:Datatables:Language:Processing' => '请稍候...',
|
'UI:Datatables:Language:Processing' => '请稍候...',
|
||||||
'UI:Datatables:Language:LengthMenu' => '_MENU_ 每页',
|
'UI:Datatables:Language:LengthMenu' => '每页 _MENU_ 项',
|
||||||
'UI:Datatables:Language:ZeroRecords' => '未找到相关结果',
|
'UI:Datatables:Language:ZeroRecords' => '未找到相关结果',
|
||||||
'UI:Datatables:Language:Info' => '_TOTAL_ 项',
|
'UI:Datatables:Language:Info' => '共 _TOTAL_ 项',
|
||||||
'UI:Datatables:Language:InfoEmpty' => '未找到相关信息',
|
'UI:Datatables:Language:InfoEmpty' => '未找到相关信息',
|
||||||
'UI:Datatables:Language:EmptyTable' => '表格中暂无数据',
|
'UI:Datatables:Language:EmptyTable' => '表格中暂无数据',
|
||||||
'UI:Datatables:Language:Error' => '运行查询时出错',
|
'UI:Datatables:Language:Error' => '运行查询时出错',
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
|
|||||||
'UI:Layout:NavigationMenu:MenuFilter:Input:Hint' => 'As correspondências em todos os grupos de menus serão exibidas',
|
'UI:Layout:NavigationMenu:MenuFilter:Input:Hint' => 'As correspondências em todos os grupos de menus serão exibidas',
|
||||||
'UI:Layout:NavigationMenu:MenuFilter:Placeholder:Hint' => 'Nenhum resultado para este filtro de menu',
|
'UI:Layout:NavigationMenu:MenuFilter:Placeholder:Hint' => 'Nenhum resultado para este filtro de menu',
|
||||||
'UI:Layout:NavigationMenu:UserInfo:WelcomeMessage:Text' => 'Olá %1$s!',
|
'UI:Layout:NavigationMenu:UserInfo:WelcomeMessage:Text' => 'Olá %1$s!',
|
||||||
'UI:Layout:NavigationMenu:UserInfo:Picture:AltText' => 'Imagem do contato %1$',
|
'UI:Layout:NavigationMenu:UserInfo:Picture:AltText' => 'Imagem do contato %1$s',
|
||||||
'UI:Layout:NavigationMenu:UserMenu:Toggler:Label' => 'Abrir menu do usuário',
|
'UI:Layout:NavigationMenu:UserMenu:Toggler:Label' => 'Abrir menu do usuário',
|
||||||
'UI:Layout:NavigationMenu:KeyboardShortcut:FocusFilter' => 'Filtrar entradas de menu',
|
'UI:Layout:NavigationMenu:KeyboardShortcut:FocusFilter' => 'Filtrar entradas de menu',
|
||||||
|
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Navigation menu
|
// Navigation menu
|
||||||
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||||
'UI:Layout:NavigationMenu:CompanyLogo:AltText' => '公司logo',
|
'UI:Layout:NavigationMenu:CompanyLogo:AltText' => '公司logo',
|
||||||
|
|||||||
@@ -20,44 +20,60 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||||
'Core:DeletedObjectLabel' => '%1s (已删除)',
|
'Core:DeletedObjectLabel' => '%1s (已删除)',
|
||||||
'Core:DeletedObjectTip' => '对象已被删除于 %1$s (%2$s)',
|
'Core:DeletedObjectTip' => '对象已被删除于 %1$s (%2$s)',
|
||||||
|
|
||||||
'Core:UnknownObjectLabel' => '对象找不到 (class: %1$s, id: %2$d)',
|
'Core:UnknownObjectLabel' => '对象找不到 (class: %1$s, id: %2$d)',
|
||||||
'Core:UnknownObjectTip' => 'The object could not be found. It may have been deleted some time ago and the log has been purged since.~~',
|
'Core:UnknownObjectTip' => 'The object could not be found. It may have been deleted some time ago and the log has been purged since.~~',
|
||||||
|
|
||||||
'Core:UniquenessDefaultError' => 'Uniqueness rule \'%1$s\' in error~~',
|
'Core:UniquenessDefaultError' => 'Uniqueness rule \'%1$s\' in error~~',
|
||||||
'Core:CheckConsistencyError' => 'Consistency rules not followed: %1$s~~',
|
'Core:CheckConsistencyError' => 'Consistency rules not followed: %1$s~~',
|
||||||
'Core:CheckValueError' => 'Unexpected value for attribute \'%1$s\' (%2$s) : %3$s~~',
|
'Core:CheckValueError' => 'Unexpected value for attribute \'%1$s\' (%2$s) : %3$s~~',
|
||||||
|
|
||||||
'Core:AttributeLinkedSet' => '对象数组',
|
'Core:AttributeLinkedSet' => '对象数组',
|
||||||
'Core:AttributeLinkedSet+' => 'Any kind of objects of the same class or subclass~~',
|
'Core:AttributeLinkedSet+' => 'Any kind of objects of the same class or subclass~~',
|
||||||
|
|
||||||
'Core:AttributeLinkedSetDuplicatesFound' => 'Duplicates in the \'%1$s\' field : %2$s~~',
|
'Core:AttributeLinkedSetDuplicatesFound' => 'Duplicates in the \'%1$s\' field : %2$s~~',
|
||||||
|
|
||||||
'Core:AttributeDashboard' => '仪表盘',
|
'Core:AttributeDashboard' => '仪表盘',
|
||||||
'Core:AttributeDashboard+' => '',
|
'Core:AttributeDashboard+' => '',
|
||||||
|
|
||||||
'Core:AttributePhoneNumber' => '电话号码',
|
'Core:AttributePhoneNumber' => '电话号码',
|
||||||
'Core:AttributePhoneNumber+' => '',
|
'Core:AttributePhoneNumber+' => '',
|
||||||
|
|
||||||
'Core:AttributeObsolescenceDate' => '报废日期',
|
'Core:AttributeObsolescenceDate' => '报废日期',
|
||||||
'Core:AttributeObsolescenceDate+' => '',
|
'Core:AttributeObsolescenceDate+' => '',
|
||||||
|
|
||||||
'Core:AttributeTagSet' => '清单',
|
'Core:AttributeTagSet' => '清单',
|
||||||
'Core:AttributeTagSet+' => '',
|
'Core:AttributeTagSet+' => '',
|
||||||
'Core:AttributeSet:placeholder' => '请点击这里添加',
|
'Core:AttributeSet:placeholder' => '请点击这里添加',
|
||||||
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromClass' => '%1$s (%2$s)~~',
|
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromClass' => '%1$s (%2$s)~~',
|
||||||
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromOneChildClass' => '%1$s (%2$s from %3$s)~~',
|
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromOneChildClass' => '%1$s (%2$s from %3$s)~~',
|
||||||
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromSeveralChildClasses' => '%1$s (%2$s from child classes)~~',
|
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromSeveralChildClasses' => '%1$s (%2$s from child classes)~~',
|
||||||
|
|
||||||
'Core:AttributeCaseLog' => '日志',
|
'Core:AttributeCaseLog' => '日志',
|
||||||
'Core:AttributeCaseLog+' => '',
|
'Core:AttributeCaseLog+' => '',
|
||||||
|
|
||||||
'Core:AttributeMetaEnum' => 'Computed enum~~',
|
'Core:AttributeMetaEnum' => 'Computed enum~~',
|
||||||
'Core:AttributeMetaEnum+' => '~~',
|
'Core:AttributeMetaEnum+' => '~~',
|
||||||
|
|
||||||
'Core:AttributeLinkedSetIndirect' => '对象数组(N-N)',
|
'Core:AttributeLinkedSetIndirect' => '对象数组(N-N)',
|
||||||
'Core:AttributeLinkedSetIndirect+' => 'Any kind of objects [subclass] of the same class~~',
|
'Core:AttributeLinkedSetIndirect+' => 'Any kind of objects [subclass] of the same class~~',
|
||||||
|
|
||||||
'Core:AttributeInteger' => '整数',
|
'Core:AttributeInteger' => '整数',
|
||||||
'Core:AttributeInteger+' => '整数值(可以为负)',
|
'Core:AttributeInteger+' => '整数值(可以为负)',
|
||||||
|
|
||||||
'Core:AttributeDecimal' => '小数',
|
'Core:AttributeDecimal' => '小数',
|
||||||
'Core:AttributeDecimal+' => '小数(可以为负)',
|
'Core:AttributeDecimal+' => '小数(可以为负)',
|
||||||
|
|
||||||
'Core:AttributeBoolean' => '布尔',
|
'Core:AttributeBoolean' => '布尔',
|
||||||
'Core:AttributeBoolean+' => '布尔',
|
'Core:AttributeBoolean+' => '',
|
||||||
'Core:AttributeBoolean/Value:null' => '',
|
'Core:AttributeBoolean/Value:null' => '',
|
||||||
'Core:AttributeBoolean/Value:yes' => '是',
|
'Core:AttributeBoolean/Value:yes' => '是',
|
||||||
'Core:AttributeBoolean/Value:no' => '否',
|
'Core:AttributeBoolean/Value:no' => '否',
|
||||||
|
|
||||||
'Core:AttributeArchiveFlag' => '是否归档',
|
'Core:AttributeArchiveFlag' => '是否归档',
|
||||||
'Core:AttributeArchiveFlag/Value:yes' => '是',
|
'Core:AttributeArchiveFlag/Value:yes' => '是',
|
||||||
'Core:AttributeArchiveFlag/Value:yes+' => '此对象仅在归档模式可见',
|
'Core:AttributeArchiveFlag/Value:yes+' => '此对象仅在归档模式可见',
|
||||||
@@ -66,6 +82,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Core:AttributeArchiveFlag/Label+' => '',
|
'Core:AttributeArchiveFlag/Label+' => '',
|
||||||
'Core:AttributeArchiveDate/Label' => '归档日期',
|
'Core:AttributeArchiveDate/Label' => '归档日期',
|
||||||
'Core:AttributeArchiveDate/Label+' => '',
|
'Core:AttributeArchiveDate/Label+' => '',
|
||||||
|
|
||||||
'Core:AttributeObsolescenceFlag' => '是否废弃',
|
'Core:AttributeObsolescenceFlag' => '是否废弃',
|
||||||
'Core:AttributeObsolescenceFlag/Value:yes' => '是',
|
'Core:AttributeObsolescenceFlag/Value:yes' => '是',
|
||||||
'Core:AttributeObsolescenceFlag/Value:yes+' => 'This object is excluded from the impact analysis, and hidden from search results~~',
|
'Core:AttributeObsolescenceFlag/Value:yes+' => 'This object is excluded from the impact analysis, and hidden from search results~~',
|
||||||
@@ -74,38 +91,54 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Core:AttributeObsolescenceFlag/Label+' => 'Computed dynamically on other attributes~~',
|
'Core:AttributeObsolescenceFlag/Label+' => 'Computed dynamically on other attributes~~',
|
||||||
'Core:AttributeObsolescenceDate/Label' => '废弃时间',
|
'Core:AttributeObsolescenceDate/Label' => '废弃时间',
|
||||||
'Core:AttributeObsolescenceDate/Label+' => 'Approximative date at which the object has been considered obsolete~~',
|
'Core:AttributeObsolescenceDate/Label+' => 'Approximative date at which the object has been considered obsolete~~',
|
||||||
|
|
||||||
'Core:AttributeString' => '字符串',
|
'Core:AttributeString' => '字符串',
|
||||||
'Core:AttributeString+' => '字符串',
|
'Core:AttributeString+' => '字符串',
|
||||||
|
|
||||||
'Core:AttributeClass' => '类',
|
'Core:AttributeClass' => '类',
|
||||||
'Core:AttributeClass+' => '类别',
|
'Core:AttributeClass+' => '',
|
||||||
|
|
||||||
'Core:AttributeApplicationLanguage' => '用户语言',
|
'Core:AttributeApplicationLanguage' => '用户语言',
|
||||||
'Core:AttributeApplicationLanguage+' => '语言和国家地区(EN US)',
|
'Core:AttributeApplicationLanguage+' => '语言和国家地区(EN US)',
|
||||||
|
|
||||||
'Core:AttributeFinalClass' => '类 (auto)',
|
'Core:AttributeFinalClass' => '类 (auto)',
|
||||||
'Core:AttributeFinalClass+' => 'Real class of the object (automatically created by the core)',
|
'Core:AttributeFinalClass+' => 'Real class of the object (automatically created by the core)',
|
||||||
|
|
||||||
'Core:AttributePassword' => '密码',
|
'Core:AttributePassword' => '密码',
|
||||||
'Core:AttributePassword+' => '外部设备的密码',
|
'Core:AttributePassword+' => '外部设备的密码',
|
||||||
|
|
||||||
'Core:AttributeEncryptedString' => '加密字符串',
|
'Core:AttributeEncryptedString' => '加密字符串',
|
||||||
'Core:AttributeEncryptedString+' => 'String encrypted with a local key~~',
|
'Core:AttributeEncryptedString+' => 'String encrypted with a local key~~',
|
||||||
'Core:AttributeEncryptUnknownLibrary' => '未知的加密库 (%1$s)',
|
'Core:AttributeEncryptUnknownLibrary' => '未知的加密库 (%1$s)',
|
||||||
'Core:AttributeEncryptFailedToDecrypt' => '** 解密错误 **',
|
'Core:AttributeEncryptFailedToDecrypt' => '** 解密错误 **',
|
||||||
|
|
||||||
'Core:AttributeText' => '文本',
|
'Core:AttributeText' => '文本',
|
||||||
'Core:AttributeText+' => '多行字符串',
|
'Core:AttributeText+' => '多行字符串',
|
||||||
|
|
||||||
'Core:AttributeHTML' => 'HTML',
|
'Core:AttributeHTML' => 'HTML',
|
||||||
'Core:AttributeHTML+' => 'HTML字符串',
|
'Core:AttributeHTML+' => 'HTML字符串',
|
||||||
|
|
||||||
'Core:AttributeEmailAddress' => '邮箱地址',
|
'Core:AttributeEmailAddress' => '邮箱地址',
|
||||||
'Core:AttributeEmailAddress+' => '邮箱地址',
|
'Core:AttributeEmailAddress+' => '邮箱地址',
|
||||||
|
|
||||||
'Core:AttributeIPAddress' => 'IP地址',
|
'Core:AttributeIPAddress' => 'IP地址',
|
||||||
'Core:AttributeIPAddress+' => 'IP地址',
|
'Core:AttributeIPAddress+' => 'IP地址',
|
||||||
|
|
||||||
'Core:AttributeOQL' => 'OQL',
|
'Core:AttributeOQL' => 'OQL',
|
||||||
'Core:AttributeOQL+' => 'Object Query Langage expression~~',
|
'Core:AttributeOQL+' => 'Object Query Langage expression~~',
|
||||||
|
|
||||||
'Core:AttributeEnum' => 'Enum~~',
|
'Core:AttributeEnum' => 'Enum~~',
|
||||||
'Core:AttributeEnum+' => 'List of predefined alphanumeric strings~~',
|
'Core:AttributeEnum+' => 'List of predefined alphanumeric strings~~',
|
||||||
|
|
||||||
'Core:AttributeTemplateString' => '字符模板',
|
'Core:AttributeTemplateString' => '字符模板',
|
||||||
'Core:AttributeTemplateString+' => '包含占位符的字符串',
|
'Core:AttributeTemplateString+' => '包含占位符的字符串',
|
||||||
|
|
||||||
'Core:AttributeTemplateText' => '文字模板',
|
'Core:AttributeTemplateText' => '文字模板',
|
||||||
'Core:AttributeTemplateText+' => '包含占位符的文本',
|
'Core:AttributeTemplateText+' => '包含占位符的文本',
|
||||||
|
|
||||||
'Core:AttributeTemplateHTML' => 'HTML模板',
|
'Core:AttributeTemplateHTML' => 'HTML模板',
|
||||||
'Core:AttributeTemplateHTML+' => 'HTML containing placeholders~~',
|
'Core:AttributeTemplateHTML+' => 'HTML containing placeholders~~',
|
||||||
|
|
||||||
'Core:AttributeDateTime' => '日期/时间',
|
'Core:AttributeDateTime' => '日期/时间',
|
||||||
'Core:AttributeDateTime+' => 'Date and time (年-月-日 时:分:秒)',
|
'Core:AttributeDateTime+' => 'Date and time (年-月-日 时:分:秒)',
|
||||||
'Core:AttributeDateTime?SmartSearch' => '
|
'Core:AttributeDateTime?SmartSearch' => '
|
||||||
@@ -123,6 +156,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
<p>
|
<p>
|
||||||
如果不写具体时间,则默认00:00:00
|
如果不写具体时间,则默认00:00:00
|
||||||
</p>',
|
</p>',
|
||||||
|
|
||||||
'Core:AttributeDate' => '日期',
|
'Core:AttributeDate' => '日期',
|
||||||
'Core:AttributeDate+' => '日期 (年-月-日)',
|
'Core:AttributeDate+' => '日期 (年-月-日)',
|
||||||
'Core:AttributeDate?SmartSearch' => '
|
'Core:AttributeDate?SmartSearch' => '
|
||||||
@@ -137,30 +171,43 @@ Operators:<br/>
|
|||||||
<b><</b><em>日期</em><br/>
|
<b><</b><em>日期</em><br/>
|
||||||
<b>[</b><em>日期</em>,<em>日期</em><b>]</b>
|
<b>[</b><em>日期</em>,<em>日期</em><b>]</b>
|
||||||
</p>',
|
</p>',
|
||||||
|
|
||||||
'Core:AttributeDeadline' => '截止日期',
|
'Core:AttributeDeadline' => '截止日期',
|
||||||
'Core:AttributeDeadline+' => '日期, 显示与当前的相对时间',
|
'Core:AttributeDeadline+' => '日期, 显示与当前的相对时间',
|
||||||
|
|
||||||
'Core:AttributeExternalKey' => '外键',
|
'Core:AttributeExternalKey' => '外键',
|
||||||
'Core:AttributeExternalKey+' => 'External (or foreign) key~~',
|
'Core:AttributeExternalKey+' => 'External (or foreign) key~~',
|
||||||
|
|
||||||
'Core:AttributeHierarchicalKey' => 'Hierarchical Key~~',
|
'Core:AttributeHierarchicalKey' => 'Hierarchical Key~~',
|
||||||
'Core:AttributeHierarchicalKey+' => 'External (or foreign) key to the parent~~',
|
'Core:AttributeHierarchicalKey+' => 'External (or foreign) key to the parent~~',
|
||||||
|
|
||||||
'Core:AttributeExternalField' => '外部字段',
|
'Core:AttributeExternalField' => '外部字段',
|
||||||
'Core:AttributeExternalField+' => 'Field mapped to an external key~~',
|
'Core:AttributeExternalField+' => 'Field mapped to an external key~~',
|
||||||
|
|
||||||
'Core:AttributeURL' => 'URL',
|
'Core:AttributeURL' => 'URL',
|
||||||
'Core:AttributeURL+' => 'Absolute or relative URL as a text string~~',
|
'Core:AttributeURL+' => 'Absolute or relative URL as a text string~~',
|
||||||
|
|
||||||
'Core:AttributeBlob' => 'Blob',
|
'Core:AttributeBlob' => 'Blob',
|
||||||
'Core:AttributeBlob+' => '任何二进制内容(文档)',
|
'Core:AttributeBlob+' => '任何二进制内容(文档)',
|
||||||
|
|
||||||
'Core:AttributeOneWayPassword' => '单向密码',
|
'Core:AttributeOneWayPassword' => '单向密码',
|
||||||
'Core:AttributeOneWayPassword+' => '单向加密(或哈希) 的密码',
|
'Core:AttributeOneWayPassword+' => '单向加密(或哈希) 的密码',
|
||||||
|
|
||||||
'Core:AttributeTable' => '表',
|
'Core:AttributeTable' => '表',
|
||||||
'Core:AttributeTable+' => '带索引的二维数组',
|
'Core:AttributeTable+' => '带索引的二维数组',
|
||||||
|
|
||||||
'Core:AttributePropertySet' => '属性',
|
'Core:AttributePropertySet' => '属性',
|
||||||
'Core:AttributePropertySet+' => 'List of untyped properties (name and value)~~',
|
'Core:AttributePropertySet+' => 'List of untyped properties (name and value)~~',
|
||||||
|
|
||||||
'Core:AttributeFriendlyName' => '通用名称',
|
'Core:AttributeFriendlyName' => '通用名称',
|
||||||
'Core:AttributeFriendlyName+' => 'Attribute created automatically ; the friendly name is computed after several attributes~~',
|
'Core:AttributeFriendlyName+' => 'Attribute created automatically ; the friendly name is computed after several attributes~~',
|
||||||
|
|
||||||
'Core:FriendlyName-Label' => '全称',
|
'Core:FriendlyName-Label' => '全称',
|
||||||
'Core:FriendlyName-Description' => '全称',
|
'Core:FriendlyName-Description' => '全称',
|
||||||
|
|
||||||
'Core:AttributeTag' => '标签',
|
'Core:AttributeTag' => '标签',
|
||||||
'Core:AttributeTag+' => '标签',
|
'Core:AttributeTag+' => '标签',
|
||||||
|
|
||||||
'Core:Context=REST/JSON' => 'REST',
|
'Core:Context=REST/JSON' => 'REST',
|
||||||
'Core:Context=Synchro' => '同步',
|
'Core:Context=Synchro' => '同步',
|
||||||
'Core:Context=Setup' => '安装向导',
|
'Core:Context=Setup' => '安装向导',
|
||||||
@@ -201,10 +248,10 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
//
|
//
|
||||||
|
|
||||||
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||||
'Class:CMDBChangeOp' => '变更操作',
|
'Class:CMDBChangeOp' => '变更操作跟踪',
|
||||||
'Class:CMDBChangeOp+' => '变更操作跟踪',
|
'Class:CMDBChangeOp+' => '某人在某时某刻对某个对象的变更操作',
|
||||||
'Class:CMDBChangeOp/Attribute:change' => '变更',
|
'Class:CMDBChangeOp/Attribute:change' => '变更',
|
||||||
'Class:CMDBChangeOp/Attribute:change+' => '变更',
|
'Class:CMDBChangeOp/Attribute:change+' => '',
|
||||||
'Class:CMDBChangeOp/Attribute:date' => '日期',
|
'Class:CMDBChangeOp/Attribute:date' => '日期',
|
||||||
'Class:CMDBChangeOp/Attribute:date+' => '变更的日期和时间',
|
'Class:CMDBChangeOp/Attribute:date+' => '变更的日期和时间',
|
||||||
'Class:CMDBChangeOp/Attribute:userinfo' => '用户',
|
'Class:CMDBChangeOp/Attribute:userinfo' => '用户',
|
||||||
@@ -325,9 +372,9 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:EventNotification' => '通知事件',
|
'Class:EventNotification' => '通知事件',
|
||||||
'Class:EventNotification+' => 'Trace of a notification that has been sent~~',
|
'Class:EventNotification+' => 'Trace of a notification that has been sent~~',
|
||||||
'Class:EventNotification/Attribute:trigger_id' => '触发器',
|
'Class:EventNotification/Attribute:trigger_id' => '触发器',
|
||||||
'Class:EventNotification/Attribute:trigger_id+' => '用户账户',
|
'Class:EventNotification/Attribute:trigger_id+' => '用户账号',
|
||||||
'Class:EventNotification/Attribute:action_id' => '用户',
|
'Class:EventNotification/Attribute:action_id' => '用户',
|
||||||
'Class:EventNotification/Attribute:action_id+' => '用户账户',
|
'Class:EventNotification/Attribute:action_id+' => '用户账号',
|
||||||
'Class:EventNotification/Attribute:object_id' => '对象id',
|
'Class:EventNotification/Attribute:object_id' => '对象id',
|
||||||
'Class:EventNotification/Attribute:object_id+' => 'object id (class defined by the trigger ?)~~',
|
'Class:EventNotification/Attribute:object_id+' => 'object id (class defined by the trigger ?)~~',
|
||||||
));
|
));
|
||||||
@@ -408,8 +455,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:EventRestService/Attribute:version+' => '参数 \'版本\'',
|
'Class:EventRestService/Attribute:version+' => '参数 \'版本\'',
|
||||||
'Class:EventRestService/Attribute:json_input' => '输入',
|
'Class:EventRestService/Attribute:json_input' => '输入',
|
||||||
'Class:EventRestService/Attribute:json_input+' => 'Argument \'json_data\'~~',
|
'Class:EventRestService/Attribute:json_input+' => 'Argument \'json_data\'~~',
|
||||||
'Class:EventRestService/Attribute:code' => '代码',
|
'Class:EventRestService/Attribute:code' => '编码',
|
||||||
'Class:EventRestService/Attribute:code+' => '返回代码',
|
'Class:EventRestService/Attribute:code+' => '返回编码',
|
||||||
'Class:EventRestService/Attribute:json_output' => '响应',
|
'Class:EventRestService/Attribute:json_output' => '响应',
|
||||||
'Class:EventRestService/Attribute:json_output+' => 'HTTP 响应 (json)',
|
'Class:EventRestService/Attribute:json_output+' => 'HTTP 响应 (json)',
|
||||||
'Class:EventRestService/Attribute:provider' => '提供者',
|
'Class:EventRestService/Attribute:provider' => '提供者',
|
||||||
@@ -660,7 +707,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||||
'Class:TriggerOnAttributeBlobDownload' => 'Trigger (on object\'s document download)~~',
|
'Class:TriggerOnAttributeBlobDownload' => 'Trigger (on object\'s document download)~~',
|
||||||
'Class:TriggerOnAttributeBlobDownload+' => 'Trigger on object\'s document field download of [a child class of] the given class~~',
|
'Class:TriggerOnAttributeBlobDownload+' => 'Trigger on object\'s document field download of [a child class of] the given class~~',
|
||||||
'Class:TriggerOnAttributeBlobDownload/Attribute:target_attcodes' => 'Target fields~~',
|
'Class:TriggerOnAttributeBlobDownload/Attribute:target_attcodes' => '目标字段',
|
||||||
'Class:TriggerOnAttributeBlobDownload/Attribute:target_attcodes+' => '',
|
'Class:TriggerOnAttributeBlobDownload/Attribute:target_attcodes+' => '',
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -671,7 +718,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||||
'Class:TriggerOnThresholdReached' => '触发器 (基于阈值)',
|
'Class:TriggerOnThresholdReached' => '触发器 (基于阈值)',
|
||||||
'Class:TriggerOnThresholdReached+' => '当达到某个阈值时触发',
|
'Class:TriggerOnThresholdReached+' => '当达到某个阈值时触发',
|
||||||
'Class:TriggerOnThresholdReached/Attribute:stop_watch_code' => '秒表',
|
'Class:TriggerOnThresholdReached/Attribute:stop_watch_code' => '计时',
|
||||||
'Class:TriggerOnThresholdReached/Attribute:stop_watch_code+' => '',
|
'Class:TriggerOnThresholdReached/Attribute:stop_watch_code+' => '',
|
||||||
'Class:TriggerOnThresholdReached/Attribute:threshold_index' => '阈值',
|
'Class:TriggerOnThresholdReached/Attribute:threshold_index' => '阈值',
|
||||||
'Class:TriggerOnThresholdReached/Attribute:threshold_index+' => '',
|
'Class:TriggerOnThresholdReached/Attribute:threshold_index+' => '',
|
||||||
@@ -768,6 +815,7 @@ The hyperlink is displayed in the tooltip appearing on the “Lock” symbol of
|
|||||||
'Class:SynchroDataSource/Attribute:user_delete_policy/Value:administrators' => 'Administrators only',
|
'Class:SynchroDataSource/Attribute:user_delete_policy/Value:administrators' => 'Administrators only',
|
||||||
'Class:SynchroDataSource/Attribute:user_delete_policy/Value:everybody' => 'Everybody allowed to delete such objects',
|
'Class:SynchroDataSource/Attribute:user_delete_policy/Value:everybody' => 'Everybody allowed to delete such objects',
|
||||||
'Class:SynchroDataSource/Attribute:user_delete_policy/Value:nobody' => 'Nobody',
|
'Class:SynchroDataSource/Attribute:user_delete_policy/Value:nobody' => 'Nobody',
|
||||||
|
|
||||||
'SynchroDataSource:Description' => '描述',
|
'SynchroDataSource:Description' => '描述',
|
||||||
'SynchroDataSource:Reconciliation' => 'Search & reconciliation~~',
|
'SynchroDataSource:Reconciliation' => 'Search & reconciliation~~',
|
||||||
'SynchroDataSource:Deletion' => 'Deletion rules~~',
|
'SynchroDataSource:Deletion' => 'Deletion rules~~',
|
||||||
@@ -1005,8 +1053,9 @@ The hyperlink is displayed in the tooltip appearing on the “Lock” symbol of
|
|||||||
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||||
'Class:TagSetFieldData' => '%2$s for class %1$s~~',
|
'Class:TagSetFieldData' => '%2$s for class %1$s~~',
|
||||||
'Class:TagSetFieldData+' => '~~',
|
'Class:TagSetFieldData+' => '~~',
|
||||||
'Class:TagSetFieldData/Attribute:code' => '代码',
|
|
||||||
'Class:TagSetFieldData/Attribute:code+' => '内部代码. 必须至少包含3个数字或字母',
|
'Class:TagSetFieldData/Attribute:code' => '编码',
|
||||||
|
'Class:TagSetFieldData/Attribute:code+' => '内部编码. 必须至少包含3个数字或字母',
|
||||||
'Class:TagSetFieldData/Attribute:label' => '标签',
|
'Class:TagSetFieldData/Attribute:label' => '标签',
|
||||||
'Class:TagSetFieldData/Attribute:label+' => '显示的标签',
|
'Class:TagSetFieldData/Attribute:label+' => '显示的标签',
|
||||||
'Class:TagSetFieldData/Attribute:description' => '描述',
|
'Class:TagSetFieldData/Attribute:description' => '描述',
|
||||||
@@ -1014,6 +1063,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:TagSetFieldData/Attribute:finalclass' => 'Tag class~~~~',
|
'Class:TagSetFieldData/Attribute:finalclass' => 'Tag class~~~~',
|
||||||
'Class:TagSetFieldData/Attribute:obj_class' => 'Object class~~~~',
|
'Class:TagSetFieldData/Attribute:obj_class' => 'Object class~~~~',
|
||||||
'Class:TagSetFieldData/Attribute:obj_attcode' => 'Field code~~~~',
|
'Class:TagSetFieldData/Attribute:obj_attcode' => 'Field code~~~~',
|
||||||
|
|
||||||
'Core:TagSetFieldData:ErrorDeleteUsedTag' => '已使用的标签无法删除',
|
'Core:TagSetFieldData:ErrorDeleteUsedTag' => '已使用的标签无法删除',
|
||||||
'Core:TagSetFieldData:ErrorDuplicateTagCodeOrLabel' => 'Tags codes or labels must be unique~~',
|
'Core:TagSetFieldData:ErrorDuplicateTagCodeOrLabel' => 'Tags codes or labels must be unique~~',
|
||||||
'Core:TagSetFieldData:ErrorTagCodeSyntax' => 'Tags code must contain between 3 and %1$d alphanumeric characters, starting with a letter.~~',
|
'Core:TagSetFieldData:ErrorTagCodeSyntax' => 'Tags code must contain between 3 and %1$d alphanumeric characters, starting with a letter.~~',
|
||||||
@@ -1139,6 +1189,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'Class:ResourceSystemMenu' => 'Resource System Menu~~',
|
'Class:ResourceSystemMenu' => 'Resource System Menu~~',
|
||||||
'Class:ResourceSystemMenu+' => '',
|
'Class:ResourceSystemMenu+' => '',
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
||||||
// Additional language entries not present in English dict
|
// Additional language entries not present in English dict
|
||||||
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||||
'INTERNAL:JQuery-DatePicker:LangCode' => 'zh-CN'
|
'INTERNAL:JQuery-DatePicker:LangCode' => 'zh-CN'
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -117,6 +117,7 @@ $(function()
|
|||||||
locked_by_someone_else: 'locked_by_someone_else',
|
locked_by_someone_else: 'locked_by_someone_else',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
release_lock_promise_resolve: null, // N°4494 - Resolve callback of the Promise used for the action following the log entry send, which must be done only once the lock is released
|
||||||
|
|
||||||
// the constructor
|
// the constructor
|
||||||
_create: function () {
|
_create: function () {
|
||||||
@@ -500,7 +501,7 @@ $(function()
|
|||||||
{
|
{
|
||||||
// Hide all filters' options only if click wasn't on one of them
|
// Hide all filters' options only if click wasn't on one of them
|
||||||
if(($(oEvent.target).closest(this.js_selectors.activity_filter_options_toggler).length === 0)
|
if(($(oEvent.target).closest(this.js_selectors.activity_filter_options_toggler).length === 0)
|
||||||
&& $(oEvent.target).closest(this.js_selectors.activity_filter_options).length === 0) {
|
&& $(oEvent.target).closest(this.js_selectors.activity_filter_options).length === 0) {
|
||||||
this._HideAllFiltersOptions();
|
this._HideAllFiltersOptions();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -947,9 +948,9 @@ $(function()
|
|||||||
|
|
||||||
// Send request to server
|
// Send request to server
|
||||||
$.post(
|
$.post(
|
||||||
GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
|
GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
|
||||||
oParams,
|
oParams,
|
||||||
'json'
|
'json'
|
||||||
)
|
)
|
||||||
.fail(function (oXHR, sStatus, sErrorThrown) {
|
.fail(function (oXHR, sStatus, sErrorThrown) {
|
||||||
CombodoModal.OpenErrorModal(sErrorThrown);
|
CombodoModal.OpenErrorModal(sErrorThrown);
|
||||||
@@ -972,12 +973,24 @@ $(function()
|
|||||||
|
|
||||||
// For now, we don't hide the forms as the user may want to add something else
|
// For now, we don't hide the forms as the user may want to add something else
|
||||||
me.element.find(me.js_selectors.caselog_entry_form).trigger('clear_entry.caselog_entry_form.itop');
|
me.element.find(me.js_selectors.caselog_entry_form).trigger('clear_entry.caselog_entry_form.itop');
|
||||||
|
|
||||||
// Redirect to stimulus
|
// Redirect to stimulus
|
||||||
// - Convert undefined, null and empty string to null
|
// - Convert undefined, null and empty string to null
|
||||||
sStimulusCode = ((sStimulusCode ?? '') === '') ? null : sStimulusCode;
|
sStimulusCode = ((sStimulusCode ?? '') === '') ? null : sStimulusCode;
|
||||||
if (null !== sStimulusCode) {
|
if (null !== sStimulusCode) {
|
||||||
window.location.href = GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=stimulus&class='+me._GetHostObjectClass()+'&id='+me._GetHostObjectID()+'&stimulus='+sStimulusCode;
|
if (me.options.lock_enabled) {
|
||||||
|
// Use a Promise to ensure that we redirect to the stimulus page ONLY when the lock is released, otherwise we might lock ourselves
|
||||||
|
const oPromise = new Promise(function(resolve) {
|
||||||
|
// Store the resolve callback so we can call it later from outside
|
||||||
|
me.release_lock_promise_resolve = resolve;
|
||||||
|
});
|
||||||
|
oPromise.then(function () {
|
||||||
|
window.location.href = GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=stimulus&class='+me._GetHostObjectClass()+'&id='+me._GetHostObjectID()+'&stimulus='+sStimulusCode;
|
||||||
|
// Resolve callback is reinitialized in case the redirection fails for any reason and we might need to retry
|
||||||
|
me.release_lock_promise_resolve = null;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.location.href = GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=stimulus&class='+me._GetHostObjectClass()+'&id='+me._GetHostObjectID()+'&stimulus='+sStimulusCode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.always(function () {
|
.always(function () {
|
||||||
@@ -995,7 +1008,7 @@ $(function()
|
|||||||
_IncreaseTabTogglerMessagesCounter: function(sCaseLogAttCode){
|
_IncreaseTabTogglerMessagesCounter: function(sCaseLogAttCode){
|
||||||
let oTabTogglerCounter = this._GetTabTogglerFromCaseLogAttCode(sCaseLogAttCode).find('[data-role="ibo-activity-panel--tab-title-messages-count"]');
|
let oTabTogglerCounter = this._GetTabTogglerFromCaseLogAttCode(sCaseLogAttCode).find('[data-role="ibo-activity-panel--tab-title-messages-count"]');
|
||||||
let iNewCounterValue = parseInt(oTabTogglerCounter.attr('data-messages-count')) + 1;
|
let iNewCounterValue = parseInt(oTabTogglerCounter.attr('data-messages-count')) + 1;
|
||||||
|
|
||||||
oTabTogglerCounter.attr('data-messages-count', iNewCounterValue).text(iNewCounterValue);
|
oTabTogglerCounter.attr('data-messages-count', iNewCounterValue).text(iNewCounterValue);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@@ -1143,11 +1156,10 @@ $(function()
|
|||||||
else {
|
else {
|
||||||
oParams.operation = 'check_lock_state';
|
oParams.operation = 'check_lock_state';
|
||||||
}
|
}
|
||||||
|
|
||||||
$.post(
|
$.post(
|
||||||
this.options.lock_endpoint,
|
this.options.lock_endpoint,
|
||||||
oParams,
|
oParams,
|
||||||
'json'
|
'json'
|
||||||
)
|
)
|
||||||
.fail(function (oXHR, sStatus, sErrorThrown) {
|
.fail(function (oXHR, sStatus, sErrorThrown) {
|
||||||
// In case of HTTP request failure (not lock request), put the details in the JS console
|
// In case of HTTP request failure (not lock request), put the details in the JS console
|
||||||
@@ -1196,6 +1208,9 @@ $(function()
|
|||||||
// Tried to release our lock
|
// Tried to release our lock
|
||||||
else if ('release_lock' === oParams.operation) {
|
else if ('release_lock' === oParams.operation) {
|
||||||
sNewLockStatus = me.enums.lock_status.unknown;
|
sNewLockStatus = me.enums.lock_status.unknown;
|
||||||
|
if (me.release_lock_promise_resolve !== null) {
|
||||||
|
me.release_lock_promise_resolve();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just checked if object was locked
|
// Just checked if object was locked
|
||||||
@@ -1430,9 +1445,9 @@ $(function()
|
|||||||
limit_results_length: bLimitResultsLength,
|
limit_results_length: bLimitResultsLength,
|
||||||
};
|
};
|
||||||
$.post(
|
$.post(
|
||||||
this.options.load_more_entries_endpoint,
|
this.options.load_more_entries_endpoint,
|
||||||
oParams,
|
oParams,
|
||||||
'json'
|
'json'
|
||||||
)
|
)
|
||||||
.fail(function (oXHR, sStatus, sErroThrown) {
|
.fail(function (oXHR, sStatus, sErroThrown) {
|
||||||
CombodoModal.OpenErrorModal(sErrorThrown);
|
CombodoModal.OpenErrorModal(sErrorThrown);
|
||||||
|
|||||||
@@ -153,16 +153,15 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizH
|
|||||||
"dataType": "html"
|
"dataType": "html"
|
||||||
})
|
})
|
||||||
.done(function (data) {
|
.done(function (data) {
|
||||||
|
/* N°6152 - Hide during data loading and before open */
|
||||||
|
$('#dlg_'+me.id).hide();
|
||||||
$('#dlg_'+me.id).html(data);
|
$('#dlg_'+me.id).html(data);
|
||||||
window[sPromiseId].then(function () {
|
window[sPromiseId].then(function () {
|
||||||
$('#dlg_'+me.id).dialog('open');
|
$('#dlg_'+me.id).dialog('open');
|
||||||
me.UpdateSizes(null, null);
|
me.UpdateSizes(null, null);
|
||||||
if (me.bDoSearch)
|
if (me.bDoSearch) {
|
||||||
{
|
|
||||||
me.SearchObjectsToAdd();
|
me.SearchObjectsToAdd();
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
$('#count_'+me.id).change(function () {
|
$('#count_'+me.id).change(function () {
|
||||||
let c = this.value;
|
let c = this.value;
|
||||||
me.UpdateButtons(c);
|
me.UpdateButtons(c);
|
||||||
|
|||||||
@@ -383,6 +383,7 @@ return array(
|
|||||||
'Combodo\\iTop\\Core\\Email\\EmailFactory' => $baseDir . '/sources/Core/Email/EmailFactory.php',
|
'Combodo\\iTop\\Core\\Email\\EmailFactory' => $baseDir . '/sources/Core/Email/EmailFactory.php',
|
||||||
'Combodo\\iTop\\Core\\Email\\iEMail' => $baseDir . '/sources/Core/Email/iEMail.php',
|
'Combodo\\iTop\\Core\\Email\\iEMail' => $baseDir . '/sources/Core/Email/iEMail.php',
|
||||||
'Combodo\\iTop\\Core\\EventListener\\AttributeBlobEventListener' => $baseDir . '/sources/Core/EventListener/AttributeBlobEventListener.php',
|
'Combodo\\iTop\\Core\\EventListener\\AttributeBlobEventListener' => $baseDir . '/sources/Core/EventListener/AttributeBlobEventListener.php',
|
||||||
|
'Combodo\\iTop\\Core\\Kpi\\KpiLogData' => $baseDir . '/sources/Core/Kpi/KpiLogData.php',
|
||||||
'Combodo\\iTop\\Core\\MetaModel\\FriendlyNameType' => $baseDir . '/sources/Core/MetaModel/FriendlyNameType.php',
|
'Combodo\\iTop\\Core\\MetaModel\\FriendlyNameType' => $baseDir . '/sources/Core/MetaModel/FriendlyNameType.php',
|
||||||
'Combodo\\iTop\\Core\\MetaModel\\HierarchicalKey' => $baseDir . '/sources/Core/MetaModel/HierarchicalKey.php',
|
'Combodo\\iTop\\Core\\MetaModel\\HierarchicalKey' => $baseDir . '/sources/Core/MetaModel/HierarchicalKey.php',
|
||||||
'Combodo\\iTop\\DesignDocument' => $baseDir . '/core/designdocument.class.inc.php',
|
'Combodo\\iTop\\DesignDocument' => $baseDir . '/core/designdocument.class.inc.php',
|
||||||
@@ -461,6 +462,7 @@ return array(
|
|||||||
'Combodo\\iTop\\Service\\Links\\LinkSetModel' => $baseDir . '/sources/Service/Links/LinkSetModel.php',
|
'Combodo\\iTop\\Service\\Links\\LinkSetModel' => $baseDir . '/sources/Service/Links/LinkSetModel.php',
|
||||||
'Combodo\\iTop\\Service\\Links\\LinkSetRepository' => $baseDir . '/sources/Service/Links/LinkSetRepository.php',
|
'Combodo\\iTop\\Service\\Links\\LinkSetRepository' => $baseDir . '/sources/Service/Links/LinkSetRepository.php',
|
||||||
'Combodo\\iTop\\Service\\Links\\LinksBulkDataPostProcessor' => $baseDir . '/sources/Service/Links/LinksBulkDataPostProcessor.php',
|
'Combodo\\iTop\\Service\\Links\\LinksBulkDataPostProcessor' => $baseDir . '/sources/Service/Links/LinksBulkDataPostProcessor.php',
|
||||||
|
'Combodo\\iTop\\Service\\Module\\ModuleService' => $baseDir . '/sources/Service/Module/ModuleService.php',
|
||||||
'Combodo\\iTop\\Service\\Router\\Exception\\RouteNotFoundException' => $baseDir . '/sources/Service/Router/Exception/RouteNotFoundException.php',
|
'Combodo\\iTop\\Service\\Router\\Exception\\RouteNotFoundException' => $baseDir . '/sources/Service/Router/Exception/RouteNotFoundException.php',
|
||||||
'Combodo\\iTop\\Service\\Router\\Exception\\RouterException' => $baseDir . '/sources/Service/Router/Exception/RouterException.php',
|
'Combodo\\iTop\\Service\\Router\\Exception\\RouterException' => $baseDir . '/sources/Service/Router/Exception/RouterException.php',
|
||||||
'Combodo\\iTop\\Service\\Router\\Router' => $baseDir . '/sources/Service/Router/Router.php',
|
'Combodo\\iTop\\Service\\Router\\Router' => $baseDir . '/sources/Service/Router/Router.php',
|
||||||
@@ -2952,6 +2954,7 @@ return array(
|
|||||||
'iDBObjectURLMaker' => $baseDir . '/application/applicationcontext.class.inc.php',
|
'iDBObjectURLMaker' => $baseDir . '/application/applicationcontext.class.inc.php',
|
||||||
'iDisplay' => $baseDir . '/core/dbobject.class.php',
|
'iDisplay' => $baseDir . '/core/dbobject.class.php',
|
||||||
'iFieldRendererMappingsExtension' => $baseDir . '/application/applicationextension.inc.php',
|
'iFieldRendererMappingsExtension' => $baseDir . '/application/applicationextension.inc.php',
|
||||||
|
'iKPILoggerExtension' => $baseDir . '/application/applicationextension.inc.php',
|
||||||
'iKeyboardShortcut' => $baseDir . '/sources/Application/UI/Hook/iKeyboardShortcut.php',
|
'iKeyboardShortcut' => $baseDir . '/sources/Application/UI/Hook/iKeyboardShortcut.php',
|
||||||
'iLogFileNameBuilder' => $baseDir . '/core/log.class.inc.php',
|
'iLogFileNameBuilder' => $baseDir . '/core/log.class.inc.php',
|
||||||
'iLoginExtension' => $baseDir . '/application/applicationextension.inc.php',
|
'iLoginExtension' => $baseDir . '/application/applicationextension.inc.php',
|
||||||
|
|||||||
@@ -747,6 +747,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
|||||||
'Combodo\\iTop\\Core\\Email\\EmailFactory' => __DIR__ . '/../..' . '/sources/Core/Email/EmailFactory.php',
|
'Combodo\\iTop\\Core\\Email\\EmailFactory' => __DIR__ . '/../..' . '/sources/Core/Email/EmailFactory.php',
|
||||||
'Combodo\\iTop\\Core\\Email\\iEMail' => __DIR__ . '/../..' . '/sources/Core/Email/iEMail.php',
|
'Combodo\\iTop\\Core\\Email\\iEMail' => __DIR__ . '/../..' . '/sources/Core/Email/iEMail.php',
|
||||||
'Combodo\\iTop\\Core\\EventListener\\AttributeBlobEventListener' => __DIR__ . '/../..' . '/sources/Core/EventListener/AttributeBlobEventListener.php',
|
'Combodo\\iTop\\Core\\EventListener\\AttributeBlobEventListener' => __DIR__ . '/../..' . '/sources/Core/EventListener/AttributeBlobEventListener.php',
|
||||||
|
'Combodo\\iTop\\Core\\Kpi\\KpiLogData' => __DIR__ . '/../..' . '/sources/Core/Kpi/KpiLogData.php',
|
||||||
'Combodo\\iTop\\Core\\MetaModel\\FriendlyNameType' => __DIR__ . '/../..' . '/sources/Core/MetaModel/FriendlyNameType.php',
|
'Combodo\\iTop\\Core\\MetaModel\\FriendlyNameType' => __DIR__ . '/../..' . '/sources/Core/MetaModel/FriendlyNameType.php',
|
||||||
'Combodo\\iTop\\Core\\MetaModel\\HierarchicalKey' => __DIR__ . '/../..' . '/sources/Core/MetaModel/HierarchicalKey.php',
|
'Combodo\\iTop\\Core\\MetaModel\\HierarchicalKey' => __DIR__ . '/../..' . '/sources/Core/MetaModel/HierarchicalKey.php',
|
||||||
'Combodo\\iTop\\DesignDocument' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php',
|
'Combodo\\iTop\\DesignDocument' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php',
|
||||||
@@ -825,6 +826,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
|||||||
'Combodo\\iTop\\Service\\Links\\LinkSetModel' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetModel.php',
|
'Combodo\\iTop\\Service\\Links\\LinkSetModel' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetModel.php',
|
||||||
'Combodo\\iTop\\Service\\Links\\LinkSetRepository' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetRepository.php',
|
'Combodo\\iTop\\Service\\Links\\LinkSetRepository' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetRepository.php',
|
||||||
'Combodo\\iTop\\Service\\Links\\LinksBulkDataPostProcessor' => __DIR__ . '/../..' . '/sources/Service/Links/LinksBulkDataPostProcessor.php',
|
'Combodo\\iTop\\Service\\Links\\LinksBulkDataPostProcessor' => __DIR__ . '/../..' . '/sources/Service/Links/LinksBulkDataPostProcessor.php',
|
||||||
|
'Combodo\\iTop\\Service\\Module\\ModuleService' => __DIR__ . '/../..' . '/sources/Service/Module/ModuleService.php',
|
||||||
'Combodo\\iTop\\Service\\Router\\Exception\\RouteNotFoundException' => __DIR__ . '/../..' . '/sources/Service/Router/Exception/RouteNotFoundException.php',
|
'Combodo\\iTop\\Service\\Router\\Exception\\RouteNotFoundException' => __DIR__ . '/../..' . '/sources/Service/Router/Exception/RouteNotFoundException.php',
|
||||||
'Combodo\\iTop\\Service\\Router\\Exception\\RouterException' => __DIR__ . '/../..' . '/sources/Service/Router/Exception/RouterException.php',
|
'Combodo\\iTop\\Service\\Router\\Exception\\RouterException' => __DIR__ . '/../..' . '/sources/Service/Router/Exception/RouterException.php',
|
||||||
'Combodo\\iTop\\Service\\Router\\Router' => __DIR__ . '/../..' . '/sources/Service/Router/Router.php',
|
'Combodo\\iTop\\Service\\Router\\Router' => __DIR__ . '/../..' . '/sources/Service/Router/Router.php',
|
||||||
@@ -3316,6 +3318,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
|||||||
'iDBObjectURLMaker' => __DIR__ . '/../..' . '/application/applicationcontext.class.inc.php',
|
'iDBObjectURLMaker' => __DIR__ . '/../..' . '/application/applicationcontext.class.inc.php',
|
||||||
'iDisplay' => __DIR__ . '/../..' . '/core/dbobject.class.php',
|
'iDisplay' => __DIR__ . '/../..' . '/core/dbobject.class.php',
|
||||||
'iFieldRendererMappingsExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
|
'iFieldRendererMappingsExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
|
||||||
|
'iKPILoggerExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
|
||||||
'iKeyboardShortcut' => __DIR__ . '/../..' . '/sources/Application/UI/Hook/iKeyboardShortcut.php',
|
'iKeyboardShortcut' => __DIR__ . '/../..' . '/sources/Application/UI/Hook/iKeyboardShortcut.php',
|
||||||
'iLogFileNameBuilder' => __DIR__ . '/../..' . '/core/log.class.inc.php',
|
'iLogFileNameBuilder' => __DIR__ . '/../..' . '/core/log.class.inc.php',
|
||||||
'iLoginExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
|
'iLoginExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<system.webServer>
|
<system.web>
|
||||||
<security>
|
<authorization>
|
||||||
<requestFiltering>
|
<deny users="*" /> <!-- Denies all users -->
|
||||||
<fileExtensions applyToWebDAV="false" allowUnlisted="false"></fileExtensions>
|
</authorization>
|
||||||
</requestFiltering>
|
</system.web>
|
||||||
<authorization>
|
|
||||||
<deny users="*" /> <!-- Denies all users -->
|
|
||||||
</authorization>
|
|
||||||
</security>
|
|
||||||
</system.webServer>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -105,7 +105,7 @@ if ($oFilter != null)
|
|||||||
$aExtraParams['action'] = utils::GetAbsoluteUrlAppRoot().'pages/UniversalSearch.php';
|
$aExtraParams['action'] = utils::GetAbsoluteUrlAppRoot().'pages/UniversalSearch.php';
|
||||||
$aExtraParams['table_id'] = '1';
|
$aExtraParams['table_id'] = '1';
|
||||||
$aExtraParams['search_header_force_dropdown'] = $sSearchHeaderForceDropdown;
|
$aExtraParams['search_header_force_dropdown'] = $sSearchHeaderForceDropdown;
|
||||||
//$aExtraParams['class'] = $sClassName;
|
$aExtraParams['submit_on_load'] = false;
|
||||||
$oBlock->Display($oP, 0, $aExtraParams);
|
$oBlock->Display($oP, 0, $aExtraParams);
|
||||||
|
|
||||||
// Search results
|
// Search results
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ try
|
|||||||
$aExtraParams['action'] = utils::GetAbsoluteUrlAppRoot().'pages/tagadmin.php';
|
$aExtraParams['action'] = utils::GetAbsoluteUrlAppRoot().'pages/tagadmin.php';
|
||||||
$aExtraParams['table_id'] = '1';
|
$aExtraParams['table_id'] = '1';
|
||||||
$aExtraParams['search_header_force_dropdown'] = $sSearchHeaderForceDropdown;
|
$aExtraParams['search_header_force_dropdown'] = $sSearchHeaderForceDropdown;
|
||||||
|
$aExtraParams['submit_on_load'] = false;
|
||||||
$oBlock->Display($oP, 0, $aExtraParams);
|
$oBlock->Display($oP, 0, $aExtraParams);
|
||||||
|
|
||||||
// Search results
|
// Search results
|
||||||
|
|||||||
@@ -358,6 +358,7 @@ class DBBackup
|
|||||||
|
|
||||||
$sPortOption = self::GetMysqliCliSingleOption('port', $this->iDBPort);
|
$sPortOption = self::GetMysqliCliSingleOption('port', $this->iDBPort);
|
||||||
$sTlsOptions = self::GetMysqlCliTlsOptions($this->oConfig);
|
$sTlsOptions = self::GetMysqlCliTlsOptions($this->oConfig);
|
||||||
|
$sProtocolOption = self::GetMysqlCliTransportOption($this->sDBHost);
|
||||||
|
|
||||||
$sMysqlVersion = CMDBSource::GetDBVersion();
|
$sMysqlVersion = CMDBSource::GetDBVersion();
|
||||||
$bIsMysqlSupportUtf8mb4 = (version_compare($sMysqlVersion, self::MYSQL_VERSION_WITH_UTF8MB4_IN_PROGRAMS) === -1);
|
$bIsMysqlSupportUtf8mb4 = (version_compare($sMysqlVersion, self::MYSQL_VERSION_WITH_UTF8MB4_IN_PROGRAMS) === -1);
|
||||||
@@ -378,8 +379,8 @@ EOF;
|
|||||||
|
|
||||||
// Note: opt implicitely sets lock-tables... which cancels the benefit of single-transaction!
|
// Note: opt implicitely sets lock-tables... which cancels the benefit of single-transaction!
|
||||||
// skip-lock-tables compensates and allows for writes during a backup
|
// skip-lock-tables compensates and allows for writes during a backup
|
||||||
$sCommand = "$sMySQLDump --defaults-extra-file=\"$sMySQLDumpCnfFile\" --opt --skip-lock-tables --default-character-set=".$sMysqldumpCharset." --add-drop-database --single-transaction --host=$sHost $sPortOption --user=$sUser $sTlsOptions --result-file=$sTmpFileName $sDBName $sTables 2>&1";
|
$sCommand = "$sMySQLDump --defaults-extra-file=\"$sMySQLDumpCnfFile\" --opt --skip-lock-tables --default-character-set=".$sMysqldumpCharset." --add-drop-database --single-transaction --host=$sHost $sPortOption $sProtocolOption --user=$sUser $sTlsOptions --result-file=$sTmpFileName $sDBName $sTables 2>&1";
|
||||||
$sCommandDisplay = "$sMySQLDump --defaults-extra-file=\"$sMySQLDumpCnfFile\" --opt --skip-lock-tables --default-character-set=".$sMysqldumpCharset." --add-drop-database --single-transaction --host=$sHost $sPortOption --user=xxxxx $sTlsOptions --result-file=$sTmpFileName $sDBName $sTables";
|
$sCommandDisplay = "$sMySQLDump --defaults-extra-file=\"$sMySQLDumpCnfFile\" --opt --skip-lock-tables --default-character-set=".$sMysqldumpCharset." --add-drop-database --single-transaction --host=$sHost $sPortOption $sProtocolOption --user=xxxxx $sTlsOptions --result-file=$sTmpFileName $sDBName $sTables";
|
||||||
|
|
||||||
// Now run the command for real
|
// Now run the command for real
|
||||||
$this->LogInfo("backup: generate data file with command: $sCommandDisplay");
|
$this->LogInfo("backup: generate data file with command: $sCommandDisplay");
|
||||||
@@ -467,8 +468,8 @@ EOF;
|
|||||||
if ($oMysqli->connect_errno)
|
if ($oMysqli->connect_errno)
|
||||||
{
|
{
|
||||||
$sHost = is_null($this->iDBPort) ? $this->sDBHost : $this->sDBHost.' on port '.$this->iDBPort;
|
$sHost = is_null($this->iDBPort) ? $this->sDBHost : $this->sDBHost.' on port '.$this->iDBPort;
|
||||||
throw new BackupException("Cannot connect to the MySQL server '$sHost' (".$oMysqli->connect_errno.") ".$oMysqli->connect_error);
|
throw new MySQLException('Could not connect to the DB server '.$oMysqli->connect_errno.' (mysql errno: '.$oMysqli->connect_error, array('host' => $sHost, 'user' => $sUser));
|
||||||
}
|
}
|
||||||
if (!$oMysqli->select_db($this->sDBName))
|
if (!$oMysqli->select_db($this->sDBName))
|
||||||
{
|
{
|
||||||
throw new BackupException("The database '$this->sDBName' does not seem to exist");
|
throw new BackupException("The database '$this->sDBName' does not seem to exist");
|
||||||
@@ -576,6 +577,28 @@ EOF;
|
|||||||
return ' --'.$sCliArgName.'='.self::EscapeShellArg($sData);
|
return ' --'.$sCliArgName.'='.self::EscapeShellArg($sData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define if we should force a transport option
|
||||||
|
*
|
||||||
|
* @param string $sHost
|
||||||
|
*
|
||||||
|
* @return string .
|
||||||
|
|
||||||
|
* @since 2.7.9 3.0.4 3.1.1 N°6123
|
||||||
|
*/
|
||||||
|
public static function GetMysqlCliTransportOption(string $sHost)
|
||||||
|
{
|
||||||
|
$sTransportOptions = '';
|
||||||
|
|
||||||
|
/** N°6123 As we're using a --port option, if we use localhost as host,
|
||||||
|
* MariaDB > 10.6 will implicitly change its protocol from socket to tcp and throw a warning **/
|
||||||
|
if($sHost === 'localhost'){
|
||||||
|
$sTransportOptions = '--protocol=tcp';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sTransportOptions;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string the command to launch mysqldump (without its params)
|
* @return string the command to launch mysqldump (without its params)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2055,7 +2055,6 @@ EOF
|
|||||||
$this->CompileCommonProperty('display_style', $oField, $aParameters, $sModuleRelativeDir);
|
$this->CompileCommonProperty('display_style', $oField, $aParameters, $sModuleRelativeDir);
|
||||||
$this->CompileCommonProperty('edit_mode', $oField, $aParameters, $sModuleRelativeDir);
|
$this->CompileCommonProperty('edit_mode', $oField, $aParameters, $sModuleRelativeDir);
|
||||||
$this->CompileCommonProperty('filter', $oField, $aParameters, $sModuleRelativeDir);
|
$this->CompileCommonProperty('filter', $oField, $aParameters, $sModuleRelativeDir);
|
||||||
$this->CompileCommonProperty('allowed_values', $oField, $aParameters, $sModuleRelativeDir);
|
|
||||||
$this->CompileCommonProperty('with_php_constraint', $oField, $aParameters, $sModuleRelativeDir, false);
|
$this->CompileCommonProperty('with_php_constraint', $oField, $aParameters, $sModuleRelativeDir, false);
|
||||||
$aParameters['depends_on'] = $sDependencies;
|
$aParameters['depends_on'] = $sDependencies;
|
||||||
} elseif ($sAttType == 'AttributeLinkedSet') {
|
} elseif ($sAttType == 'AttributeLinkedSet') {
|
||||||
|
|||||||
@@ -892,7 +892,10 @@ class iTopDesignFormat
|
|||||||
$oNodeList = $oXPath->query("/itop_design/classes//class/fields/field/values/value");
|
$oNodeList = $oXPath->query("/itop_design/classes//class/fields/field/values/value");
|
||||||
foreach ($oNodeList as $oNode) {
|
foreach ($oNodeList as $oNode) {
|
||||||
$sCode = $oNode->textContent;
|
$sCode = $oNode->textContent;
|
||||||
$oNode->textContent = '';
|
// N°6562 textContent is readonly, see https://www.php.net/manual/en/class.domnode.php#95545
|
||||||
|
// $oNode->textContent = '';
|
||||||
|
// N°6562 to update text node content we must use the node methods !
|
||||||
|
$oNode->removeChild($oNode->firstChild);
|
||||||
$oCodeNode = $oNode->ownerDocument->createElement("code", $sCode);
|
$oCodeNode = $oNode->ownerDocument->createElement("code", $sCode);
|
||||||
$oNode->appendChild($oCodeNode);
|
$oNode->appendChild($oCodeNode);
|
||||||
}
|
}
|
||||||
@@ -982,7 +985,14 @@ class iTopDesignFormat
|
|||||||
if ($oStyleNode) {
|
if ($oStyleNode) {
|
||||||
$this->DeleteNode($oStyleNode);
|
$this->DeleteNode($oStyleNode);
|
||||||
}
|
}
|
||||||
$oNode->textContent = $sCode;
|
|
||||||
|
// N°6562 textContent is readonly, see https://www.php.net/manual/en/class.domnode.php#95545
|
||||||
|
// $oNode->textContent = $sCode;
|
||||||
|
// N°6562 to update text node content we must use the node methods !
|
||||||
|
// we are using DOMDocument::createTextNode instead of new DOMText because elements created using the constructor are read only
|
||||||
|
// see https://www.php.net/manual/en/domelement.construct.php
|
||||||
|
$oTextContentNode = $this->oDocument->createTextNode($sCode);
|
||||||
|
$oNode->appendChild($oTextContentNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// - Style
|
// - Style
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ namespace Combodo\iTop\Application\UI\Base\Component\Dashlet;
|
|||||||
|
|
||||||
|
|
||||||
use Combodo\iTop\Application\UI\Base\tJSRefreshCallback;
|
use Combodo\iTop\Application\UI\Base\tJSRefreshCallback;
|
||||||
|
use utils;
|
||||||
|
|
||||||
class DashletBadge extends DashletContainer
|
class DashletBadge extends DashletContainer
|
||||||
{
|
{
|
||||||
@@ -29,6 +30,11 @@ class DashletBadge extends DashletContainer
|
|||||||
protected $iCount;
|
protected $iCount;
|
||||||
/** @var string */
|
/** @var string */
|
||||||
protected $sClassLabel;
|
protected $sClassLabel;
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
* @since 3.1.1 3.2.0
|
||||||
|
*/
|
||||||
|
protected $sClassDescription;
|
||||||
|
|
||||||
/** @var string */
|
/** @var string */
|
||||||
protected $sCreateActionUrl;
|
protected $sCreateActionUrl;
|
||||||
@@ -62,6 +68,7 @@ class DashletBadge extends DashletContainer
|
|||||||
$this->sCreateActionUrl = $sCreateActionUrl;
|
$this->sCreateActionUrl = $sCreateActionUrl;
|
||||||
$this->sCreateActionLabel = $sCreateActionLabel;
|
$this->sCreateActionLabel = $sCreateActionLabel;
|
||||||
$this->aRefreshParams = $aRefreshParams;
|
$this->aRefreshParams = $aRefreshParams;
|
||||||
|
$this->sClassDescription = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -185,6 +192,37 @@ class DashletBadge extends DashletContainer
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
* @since 3.1.1 3.2.0
|
||||||
|
*/
|
||||||
|
public function GetClassDescription(): string
|
||||||
|
{
|
||||||
|
return $this->sClassDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sClassDescription
|
||||||
|
*
|
||||||
|
* @return DashletBadge
|
||||||
|
* @since 3.1.1 3.2.0
|
||||||
|
*/
|
||||||
|
public function SetClassDescription(string $sClassDescription)
|
||||||
|
{
|
||||||
|
$this->sClassDescription = $sClassDescription;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
* @since 3.1.1
|
||||||
|
*/
|
||||||
|
public function HasClassDescription(): bool
|
||||||
|
{
|
||||||
|
return utils::IsNotNullOrEmptyString($this->sClassDescription);
|
||||||
|
}
|
||||||
|
|
||||||
public function GetJSRefresh(): string
|
public function GetJSRefresh(): string
|
||||||
{
|
{
|
||||||
return "$('#".$this->sId."').block();
|
return "$('#".$this->sId."').block();
|
||||||
|
|||||||
@@ -55,11 +55,13 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
|||||||
public const UI_BLOCK_CLASS_NAME = DataTable::class;
|
public const UI_BLOCK_CLASS_NAME = DataTable::class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* If inside an iTop object, you can use {@see cmdbAbstractObject::DisplaySet()}
|
||||||
|
*
|
||||||
* @api
|
* @api
|
||||||
* @param \WebPage $oPage
|
* @param \WebPage $oPage
|
||||||
* @param string $sListId
|
* @param string $sListId
|
||||||
* @param \DBObjectSet $oSet
|
* @param \DBObjectSet $oSet
|
||||||
* @param array $aExtraParams
|
* @param array $aExtraParams See possible values in {@see self::RenderDataTable()}
|
||||||
*
|
*
|
||||||
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
|
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
|
||||||
* @throws \ApplicationException
|
* @throws \ApplicationException
|
||||||
@@ -84,11 +86,13 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* If inside an iTop object, you can use {@see cmdbAbstractObject::DisplaySet()}
|
||||||
|
*
|
||||||
* @api
|
* @api
|
||||||
* @param \WebPage $oPage
|
* @param \WebPage $oPage
|
||||||
* @param string $sListId
|
* @param string $sListId
|
||||||
* @param DBObjectSet $oSet
|
* @param DBObjectSet $oSet
|
||||||
* @param array $aExtraParams
|
* @param array $aExtraParams See possible values in {@see self::RenderDataTable()}
|
||||||
*
|
*
|
||||||
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
|
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
|
||||||
* @throws \ArchivedObjectException
|
* @throws \ArchivedObjectException
|
||||||
@@ -117,7 +121,12 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
|||||||
* @param \WebPage $oPage
|
* @param \WebPage $oPage
|
||||||
* @param string $sListId
|
* @param string $sListId
|
||||||
* @param \DBObjectSet $oSet
|
* @param \DBObjectSet $oSet
|
||||||
* @param array $aExtraParams
|
* @param array $aExtraParams example keys used in this method :
|
||||||
|
* - toolkit_menu = boolean
|
||||||
|
* - surround_with_panel = boolean : if true adds the standard class panel (icon, title, ...)
|
||||||
|
* - panel_title = string
|
||||||
|
* - panel_title_is_html = boolean
|
||||||
|
* - panel_icon = string : class icon (for example from {@see MetaModel::GetClassIcon()})
|
||||||
*
|
*
|
||||||
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
|
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
|
||||||
* @throws \ArchivedObjectException
|
* @throws \ArchivedObjectException
|
||||||
|
|||||||
@@ -70,6 +70,9 @@ abstract class AbstractBlockLinkSetViewTable extends UIContentBlock
|
|||||||
/** @var AttributeLinkedSet $oAttDef attribute link set */
|
/** @var AttributeLinkedSet $oAttDef attribute link set */
|
||||||
protected AttributeLinkedSet $oAttDef;
|
protected AttributeLinkedSet $oAttDef;
|
||||||
|
|
||||||
|
/** @var bool $bIsAttEditable Is attribute editable */
|
||||||
|
protected bool $bIsAttEditable;
|
||||||
|
|
||||||
/** @var string $sTargetClass links target classname */
|
/** @var string $sTargetClass links target classname */
|
||||||
protected string $sTargetClass;
|
protected string $sTargetClass;
|
||||||
|
|
||||||
@@ -119,11 +122,12 @@ abstract class AbstractBlockLinkSetViewTable extends UIContentBlock
|
|||||||
private function Init()
|
private function Init()
|
||||||
{
|
{
|
||||||
$this->sTargetClass = $this->GetTargetClass();
|
$this->sTargetClass = $this->GetTargetClass();
|
||||||
|
$this->InitIsAttEditable();
|
||||||
|
|
||||||
// User rights
|
// User rights
|
||||||
$this->bIsAllowCreate = UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_CREATE) == UR_ALLOWED_YES;
|
$this->bIsAllowCreate = $this->bIsAttEditable && UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_CREATE) == UR_ALLOWED_YES;
|
||||||
$this->bIsAllowModify = UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_MODIFY) == UR_ALLOWED_YES;
|
$this->bIsAllowModify = $this->bIsAttEditable && UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_MODIFY) == UR_ALLOWED_YES;
|
||||||
$this->bIsAllowDelete = UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_DELETE) == UR_ALLOWED_YES;
|
$this->bIsAllowDelete = $this->bIsAttEditable && UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_DELETE) == UR_ALLOWED_YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -196,6 +200,26 @@ abstract class AbstractBlockLinkSetViewTable extends UIContentBlock
|
|||||||
$this->AddSubBlock($oBlock->GetRenderContent($oPage, $this->GetExtraParam(), $this->sTableId));
|
$this->AddSubBlock($oBlock->GetRenderContent($oPage, $this->GetExtraParam(), $this->sTableId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
* @throws \CoreException
|
||||||
|
*/
|
||||||
|
private function InitIsAttEditable(): void
|
||||||
|
{
|
||||||
|
$iFlags = 0;
|
||||||
|
|
||||||
|
if ($this->oDbObject->IsNew())
|
||||||
|
{
|
||||||
|
$iFlags = $this->oDbObject->GetInitialStateAttributeFlags($this->sAttCode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$iFlags = $this->oDbObject->GetAttributeFlags($this->sAttCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->bIsAttEditable = !($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE | OPT_ATT_HIDDEN));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GetTableId.
|
* GetTableId.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -733,6 +733,7 @@ class AjaxRenderController
|
|||||||
} else {
|
} else {
|
||||||
$oFullSetFilter = new DBObjectSearch($sRemoteClass);
|
$oFullSetFilter = new DBObjectSearch($sRemoteClass);
|
||||||
}
|
}
|
||||||
|
$oFullSetFilter->SetShowObsoleteData(utils::ShowObsoleteData());
|
||||||
$oWidget->DoAddObjects($oPage, $iMaxAddedId, $oFullSetFilter, $oObj);
|
$oWidget->DoAddObjects($oPage, $iMaxAddedId, $oFullSetFilter, $oObj);
|
||||||
$oKPI->ComputeAndReport('Data write');
|
$oKPI->ComputeAndReport('Data write');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ class OAuthClientProviderFactory
|
|||||||
* @return AccessTokenInterface
|
* @return AccessTokenInterface
|
||||||
* @throws \ArchivedObjectException
|
* @throws \ArchivedObjectException
|
||||||
* @throws \CoreException
|
* @throws \CoreException
|
||||||
|
* @throws \League\OAuth2\Client\Provider\Exception\IdentityProviderException
|
||||||
*/
|
*/
|
||||||
public static function GetAccessTokenFromCode(OAuthClient $oOAuthClient, $sCode)
|
public static function GetAccessTokenFromCode(OAuthClient $oOAuthClient, $sCode)
|
||||||
{
|
{
|
||||||
@@ -109,7 +110,7 @@ class OAuthClientProviderFactory
|
|||||||
/**
|
/**
|
||||||
* @param \DBObject $oOAuthClient
|
* @param \DBObject $oOAuthClient
|
||||||
*
|
*
|
||||||
* @return mixed
|
* @return OAuthClientProviderAbstract
|
||||||
* @throws \ArchivedObjectException
|
* @throws \ArchivedObjectException
|
||||||
* @throws \CoreException
|
* @throws \CoreException
|
||||||
*/
|
*/
|
||||||
|
|||||||
125
sources/Core/Kpi/KpiLogData.php
Normal file
125
sources/Core/Kpi/KpiLogData.php
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||||
|
* @license http://opensource.org/licenses/AGPL-3.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Combodo\iTop\Core\Kpi;
|
||||||
|
|
||||||
|
class KpiLogData
|
||||||
|
{
|
||||||
|
const TYPE_REPORT = 'report';
|
||||||
|
const TYPE_STATS = 'stats';
|
||||||
|
const TYPE_REQUEST = 'request';
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
public $sType;
|
||||||
|
/** @var string */
|
||||||
|
public $sOperation;
|
||||||
|
/** @var string */
|
||||||
|
public $sArguments;
|
||||||
|
/** @var float */
|
||||||
|
public $fStartTime;
|
||||||
|
/** @var float */
|
||||||
|
public $fStopTime;
|
||||||
|
/** @var string */
|
||||||
|
public $sExtension;
|
||||||
|
/** @var int */
|
||||||
|
public $iInitialMemory;
|
||||||
|
/** @var int */
|
||||||
|
public $iCurrentMemory;
|
||||||
|
/** @var int */
|
||||||
|
public $iPeakMemory;
|
||||||
|
/** @var array */
|
||||||
|
public $aData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sType
|
||||||
|
* @param string $sOperation
|
||||||
|
* @param string $sArguments
|
||||||
|
* @param float $fStartTime
|
||||||
|
* @param float $fStopTime
|
||||||
|
* @param string $sExtension
|
||||||
|
* @param int $iInitialMemory
|
||||||
|
* @param int $iCurrentMemory
|
||||||
|
* @param array $aData
|
||||||
|
*/
|
||||||
|
public function __construct($sType, $sOperation, $sArguments, $fStartTime, $fStopTime, $sExtension, $iInitialMemory = 0, $iCurrentMemory = 0, $iPeakMemory = 0, $aData = [])
|
||||||
|
{
|
||||||
|
$this->sType = $sType;
|
||||||
|
$this->sOperation = $sOperation;
|
||||||
|
$this->sArguments = @iconv(mb_detect_encoding($sArguments, mb_detect_order(), true), 'UTF-8', $sArguments);
|
||||||
|
$this->fStartTime = $fStartTime;
|
||||||
|
$this->fStopTime = $fStopTime;
|
||||||
|
$this->sExtension = $sExtension;
|
||||||
|
$this->iInitialMemory = $iInitialMemory;
|
||||||
|
$this->iCurrentMemory = $iCurrentMemory;
|
||||||
|
$this->iPeakMemory = $iPeakMemory;
|
||||||
|
$this->aData = $aData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the CSV Header
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function GetCSVHeader()
|
||||||
|
{
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
|
||||||
|
private function RemoveQuotes(string $sEntry): string
|
||||||
|
{
|
||||||
|
return str_replace('"', "'", $sEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Combodo\iTop\Core\Kpi\KpiLogData $oOther
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
public function Compare(KpiLogData $oOther): float
|
||||||
|
{
|
||||||
|
if ($oOther->fStartTime > $this->fStartTime) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Contains(KpiLogData $oOther): bool
|
||||||
|
{
|
||||||
|
if ($oOther->fStartTime < $this->fStartTime) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($oOther->fStartTime > $this->fStopTime) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
return "$this->sType:$this->sOperation:$this->sArguments";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetUUID(): string
|
||||||
|
{
|
||||||
|
return sha1($this->__toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -320,12 +320,19 @@ EOF
|
|||||||
if ($this->oField->GetCurrentValue() !== null && $this->oField->GetCurrentValue() !== 0 && $this->oField->GetCurrentValue() !== '')
|
if ($this->oField->GetCurrentValue() !== null && $this->oField->GetCurrentValue() !== 0 && $this->oField->GetCurrentValue() !== '')
|
||||||
{
|
{
|
||||||
// Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
|
// Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
|
||||||
$oFieldValue = MetaModel::GetObject($sFieldValueClass, $this->oField->GetCurrentValue(), true, true);
|
$oFieldValue = MetaModel::GetObjectWithArchive($sFieldValueClass, $this->oField->GetCurrentValue(), true, true);
|
||||||
$sFieldHtmlValue = $oFieldValue->GetName();
|
$sFieldHtmlValue = $oFieldValue->GetName();
|
||||||
$sFieldUrl = ApplicationContext::MakeObjectUrl($sFieldValueClass, $this->oField->GetCurrentValue());
|
if($oFieldValue->IsArchived())
|
||||||
if(!empty($sFieldUrl))
|
|
||||||
{
|
{
|
||||||
$sFieldHtmlValue = '<a href="'.$sFieldUrl.'" data-toggle="itop-portal-modal">'.$sFieldHtmlValue.'</a>';
|
$sFieldHtmlValue = '<span class="text_decoration"><span class="fas fa-archive"></span></span>' . $sFieldHtmlValue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$sFieldUrl = ApplicationContext::MakeObjectUrl($sFieldValueClass, $this->oField->GetCurrentValue());
|
||||||
|
if (!empty($sFieldUrl))
|
||||||
|
{
|
||||||
|
$sFieldHtmlValue = '<a href="' . $sFieldUrl . '" data-toggle="itop-portal-modal">' . $sFieldHtmlValue . '</a>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
214
sources/Service/Module/ModuleService.php
Normal file
214
sources/Service/Module/ModuleService.php
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||||
|
* @license http://opensource.org/licenses/AGPL-3.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Combodo\iTop\Service\Module;
|
||||||
|
|
||||||
|
use MetaModel;
|
||||||
|
use ReflectionClass;
|
||||||
|
use ReflectionMethod;
|
||||||
|
use utils;
|
||||||
|
|
||||||
|
class ModuleService
|
||||||
|
{
|
||||||
|
/** @var ModuleService */
|
||||||
|
private static $oInstance;
|
||||||
|
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function GetInstance(): ModuleService
|
||||||
|
{
|
||||||
|
if (!isset(static::$oInstance)) {
|
||||||
|
static::$oInstance = new ModuleService();
|
||||||
|
}
|
||||||
|
|
||||||
|
return static::$oInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a "signature" of the method of an extension in the form of: "[module-name] class::method()"
|
||||||
|
*
|
||||||
|
* @param object|string $object Object or class
|
||||||
|
* @param string $sMethod
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* @throws \ReflectionException
|
||||||
|
*/
|
||||||
|
public function GetModuleMethodSignature($object, string $sMethod): string
|
||||||
|
{
|
||||||
|
$sSignature = '';
|
||||||
|
$oReflectionMethod = new ReflectionMethod($object, $sMethod);
|
||||||
|
$oReflectionClass = $oReflectionMethod->getDeclaringClass();
|
||||||
|
$sExtension = $this->GetModuleNameFromObject($oReflectionClass->getName());
|
||||||
|
if (strlen($sExtension) !== 0) {
|
||||||
|
$sSignature .= '['.$sExtension.'] ';
|
||||||
|
}
|
||||||
|
$sSignature .= $oReflectionClass->getShortName().'::'.$sMethod.'()';
|
||||||
|
|
||||||
|
return $sSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the module name from an object or class
|
||||||
|
*
|
||||||
|
* @param object|string $object
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* @throws \ReflectionException
|
||||||
|
*/
|
||||||
|
public function GetModuleNameFromObject($object): string
|
||||||
|
{
|
||||||
|
$oReflectionClass = new ReflectionClass($object);
|
||||||
|
$sPath = str_replace('\\', '/', $oReflectionClass->getFileName());
|
||||||
|
$sPattern = str_replace('\\', '/', '@'.APPROOT.'env-'.utils::GetCurrentEnvironment()).'/(?<ext>.+)/@U';
|
||||||
|
if (preg_match($sPattern, $sPath, $aMatches) !== false) {
|
||||||
|
if (isset($aMatches['ext'])) {
|
||||||
|
return $aMatches['ext'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **Warning** : returned result can be invalid as we're using backtrace to find the module dir name
|
||||||
|
*
|
||||||
|
* @param int $iCallDepth The depth of the module in the callstack. Zero when called directly from within the module
|
||||||
|
*
|
||||||
|
* @return string the relative (to MODULESROOT) path of the root directory of the module containing the file where the call to
|
||||||
|
* this function is made
|
||||||
|
* or an empty string if no such module is found (or not called within a module file)
|
||||||
|
*
|
||||||
|
* @uses \debug_backtrace()
|
||||||
|
*/
|
||||||
|
public function GetCurrentModuleDir(int $iCallDepth): string
|
||||||
|
{
|
||||||
|
$sCurrentModuleDir = '';
|
||||||
|
$aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||||
|
$sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
|
||||||
|
|
||||||
|
foreach(GetModulesInfo() as $sModuleName => $aInfo)
|
||||||
|
{
|
||||||
|
if ($aInfo['root_dir'] !== '')
|
||||||
|
{
|
||||||
|
$sRootDir = realpath(APPROOT.$aInfo['root_dir']);
|
||||||
|
|
||||||
|
if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
|
||||||
|
{
|
||||||
|
$sCurrentModuleDir = basename($sRootDir);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $sCurrentModuleDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **Warning** : as this method uses {@see GetCurrentModuleDir} it produces hazardous results.
|
||||||
|
* You should better uses directly {@see GetAbsoluteUrlModulesRoot} and add the module dir name yourself ! See N°4573
|
||||||
|
*
|
||||||
|
* @return string the base URL for all files in the current module from which this method is called
|
||||||
|
* or an empty string if no such module is found (or not called within a module file)
|
||||||
|
* @throws \Exception
|
||||||
|
*
|
||||||
|
* @uses GetCurrentModuleDir
|
||||||
|
*/
|
||||||
|
public function GetCurrentModuleUrl(int $iCallDepth = 0): string
|
||||||
|
{
|
||||||
|
$sDir = $this->GetCurrentModuleDir(1 + $iCallDepth);
|
||||||
|
if ( $sDir !== '')
|
||||||
|
{
|
||||||
|
return utils::GetAbsoluteUrlModulesRoot().'/'.$sDir;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sProperty The name of the property to retrieve
|
||||||
|
* @param mixed $defaultValue
|
||||||
|
*
|
||||||
|
* @return mixed the value of a given setting for the current module
|
||||||
|
*/
|
||||||
|
public function GetCurrentModuleSetting(string $sProperty, $defaultValue = null)
|
||||||
|
{
|
||||||
|
$sModuleName = $this->GetCurrentModuleName(1);
|
||||||
|
return MetaModel::GetModuleSetting($sModuleName, $sProperty, $defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sModuleName
|
||||||
|
*
|
||||||
|
* @return string|NULL compiled version of a given module, as it was seen by the compiler
|
||||||
|
*/
|
||||||
|
public function GetCompiledModuleVersion(string $sModuleName): ?string
|
||||||
|
{
|
||||||
|
$aModulesInfo = GetModulesInfo();
|
||||||
|
if (array_key_exists($sModuleName, $aModulesInfo))
|
||||||
|
{
|
||||||
|
return $aModulesInfo[$sModuleName]['version'];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the module containing the file where the call to this function is made
|
||||||
|
* or an empty string if no such module is found (or not called within a module file)
|
||||||
|
*
|
||||||
|
* @param int $iCallDepth The depth of the module in the callstack. Zero when called directly from within the module
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function GetCurrentModuleName(int $iCallDepth = 0): string
|
||||||
|
{
|
||||||
|
$sCurrentModuleName = '';
|
||||||
|
$aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||||
|
$sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
|
||||||
|
|
||||||
|
return $this->GetModuleNameFromPath($sCallerFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function GetModuleNameFromPath($sPath)
|
||||||
|
{
|
||||||
|
foreach (GetModulesInfo() as $sModuleName => $aInfo) {
|
||||||
|
if ($aInfo['root_dir'] !== '') {
|
||||||
|
$sRootDir = realpath(APPROOT.$aInfo['root_dir']);
|
||||||
|
if (substr($sPath, 0, strlen($sRootDir)) === $sRootDir) {
|
||||||
|
|
||||||
|
return $sModuleName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the extension code from the call stack.
|
||||||
|
* Scan the call stack until a module is found.
|
||||||
|
*
|
||||||
|
* @param int $iLevelsToIgnore
|
||||||
|
*
|
||||||
|
* @return string module name
|
||||||
|
*/
|
||||||
|
public function GetModuleNameFromCallStack(int $iLevelsToIgnore = 0): string
|
||||||
|
{
|
||||||
|
$aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||||
|
$aCallStack = array_slice($aCallStack, $iLevelsToIgnore);
|
||||||
|
|
||||||
|
foreach ($aCallStack as $aCallInfo) {
|
||||||
|
$sFile = realpath(empty($aCallInfo['file']) ? '' : $aCallInfo['file']);
|
||||||
|
|
||||||
|
$sModuleName = $this->GetModuleNameFromPath($sFile);
|
||||||
|
if (strlen($sModuleName) > 0) {
|
||||||
|
return $sModuleName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2162,7 +2162,9 @@ class SynchroReplica extends DBObject implements iDisplay
|
|||||||
// it will be deleted by the mean of a trigger too
|
// it will be deleted by the mean of a trigger too
|
||||||
protected function DBDeleteSingleObject()
|
protected function DBDeleteSingleObject()
|
||||||
{
|
{
|
||||||
|
$oKPI = new ExecutionKPI();
|
||||||
$this->OnDelete();
|
$this->OnDelete();
|
||||||
|
$oKPI->ComputeStatsForExtension($this, 'OnDelete');
|
||||||
|
|
||||||
if (!MetaModel::DBIsReadOnly())
|
if (!MetaModel::DBIsReadOnly())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,7 +3,16 @@
|
|||||||
{% apply spaceless %}
|
{% apply spaceless %}
|
||||||
<div class="ibo-dashlet-badge--body{% if oUIBlock.IsHidden() %} ibo-is-hidden{% endif %}" id="{{ oUIBlock.GetId() }}"
|
<div class="ibo-dashlet-badge--body{% if oUIBlock.IsHidden() %} ibo-is-hidden{% endif %}" id="{{ oUIBlock.GetId() }}"
|
||||||
data-role="ibo-dashlet-badge--body"
|
data-role="ibo-dashlet-badge--body"
|
||||||
data-tooltip-content="{{ oUIBlock.GetClassLabel() }}">
|
{% if oUIBlock.HasClassDescription() %}
|
||||||
|
{# Display both class name and description as the name could be truncated if too long #}
|
||||||
|
data-tooltip-content="{{ '<div class="ibo-dashlet-badge--body--tooltip-title">'|escape }}{{ oUIBlock.GetClassLabel() }}{{ '</div><div class="ibo-dashlet-badge--body--tooltip-description">'|escape }}{{ oUIBlock.GetClassDescription() }}{{ '</div>'|escape }}"
|
||||||
|
data-tooltip-html-enabled="true"
|
||||||
|
{% else %}
|
||||||
|
{# Display only class name as it could be truncated if too long #}
|
||||||
|
data-tooltip-content="{{ oUIBlock.GetClassLabel() }}"
|
||||||
|
{% endif %}
|
||||||
|
{# Delay display to avoid having all tooltips appearing when mouse is just passing through the tabs #}
|
||||||
|
data-tooltip-show-delay="300">
|
||||||
<div class="ibo-dashlet-badge--icon-container">
|
<div class="ibo-dashlet-badge--icon-container">
|
||||||
{# Mind the empty "alt" attribute https://www.w3.org/WAI/tutorials/images/decorative/ #}
|
{# Mind the empty "alt" attribute https://www.w3.org/WAI/tutorials/images/decorative/ #}
|
||||||
<img class="ibo-dashlet-badge--icon" src="{{ oUIBlock.GetClassIconUrl() }}" alt="">
|
<img class="ibo-dashlet-badge--icon" src="{{ oUIBlock.GetClassIconUrl() }}" alt="">
|
||||||
|
|||||||
@@ -23,16 +23,16 @@
|
|||||||
{% if aAction.confirmation is defined %}
|
{% if aAction.confirmation is defined %}
|
||||||
|
|
||||||
// Prepare confirmation title
|
// Prepare confirmation title
|
||||||
let sTitle = `{{ 'UI:Datatables:RowActions:ConfirmationDialog'|dict_s|raw }}`;
|
let sTitle = '{{ 'UI:Datatables:RowActions:ConfirmationDialog'|dict_s }}';
|
||||||
{% if aAction.confirmation.title is defined %}
|
{% if aAction.confirmation.title is defined %}
|
||||||
sTitle = `{{ aAction.confirmation.title|dict_s|raw }}`;
|
sTitle = '{{ aAction.confirmation.title|dict_s }}';
|
||||||
{% endif %}
|
{% endif %}
|
||||||
sTitle = sTitle.replaceAll('{item}', aRowData['{{ aAction.confirmation.row_data }}']);
|
sTitle = sTitle.replaceAll('{item}', aRowData['{{ aAction.confirmation.row_data }}']);
|
||||||
|
|
||||||
// Prepare confirmation message
|
// Prepare confirmation message
|
||||||
let sMessage = `{{ 'UI:Datatables:RowActions:ConfirmationMessage'|dict_s|raw }}`;
|
let sMessage = '{{ 'UI:Datatables:RowActions:ConfirmationMessage'|dict_s }}';
|
||||||
{% if aAction.confirmation.message is defined %}
|
{% if aAction.confirmation.message is defined %}
|
||||||
sMessage = `{{ aAction.confirmation.message|dict_s|raw }}`;
|
sMessage = '{{ aAction.confirmation.message|dict_s }}';
|
||||||
{% endif %}
|
{% endif %}
|
||||||
sMessage = sMessage.replaceAll('{item}', aRowData['{{ aAction.confirmation.row_data }}']);
|
sMessage = sMessage.replaceAll('{item}', aRowData['{{ aAction.confirmation.row_data }}']);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
[infra]
|
||||||
|
php_version=7.4-apache
|
||||||
|
; N°6629 perf bug on some tests on mariadb for now, so specifying MySQL
|
||||||
|
db_version=5.7
|
||||||
|
|
||||||
[itop]
|
[itop]
|
||||||
itop_setup=tests/setup_params/default-params.xml
|
itop_setup=tests/setup_params/default-params.xml
|
||||||
itop_backup=tests/backups/backup-itop.tar.gz
|
itop_backup=tests/backups/backup-itop.tar.gz
|
||||||
|
|||||||
@@ -1,7 +1,122 @@
|
|||||||
# PHP unitary tests
|
# PHP unitary tests
|
||||||
|
|
||||||
## Where should I add my test?
|
## Where should I add my test?
|
||||||
|
|
||||||
- Covers an iTop PHP class or method?
|
- Covers an iTop PHP class or method?
|
||||||
- Most likely in "unitary-tests".
|
- Most likely in "unitary-tests".
|
||||||
- Covers the consistency of some data through the app?
|
- Covers the consistency of some data through the app?
|
||||||
- Most likely in "integration-tests".
|
- Most likely in "integration-tests".
|
||||||
|
|
||||||
|
## How do I make sure that my tests are efficient?
|
||||||
|
|
||||||
|
|
||||||
|
### Derive from the relevant test class
|
||||||
|
|
||||||
|
Whenever possible keep it the most simple, hence you should first
|
||||||
|
attempt to derive from `TestCase`.
|
||||||
|
|
||||||
|
Then, you might need to derive from `ItopTestCase`.
|
||||||
|
|
||||||
|
Finally, as a last resort, you will use `ItopDataTestCase`.
|
||||||
|
|
||||||
|
### Determine the most relevant isolation configuration
|
||||||
|
|
||||||
|
Should you have opted for `ItopDataTestCase`, then you will have to follow these steps:
|
||||||
|
|
||||||
|
1) Build you test class until it is successfull, without process isolation.
|
||||||
|
2) Run the whole test suite [unitary-tests](unitary-tests)
|
||||||
|
3) If a false-positive appears, then you will start troubleshooting. One advise: be positive!
|
||||||
|
|
||||||
|
### Leave the place clean
|
||||||
|
|
||||||
|
To check your code against polluting coding patterns, run the test [integration-tests/DetectStaticPollutionTest.php](integration-tests/DetectStaticPollutionTest.php)
|
||||||
|
It will tell you if something is wrong, either in your code, or anywhere else in the tests.
|
||||||
|
Fortunately, it will give you an alternative.
|
||||||
|
|
||||||
|
Detected patterns:
|
||||||
|
* ContextTag::addTag()
|
||||||
|
* EventService::RegisterListener()
|
||||||
|
* Dict::Add()
|
||||||
|
|
||||||
|
|
||||||
|
By the way, some patterns do not pollute, because they are handled by the test framework:
|
||||||
|
* Configuration : automatically reset after test class execution
|
||||||
|
* UserRights : a logoff is performed after each test execution
|
||||||
|
* Dict::SetUserLanguage: the user language is reset after each test execution
|
||||||
|
|
||||||
|
See also `@beforeClass` and `@afterClass` to handle cleanup.
|
||||||
|
|
||||||
|
If you can't, then ok you will have to isolate it!
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
### Memory limit
|
||||||
|
|
||||||
|
As the tests are run in the same process, memory usage
|
||||||
|
may become an issue as soon as tests are all executed at once.
|
||||||
|
|
||||||
|
Fix that in the XML configuration in the PHP section
|
||||||
|
```xml
|
||||||
|
<ini name="memory_limit" value="512M"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Understand tests interactions
|
||||||
|
|
||||||
|
With PHPStorm, select two tests, right click to get the context menu, then `run`.
|
||||||
|
|
||||||
|
You will have both tests executed and you will be able to figure out if the first one has an impact on the second one.
|
||||||
|
|
||||||
|
### About process isolation
|
||||||
|
#### Isolation with PHPUnit
|
||||||
|
|
||||||
|
By default, tests are run in a single process launched by PHPUnit.
|
||||||
|
|
||||||
|
If process isolation is configured for some tests, then those tests
|
||||||
|
will be executed in a separate process. The main process will
|
||||||
|
continue executing non isolated tests.
|
||||||
|
|
||||||
|
#### Cost of isolation
|
||||||
|
|
||||||
|
The cost of isolating a very basic `TestCase` is approximately 4 ms.
|
||||||
|
|
||||||
|
The cost of isolating an `ItopDataTestCase` is approximately 800 ms.
|
||||||
|
|
||||||
|
### Isolation within iTop
|
||||||
|
|
||||||
|
#### At the test level (preferred)
|
||||||
|
Add annotation `@runInSeparateProcess`
|
||||||
|
Each and every test case will run in a separate
|
||||||
|
process.
|
||||||
|
|
||||||
|
Note : before N°6658 (3.0.4 / 3.1.1 / 3.2.0) we were also adding the `@backupGlobals disabled`
|
||||||
|
and `@preserveGlobalState disabled` annotations. This is no longer necessary as the first has this default value
|
||||||
|
already, and the second one is now set in iTopTestCase as a PHP class attribute.
|
||||||
|
|
||||||
|
#### At the test class level
|
||||||
|
Add annotation `@runTestsInSeparateProcesses`
|
||||||
|
Each and every test case in the class will run in a separate
|
||||||
|
process.
|
||||||
|
|
||||||
|
#### Globally (never do that)
|
||||||
|
Set it into [phpunit.xml.dist](phpunit.xml.dist)
|
||||||
|
|
||||||
|
### Further enhancements
|
||||||
|
The annotation [`@runClassInSeparateProcess`](https://docs.phpunit.de/en/10.0/attributes.html?highlight=runclassinseparateprocess#runclassinseparateprocess) is supposed to do the perfect job, but it is buggy [(See Issue 5230)](https://github.com/sebastianbergmann/phpunit/issues/5230) and it has
|
||||||
|
the exact same effect as `@runTestsInSeparateProcesses`.
|
||||||
|
|
||||||
|
Note : this option is documented only in the [attributes part of the documentation](https://docs.phpunit.de/en/10.0/attributes.html).
|
||||||
|
|
||||||
|
### Traps
|
||||||
|
#### When it is a matter of stars
|
||||||
|
```php
|
||||||
|
/*
|
||||||
|
* @runTestsInSeparateProcesses
|
||||||
|
```
|
||||||
|
This won't work because the comment MUST start with `/**` (two stars) to be considerer by PHPUnit.
|
||||||
|
|
||||||
|
#### SetupBeforeClass called more often than expected
|
||||||
|
|
||||||
|
`setupBeforeClass` is called once for the class **in a given process**.
|
||||||
|
|
||||||
|
Therefore, if the tests are isolated, then `setupBeforeClass` will be called as often as `setUp`.
|
||||||
|
|
||||||
|
This has been proven with [`runClassInSeparateProcessTest.php`](experiments/runClassInSeparateProcessTest.php)
|
||||||
@@ -2,5 +2,12 @@
|
|||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit" : "^9",
|
"phpunit/phpunit" : "^9",
|
||||||
"sempro/phpunit-pretty-print": "^1.4"
|
"sempro/phpunit-pretty-print": "^1.4"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Combodo\\iTop\\Test\\UnitTest\\": "src/BaseTestCase/",
|
||||||
|
"Combodo\\iTop\\Test\\UnitTest\\Hook\\": "src/Hook/",
|
||||||
|
"Combodo\\iTop\\Test\\UnitTest\\Service\\": "src/Service/"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
tests/php-unit-tests/experiments/README.md
Normal file
1
tests/php-unit-tests/experiments/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
This directory aims at providing experimental proof of the mechanics of PHPUnit
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Combodo\iTop\Test\UnitTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows that
|
||||||
|
* 1) the option runClassInSeparateProcess is equivalent to runTestsInSeparateProcesses
|
||||||
|
* 2) setUpBeforeClass is called within each spawned process (the main one, then in eventuel subprocesses)
|
||||||
|
* 3) setUp behaves as expected, i.e. called one within the same process as the test itself
|
||||||
|
*
|
||||||
|
* @preserveGlobalState disabled
|
||||||
|
* @runClassInSeparateProcess
|
||||||
|
*/
|
||||||
|
class runClassInSeparateProcessTest extends ItopDataTestCase
|
||||||
|
{
|
||||||
|
static public function setUpBeforeClass(): void
|
||||||
|
{
|
||||||
|
parent::setUpBeforeClass(); // TODO: Change the autogenerated stub
|
||||||
|
|
||||||
|
file_put_contents(
|
||||||
|
dirname(__FILE__).'/pid.txt',
|
||||||
|
getmypid().';'.static::class.';'.__METHOD__."\n",
|
||||||
|
FILE_APPEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function LogPid()
|
||||||
|
{
|
||||||
|
file_put_contents(
|
||||||
|
dirname(__FILE__).'/pid.txt',
|
||||||
|
getmypid().';'.static::class.';'.$this->getName()."\n",
|
||||||
|
FILE_APPEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testA()
|
||||||
|
{
|
||||||
|
$this->LogPid();
|
||||||
|
static::assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testB()
|
||||||
|
{
|
||||||
|
$this->LogPid();
|
||||||
|
static::assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider CProvider
|
||||||
|
*/
|
||||||
|
function testC($i)
|
||||||
|
{
|
||||||
|
$this->LogPid();
|
||||||
|
static::assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CProvider()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[1],
|
||||||
|
[1],
|
||||||
|
[1],
|
||||||
|
[1],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Combodo\iTop\Test\UnitTest;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows that tearDown is called after a fatal error within a test
|
||||||
|
*/
|
||||||
|
class tearDownAfterFailureTest extends TestCase
|
||||||
|
{
|
||||||
|
static $bIsCorrectlyInitialized = true;
|
||||||
|
|
||||||
|
protected function tearDown(): void
|
||||||
|
{
|
||||||
|
parent::tearDown();
|
||||||
|
static::$bIsCorrectlyInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function testIsInitializedAndChangeIt()
|
||||||
|
{
|
||||||
|
static::assertTrue(static::$bIsCorrectlyInitialized);
|
||||||
|
|
||||||
|
static::$bIsCorrectlyInitialized = false;
|
||||||
|
|
||||||
|
$this->expectException('Exception');
|
||||||
|
throw new \Exception('hello');
|
||||||
|
}
|
||||||
|
|
||||||
|
function testIsStillInitialized()
|
||||||
|
{
|
||||||
|
static::assertTrue(static::$bIsCorrectlyInitialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testFailingDueToUnexpectedException()
|
||||||
|
{
|
||||||
|
static::$bIsCorrectlyInitialized = false;
|
||||||
|
This_Is_Not_A_Function_And_Causes_A_Fatal_Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testIsStillInitializedAnyway()
|
||||||
|
{
|
||||||
|
static::assertTrue(static::$bIsCorrectlyInitialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2013-2023 Combodo SARL
|
||||||
|
* This file is part of iTop.
|
||||||
|
* iTop is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
* iTop is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Combodo\iTop\Test\UnitTest\Integration;
|
||||||
|
|
||||||
|
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||||
|
use Dict;
|
||||||
|
use const APPROOT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* As {@see DictionariesConsistencyTest}, we are testing dict files, but the ones that are compiled, so we cannot be in the beforeSetup group !
|
||||||
|
*/
|
||||||
|
class CompiledDictionariesConsistencyTest extends ItopTestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* make sure N°5305 dictionary changes (CSV import ergonomy) are still here and UI remains unbroken for any lang
|
||||||
|
*
|
||||||
|
* One of the things checked is the number of parameters in the dict value. This is for now crashing the app (N°5491)
|
||||||
|
* and we have multiple inconsistencies in our existing dict files... So it is complicated to have a generic test for all files !
|
||||||
|
* At least we are protecting those new entries...
|
||||||
|
*/
|
||||||
|
public function testImportCsvMessageStillOk()
|
||||||
|
{
|
||||||
|
$aFailedLabels = [];
|
||||||
|
$aLabelsToTest = [
|
||||||
|
'UI:CSVReport-Value-SetIssue' => [],
|
||||||
|
'UI:CSVReport-Value-ChangeIssue' => ['arg1'],
|
||||||
|
'UI:CSVReport-Value-NoMatch' => ['arg1'],
|
||||||
|
'UI:CSVReport-Value-NoMatch-PossibleValues' => ['arg1', 'arg2'],
|
||||||
|
'UI:CSVReport-Value-NoMatch-NoObject' => ['arg1'],
|
||||||
|
'UI:CSVReport-Value-NoMatch-NoObject-ForCurrentUser' => ['arg1'],
|
||||||
|
'UI:CSVReport-Value-NoMatch-SomeObjectNotVisibleForCurrentUser' => ['arg1'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$sCompiledLanguagesFilePath = APPROOT . 'env-' . \utils::GetCurrentEnvironment() . '/dictionaries/languages.php';
|
||||||
|
$this->assertFileExists($sCompiledLanguagesFilePath, 'We must have an existing compiled language.php file in the current env !');
|
||||||
|
require_once($sCompiledLanguagesFilePath);
|
||||||
|
$this->assertNotEmpty(Dict::GetLanguages(), 'the languages.php file exists but didn\'t load any language');
|
||||||
|
|
||||||
|
foreach (glob(APPROOT . 'env-' . \utils::GetCurrentEnvironment() . '/dictionaries/*.dict.php') as $sDictFile) {
|
||||||
|
if (preg_match('/.*\\/(.*).dict.php/', $sDictFile, $aMatches)) {
|
||||||
|
$sLangCode = $aMatches[1];
|
||||||
|
$sLanguageCode = strtoupper(str_replace('-', ' ', $sLangCode));
|
||||||
|
Dict::SetUserLanguage($sLanguageCode);
|
||||||
|
|
||||||
|
foreach ($aLabelsToTest as $sLabelKey => $aLabelArgs) {
|
||||||
|
echo "Testing $sDictFile, label $sLabelKey with " . \var_export($aLabelArgs, true) . "\n";
|
||||||
|
try {
|
||||||
|
$sLabelValue = Dict::Format($sLabelKey, ...$aLabelArgs);
|
||||||
|
//$this->debug($sLabelValue);
|
||||||
|
} catch (\ValueError $e) {
|
||||||
|
$aFailedLabels[] = $sLabelKey;
|
||||||
|
|
||||||
|
$this->debug([
|
||||||
|
'exception' => $e->getMessage(),
|
||||||
|
'trace' => $e->getTraceAsString(),
|
||||||
|
'label_name' => $sLabelKey,
|
||||||
|
'label_args' => $aLabelArgs,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->assertEquals([], $aFailedLabels, "$sDictFile : test fail for lang $sLangCode and labels (" . implode(", ", $aFailedLabels) . ')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Combodo\iTop\Test\UnitTest;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use GlobIterator;
|
||||||
|
use RecursiveDirectoryIterator;
|
||||||
|
use RecursiveIteratorIterator;
|
||||||
|
use RecursiveRegexIterator;
|
||||||
|
use RegexIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs code static analysis to detect patterns that will change the values of static data and therefor could affect other tests while running them in a single process
|
||||||
|
*
|
||||||
|
* @runClassInSeparateProcess
|
||||||
|
* @preserveGlobalState disabled
|
||||||
|
*/
|
||||||
|
class detectStaticPollutionTest extends TestCase
|
||||||
|
{
|
||||||
|
protected function FindMatches($sFile, $sFileContents, $sRegexp)
|
||||||
|
{
|
||||||
|
$aRes = [];
|
||||||
|
foreach (explode("\n", $sFileContents) as $iLine => $sLine) {
|
||||||
|
if (preg_match_all($sRegexp, $sLine, $aMatches, PREG_PATTERN_ORDER)) {
|
||||||
|
$sLine = $iLine + 1;
|
||||||
|
$aRes[] = "$sFile:$sLine";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $aRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider PollutingPatterns
|
||||||
|
* @param $sPattern
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function testDetectPolluters($sPattern, $sFix)
|
||||||
|
{
|
||||||
|
$sScannedDir = dirname(__FILE__).'/../unitary-tests';
|
||||||
|
|
||||||
|
$aPolluters = [];
|
||||||
|
$oDirectory = new RecursiveDirectoryIterator($sScannedDir);
|
||||||
|
$Iterator = new RecursiveIteratorIterator($oDirectory);
|
||||||
|
foreach (new RegexIterator($Iterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH) as $aMatch) {
|
||||||
|
$sFile = $aMatch[0];
|
||||||
|
if(is_file($sFile)) {
|
||||||
|
$sFileContents = file_get_contents($sFile);
|
||||||
|
if (preg_match_all($sPattern, $sFileContents, $keys, PREG_PATTERN_ORDER)) {
|
||||||
|
$aPolluters = array_merge($aPolluters, $this->FindMatches($sFile, $sFileContents, $sPattern));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$iPolluters = count($aPolluters);
|
||||||
|
static::assertTrue($iPolluters === 0, "Found polluter(s) for pattern $sPattern, $sFix:\n".implode("\n", $aPolluters));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function PollutingPatterns()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'ContextTags' => ['/ContextTag::AddContext/i', 'Use new ContextTag() instead'],
|
||||||
|
'Dict::Add' => ['/Dict::Add/i', 'TODO: implement a facade into ItopDataTestCase'],
|
||||||
|
'EventService::RegisterListener' => ['/EventService::RegisterListener/i', 'Use ItopDataTestCase::EventService_RegisterListener instead'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Combodo\iTop\Test\UnitTest\Integration;
|
||||||
|
|
||||||
|
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||||
|
|
||||||
|
class DictionariesConsistencyAfterSetupTest extends ItopTestCase
|
||||||
|
{
|
||||||
|
//used by testDictEntryValues
|
||||||
|
//to filter false positive broken traductions
|
||||||
|
private static $aLabelCodeNotToCheck = [
|
||||||
|
//use of Dict::S not Format
|
||||||
|
"UI:Audit:PercentageOk",
|
||||||
|
|
||||||
|
//unused dead labels
|
||||||
|
"Class:DatacenterDevice/Attribute:redundancy/count",
|
||||||
|
"Class:DatacenterDevice/Attribute:redundancy/disabled",
|
||||||
|
"Class:DatacenterDevice/Attribute:redundancy/percent",
|
||||||
|
"Class:TriggerOnThresholdReached/Attribute:threshold_index+"
|
||||||
|
];
|
||||||
|
|
||||||
|
public function FormatProvider(){
|
||||||
|
return [
|
||||||
|
'key does not exist in dictionnary' => [
|
||||||
|
'sTemplate' => null,
|
||||||
|
'sExpectedTraduction' => 'ITOP::DICT:FORMAT:BROKEN:KEY - 1',
|
||||||
|
],
|
||||||
|
'traduction that breaks expected nb of arguments' => [
|
||||||
|
'sTemplate' => 'toto %1$s titi %2$s',
|
||||||
|
'sExpectedTraduction' => 'ITOP::DICT:FORMAT:BROKEN:KEY - 1',
|
||||||
|
],
|
||||||
|
'traduction ok' => [
|
||||||
|
'sTemplate' => 'toto %1$s titi',
|
||||||
|
'sExpectedTraduction' => 'toto 1 titi',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $sTemplate : if null it will not create dict entry
|
||||||
|
* @since 2.7.10 N°5491 - Inconsistent dictionary entries regarding arguments to pass to Dict::Format
|
||||||
|
* @dataProvider FormatProvider
|
||||||
|
*/
|
||||||
|
public function testFormatWithOneArgumentAndCustomKey(?string $sTemplate, $sExpectedTranslation){
|
||||||
|
//tricky way to mock GetLabelAndLangCode behavior via connected user language
|
||||||
|
$sLangCode = \Dict::GetUserLanguage();
|
||||||
|
$aDictByLang = $this->GetNonPublicStaticProperty(\Dict::class, 'm_aData');
|
||||||
|
$sDictKey = 'ITOP::DICT:FORMAT:BROKEN:KEY';
|
||||||
|
|
||||||
|
if (! is_null($sTemplate)){
|
||||||
|
$aDictByLang[$sLangCode][$sDictKey] = $sTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->SetNonPublicStaticProperty(\Dict::class, 'm_aData', $aDictByLang);
|
||||||
|
|
||||||
|
$this->assertEquals($sExpectedTranslation, \Dict::Format($sDictKey, "1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
//test works after setup (no annotation @beforesetup)
|
||||||
|
//even if it does not extend ItopDataTestCase
|
||||||
|
private function ReadDictKeys($sLangCode) : array {
|
||||||
|
\Dict::InitLangIfNeeded($sLangCode);
|
||||||
|
|
||||||
|
$aDictEntries = $this->GetNonPublicStaticProperty(\Dict::class, 'm_aData');
|
||||||
|
return $aDictEntries[$sLangCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* foreach dictionnary label map (key/value) it counts the number argument that should be passed to use Dict::Format
|
||||||
|
* examples:
|
||||||
|
* for "gabu zomeu" label there are no args
|
||||||
|
* for "shadok %1 %2 %3" there are 3 args
|
||||||
|
*
|
||||||
|
* limitation: there is no validation check for "%3 itop %2 combodo" which seems unconsistent
|
||||||
|
* @param $aDictEntry
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function GetKeyArgCountMap($aDictEntry) {
|
||||||
|
$aKeyArgsCount = [];
|
||||||
|
foreach ($aDictEntry as $sKey => $sValue){
|
||||||
|
$aKeyArgsCount[$sKey] = $this->countArg($sValue);
|
||||||
|
}
|
||||||
|
ksort($aKeyArgsCount);
|
||||||
|
return $aKeyArgsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function countArg($sLabel) {
|
||||||
|
$iMaxIndex = 0;
|
||||||
|
if (preg_match_all("/%(\d+)/", $sLabel, $aMatches)){
|
||||||
|
$aSubMatches = $aMatches[1];
|
||||||
|
if (is_array($aSubMatches)){
|
||||||
|
foreach ($aSubMatches as $aCurrentMatch){
|
||||||
|
$iIndex = $aCurrentMatch;
|
||||||
|
$iMaxIndex = ($iMaxIndex < $iIndex) ? $iIndex : $iMaxIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ((false !== strpos($sLabel, "%s"))
|
||||||
|
|| (false !== strpos($sLabel, "%d"))
|
||||||
|
){
|
||||||
|
$iMaxIndex = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $iMaxIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Warning: hardcoded list of languages
|
||||||
|
* It is hard to have it dynamically via Dict::GetLanguages as for each lang Dict::Init should be called first
|
||||||
|
**/
|
||||||
|
public function LangCodeProvider(){
|
||||||
|
return [
|
||||||
|
'cs' => [ 'CS CZ' ],
|
||||||
|
'da' => [ 'DA DA' ],
|
||||||
|
'de' => [ 'DE DE' ],
|
||||||
|
'en' => [ 'EN US' ],
|
||||||
|
'es' => [ 'ES CR' ],
|
||||||
|
'fr' => [ 'FR FR' ],
|
||||||
|
'hu' => [ 'HU HU' ],
|
||||||
|
'it' => [ 'IT IT' ],
|
||||||
|
'ja' => [ 'JA JP' ],
|
||||||
|
'nl' => [ 'NL NL' ],
|
||||||
|
'pt' => [ 'PT BR' ],
|
||||||
|
'ru' => [ 'RU RU' ],
|
||||||
|
'sk' => [ 'SK SK' ],
|
||||||
|
'tr' => [ 'TR TR' ],
|
||||||
|
'zh' => [ 'ZH CN' ],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* compare en and other dictionaries and check that for all labels there is the same number of arguments
|
||||||
|
* if not Dict::Format could raise an exception for some languages. translation should be done again...
|
||||||
|
* @dataProvider LangCodeProvider
|
||||||
|
*/
|
||||||
|
public function testDictEntryValues($sLanguageCodeToTest)
|
||||||
|
{
|
||||||
|
$sReferenceLangCode = 'EN US';
|
||||||
|
$aReferenceLangDictEntry = $this->ReadDictKeys($sReferenceLangCode);
|
||||||
|
|
||||||
|
$aDictEntry = $this->ReadDictKeys($sLanguageCodeToTest);
|
||||||
|
|
||||||
|
|
||||||
|
$aKeyArgsCountMap = [];
|
||||||
|
$aKeyArgsCountMap[$sReferenceLangCode] = $this->GetKeyArgCountMap($aReferenceLangDictEntry);
|
||||||
|
//$aKeyArgsCountMap[$sCode] = $this->GetKeyArgCountMap($aDictEntry);
|
||||||
|
|
||||||
|
//set user language
|
||||||
|
$this->SetNonPublicStaticProperty(\Dict::class, 'm_sCurrentLanguage', $sLanguageCodeToTest);
|
||||||
|
|
||||||
|
$aMismatchedKeys = [];
|
||||||
|
|
||||||
|
foreach ($aKeyArgsCountMap[$sReferenceLangCode] as $sKey => $iExpectedNbOfArgs){
|
||||||
|
if (in_array($sKey, self::$aLabelCodeNotToCheck)){
|
||||||
|
//false positive: do not test
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists($sKey, $aDictEntry)){
|
||||||
|
$aPlaceHolders = [];
|
||||||
|
for ($i=0; $i<$iExpectedNbOfArgs; $i++){
|
||||||
|
$aPlaceHolders[]=$i;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sLabelTemplate = $aDictEntry[$sKey];
|
||||||
|
try{
|
||||||
|
vsprintf($sLabelTemplate, $aPlaceHolders);
|
||||||
|
} catch(\Throwable $e){
|
||||||
|
$sError = $e->getMessage();
|
||||||
|
if (array_key_exists($sError, $aMismatchedKeys)){
|
||||||
|
$aMismatchedKeys[$sError][$sKey] = $iExpectedNbOfArgs;
|
||||||
|
} else {
|
||||||
|
$aMismatchedKeys[$sError] = [$sKey => $iExpectedNbOfArgs];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$iCount = 0;
|
||||||
|
foreach ($aMismatchedKeys as $sError => $aKeys){
|
||||||
|
var_dump($sError);
|
||||||
|
foreach ($aKeys as $sKey => $iExpectedNbOfArgs) {
|
||||||
|
$iCount++;
|
||||||
|
if ($sReferenceLangCode === $sLanguageCodeToTest) {
|
||||||
|
var_dump([
|
||||||
|
'key label' => $sKey,
|
||||||
|
'expected nb of expected args' => $iExpectedNbOfArgs,
|
||||||
|
"key value in $sLanguageCodeToTest" => $aDictEntry[$sKey],
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
var_dump([
|
||||||
|
'key label' => $sKey,
|
||||||
|
'expected nb of expected args' => $iExpectedNbOfArgs,
|
||||||
|
"key value in $sLanguageCodeToTest" => $aDictEntry[$sKey],
|
||||||
|
"key value in $sReferenceLangCode" => $aReferenceLangDictEntry[$sKey],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sErrorMsg = sprintf("%s broken propertie(s) on $sLanguageCodeToTest dictionaries! either change the dict value in $sLanguageCodeToTest or add it in ignored label list (cf aLabelCodeNotToCheck)", $iCount);
|
||||||
|
$this->assertEquals([], $aMismatchedKeys, $sErrorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function VsprintfProvider(){
|
||||||
|
return [
|
||||||
|
'not enough args' => [
|
||||||
|
"sLabelTemplate" => "$1%s",
|
||||||
|
"aPlaceHolders" => [],
|
||||||
|
],
|
||||||
|
'exact nb of args' => [
|
||||||
|
"sLabelTemplate" => "$1%s",
|
||||||
|
"aPlaceHolders" => ["1"],
|
||||||
|
],
|
||||||
|
'too much args' => [
|
||||||
|
"sLabelTemplate" => "$1%s",
|
||||||
|
"aPlaceHolders" => ["1", "2"],
|
||||||
|
],
|
||||||
|
'\"% ok\" without args' => [
|
||||||
|
"sLabelTemplate" => "% ok",
|
||||||
|
"aPlaceHolders" => [],
|
||||||
|
],
|
||||||
|
'\"% ok $1%s\" without args' => [
|
||||||
|
"sLabelTemplate" => "% ok",
|
||||||
|
"aPlaceHolders" => ['1'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider VsprintfProvider
|
||||||
|
public function testVsprintf($sLabelTemplate, $aPlaceHolders){
|
||||||
|
try{
|
||||||
|
$this->markTestSkipped("usefull to check a specific PHP version behavior");
|
||||||
|
vsprintf($sLabelTemplate, $aPlaceHolders);
|
||||||
|
$this->assertTrue(true);
|
||||||
|
} catch(\Throwable $e) {
|
||||||
|
$this->assertTrue(false, "label \'" . $sLabelTemplate . " failed with " . var_export($aPlaceHolders, true) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -16,9 +16,9 @@
|
|||||||
namespace Combodo\iTop\Test\UnitTest\Integration;
|
namespace Combodo\iTop\Test\UnitTest\Integration;
|
||||||
|
|
||||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||||
use Dict;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* For tests on compiled dict files, see {@see CompiledDictionariesConsistencyTest}
|
||||||
* @group beforeSetup
|
* @group beforeSetup
|
||||||
*/
|
*/
|
||||||
class DictionariesConsistencyTest extends ItopTestCase
|
class DictionariesConsistencyTest extends ItopTestCase
|
||||||
@@ -32,6 +32,8 @@ class DictionariesConsistencyTest extends ItopTestCase
|
|||||||
*/
|
*/
|
||||||
public function testDictionariesLanguage($sDictFile): void
|
public function testDictionariesLanguage($sDictFile): void
|
||||||
{
|
{
|
||||||
|
// In iTop the language available list is dynamically made during setup, depending on the dict files found
|
||||||
|
// Here we are using a fixed list
|
||||||
$aPrefixToLanguageData = array(
|
$aPrefixToLanguageData = array(
|
||||||
'cs' => array('CS CZ', 'Czech', 'Čeština'),
|
'cs' => array('CS CZ', 'Czech', 'Čeština'),
|
||||||
'da' => array('DA DA', 'Danish', 'Dansk'),
|
'da' => array('DA DA', 'Danish', 'Dansk'),
|
||||||
@@ -96,10 +98,12 @@ class DictionariesConsistencyTest extends ItopTestCase
|
|||||||
{
|
{
|
||||||
$this->setUp();
|
$this->setUp();
|
||||||
|
|
||||||
|
$sAppRoot = $this->GetAppRoot();
|
||||||
|
|
||||||
$aDictFiles = array_merge(
|
$aDictFiles = array_merge(
|
||||||
glob(APPROOT.'datamodels/2.x/*/*.dict*.php'), // legacy form in modules
|
glob($sAppRoot.'datamodels/2.x/*/*.dict*.php'), // legacy form in modules
|
||||||
glob(APPROOT.'datamodels/2.x/*/dictionaries/*.dict*.php'), // modern form in modules
|
glob($sAppRoot.'datamodels/2.x/*/dictionaries/*.dict*.php'), // modern form in modules
|
||||||
glob(APPROOT.'dictionaries/*.dict*.php') // framework
|
glob($sAppRoot.'dictionaries/*.dict*.php') // framework
|
||||||
);
|
);
|
||||||
$aTestCases = array();
|
$aTestCases = array();
|
||||||
foreach ($aDictFiles as $sDictFile) {
|
foreach ($aDictFiles as $sDictFile) {
|
||||||
@@ -150,67 +154,4 @@ class DictionariesConsistencyTest extends ItopTestCase
|
|||||||
$sMessage = "File `{$sDictFile}` syntax didn't matched expectations\nparsing results=".var_export($output, true);
|
$sMessage = "File `{$sDictFile}` syntax didn't matched expectations\nparsing results=".var_export($output, true);
|
||||||
self::assertEquals($bIsSyntaxValid, $bDictFileSyntaxOk, $sMessage);
|
self::assertEquals($bIsSyntaxValid, $bDictFileSyntaxOk, $sMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @dataProvider ImportCsvMessageStillOkProvider
|
|
||||||
* make sure N°5305 dictionary changes are still here and UI remains unbroken for any lang
|
|
||||||
*/
|
|
||||||
public function testImportCsvMessageStillOk($sLangCode, $sDictFile)
|
|
||||||
{
|
|
||||||
$aFailedLabels = [];
|
|
||||||
$aLabelsToTest = [
|
|
||||||
'UI:CSVReport-Value-SetIssue' => [],
|
|
||||||
'UI:CSVReport-Value-ChangeIssue' => [ 'arg1' ],
|
|
||||||
'UI:CSVReport-Value-NoMatch' => [ 'arg1' ],
|
|
||||||
'UI:CSVReport-Value-NoMatch-PossibleValues' => [ 'arg1', 'arg2' ],
|
|
||||||
'UI:CSVReport-Value-NoMatch-NoObject' => [ 'arg1' ],
|
|
||||||
'UI:CSVReport-Value-NoMatch-NoObject-ForCurrentUser' => [ 'arg1' ],
|
|
||||||
'UI:CSVReport-Value-NoMatch-SomeObjectNotVisibleForCurrentUser' => [ 'arg1' ],
|
|
||||||
];
|
|
||||||
|
|
||||||
$sLanguageCode = strtoupper(str_replace('-', ' ', $sLangCode));
|
|
||||||
require_once(APPROOT.'env-'.\utils::GetCurrentEnvironment().'/dictionaries/languages.php');
|
|
||||||
Dict::SetUserLanguage($sLanguageCode);
|
|
||||||
foreach ($aLabelsToTest as $sLabelKey => $aLabelArgs){
|
|
||||||
try{
|
|
||||||
$sLabelValue = Dict::Format($sLabelKey, ...$aLabelArgs);
|
|
||||||
//$this->debug($sLabelValue);
|
|
||||||
} catch (\ValueError $e){
|
|
||||||
$aFailedLabels[] = $sLabelKey;
|
|
||||||
|
|
||||||
$this->debug([
|
|
||||||
'exception' => $e->getMessage(),
|
|
||||||
'trace' => $e->getTraceAsString(),
|
|
||||||
'label_name' => $sLabelKey,
|
|
||||||
'label_args' =>$aLabelArgs,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->assertEquals([], $aFailedLabels, "test fail for lang $sLangCode and labels (" . implode(", ", $aFailedLabels) . ')');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function ImportCsvMessageStillOkProvider(){
|
|
||||||
return $this->GetDictFiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* return a map linked to *.dict.php files that are generated after setup
|
|
||||||
* each entry key is lang code (example 'en')
|
|
||||||
* each value is an array with lang code (again) and dict file path
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function GetDictFiles() : array {
|
|
||||||
$aDictFiles = [];
|
|
||||||
|
|
||||||
foreach (glob(APPROOT.'env-'.\utils::GetCurrentEnvironment().'/dictionaries/*.dict.php') as $sDictFile){
|
|
||||||
if (preg_match('/.*\\/(.*).dict.php/', $sDictFile, $aMatches)){
|
|
||||||
$sLangCode = $aMatches[1];
|
|
||||||
$aDictFiles[$sLangCode] = [
|
|
||||||
'lang' => $sLangCode,
|
|
||||||
'file' => $sDictFile
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $aDictFiles;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use utils;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @package Combodo\iTop\Test\UnitTest\Setup
|
* @package Combodo\iTop\Test\UnitTest\Setup
|
||||||
|
* @group beforeSetup
|
||||||
*/
|
*/
|
||||||
class iTopModulesPhpVersionIntegrationTest extends ItopTestCase {
|
class iTopModulesPhpVersionIntegrationTest extends ItopTestCase {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ use iTopDesignFormat;
|
|||||||
* @covers iTopDesignFormat
|
* @covers iTopDesignFormat
|
||||||
*
|
*
|
||||||
* @package Combodo\iTop\Test\UnitTest\Setup
|
* @package Combodo\iTop\Test\UnitTest\Setup
|
||||||
|
* @group beforeSetup
|
||||||
*/
|
*/
|
||||||
class iTopModulesXmlVersionIntegrationTest extends ItopTestCase
|
class iTopModulesXmlVersionIntegrationTest extends ItopTestCase
|
||||||
{
|
{
|
||||||
@@ -71,11 +72,13 @@ class iTopModulesXmlVersionIntegrationTest extends ItopTestCase
|
|||||||
{
|
{
|
||||||
static::setUp();
|
static::setUp();
|
||||||
|
|
||||||
$sPath = APPROOT.'datamodels/2.x/*/datamodel.*.xml';
|
$sAppRoot = $this->GetAppRoot();
|
||||||
|
|
||||||
|
$sPath = $sAppRoot.'datamodels/2.x/*/datamodel.*.xml';
|
||||||
$aXmlFiles = glob($sPath);
|
$aXmlFiles = glob($sPath);
|
||||||
|
|
||||||
$aXmlFiles[] = APPROOT.'core/datamodel.core.xml';
|
$aXmlFiles[] = $sAppRoot.'core/datamodel.core.xml';
|
||||||
$aXmlFiles[] = APPROOT.'application/datamodel.application.xml';
|
$aXmlFiles[] = $sAppRoot.'application/datamodel.application.xml';
|
||||||
|
|
||||||
$aTestCases = array();
|
$aTestCases = array();
|
||||||
foreach ($aXmlFiles as $sXmlFile) {
|
foreach ($aXmlFiles as $sXmlFile) {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @package Combodo\iTop\Test\UnitTest\Setup
|
* @package Combodo\iTop\Test\UnitTest\Setup
|
||||||
|
* @group beforeSetup
|
||||||
*/
|
*/
|
||||||
class iTopXmlVersionIntegrationTest extends ItopTestCase
|
class iTopXmlVersionIntegrationTest extends ItopTestCase
|
||||||
{
|
{
|
||||||
|
|||||||
51
tests/php-unit-tests/perf-tests.xml.dist
Normal file
51
tests/php-unit-tests/perf-tests.xml.dist
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.5/phpunit.xsd"
|
||||||
|
bootstrap="unittestautoload.php"
|
||||||
|
backupGlobals="true"
|
||||||
|
colors="true"
|
||||||
|
columns="120"
|
||||||
|
convertErrorsToExceptions="true"
|
||||||
|
convertNoticesToExceptions="true"
|
||||||
|
convertWarningsToExceptions="true"
|
||||||
|
processIsolation="false"
|
||||||
|
stopOnError="false"
|
||||||
|
stopOnFailure="false"
|
||||||
|
stopOnIncomplete="false"
|
||||||
|
stopOnRisky="false"
|
||||||
|
stopOnSkipped="false"
|
||||||
|
verbose="true"
|
||||||
|
printerClass="\Sempro\PHPUnitPrettyPrinter\PrettyPrinterForPhpUnit9"
|
||||||
|
>
|
||||||
|
|
||||||
|
<extensions>
|
||||||
|
<extension class="Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook" />
|
||||||
|
</extensions>
|
||||||
|
|
||||||
|
<php>
|
||||||
|
<ini name="memory_limit" value="512M"/>
|
||||||
|
<ini name="error_reporting" value="E_ALL"/>
|
||||||
|
<ini name="display_errors" value="On"/>
|
||||||
|
<ini name="log_errors" value="On"/>
|
||||||
|
<ini name="html_errors" value="Off"/>
|
||||||
|
<env name="PHPUNIT_PRETTY_PRINT_PROGRESS" value="true"/>
|
||||||
|
</php>
|
||||||
|
|
||||||
|
<testsuites>
|
||||||
|
<!-- Unitary tests -->
|
||||||
|
<testsuite name="Perf">
|
||||||
|
<directory>perf-tests</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
|
||||||
|
<!-- Code coverage white list -->
|
||||||
|
<filter>
|
||||||
|
<whitelist>
|
||||||
|
<file>../../core/apc-emulation.php</file>
|
||||||
|
<file>../../core/ormlinkset.class.inc.php</file>
|
||||||
|
<file>../../datamodels/2.x/itop-tickets/main.itop-tickets.php</file>
|
||||||
|
</whitelist>
|
||||||
|
</filter>
|
||||||
|
|
||||||
|
</phpunit>
|
||||||
93
tests/php-unit-tests/perf-tests/BulkDBObjectTest.php
Normal file
93
tests/php-unit-tests/perf-tests/BulkDBObjectTest.php
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||||
|
* @license http://opensource.org/licenses/AGPL-3.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
class BulkDBObjectTest extends ItopDataTestCase
|
||||||
|
{
|
||||||
|
const CREATE_TEST_ORG = true;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInsertAndReadPersonObjects()
|
||||||
|
{
|
||||||
|
echo "Part 1: Insert Persons\n";
|
||||||
|
|
||||||
|
$sOrgId = $this->getTestOrgId();
|
||||||
|
$idx = 1;
|
||||||
|
$fStart = microtime(true);
|
||||||
|
$fMaxExecutionTimeAllowed = 40.0;
|
||||||
|
$iInitialPeak = 0;
|
||||||
|
$sInitialPeak = '';
|
||||||
|
$fStartLoop = $fStart;
|
||||||
|
for ($i = 0; $i < 3000; $i++) {
|
||||||
|
$oPerson = $this->CreateObject(Person::class, ['org_id' => $sOrgId, 'name' => "Person_$i", 'first_name' => 'John']);
|
||||||
|
if (0 == ($idx % 100)) {
|
||||||
|
$fDuration = microtime(true) - $fStartLoop;
|
||||||
|
$iMemoryPeakUsage = memory_get_peak_usage();
|
||||||
|
if ($iInitialPeak === 0) {
|
||||||
|
$iInitialPeak = $iMemoryPeakUsage;
|
||||||
|
$sInitialPeak = \utils::BytesToFriendlyFormat($iInitialPeak, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sCurrPeak = \utils::BytesToFriendlyFormat($iMemoryPeakUsage, 4);
|
||||||
|
echo "$idx ".sprintf('%.1f ms', $fDuration * 1000)." - Peak Memory Usage: $sCurrPeak\n";
|
||||||
|
$this->assertTrue($iMemoryPeakUsage === $iInitialPeak, "Peak memory changed from $sInitialPeak to $sCurrPeak after $idx insert loops"); $fStartLoop = microtime(true);
|
||||||
|
|
||||||
|
$fTotalDuration = microtime(true) - $fStart;
|
||||||
|
$sTotalDuration = sprintf('%.3f s', $fTotalDuration);
|
||||||
|
$this->assertTrue($fTotalDuration < $fMaxExecutionTimeAllowed, "execution time $sTotalDuration should be < $fMaxExecutionTimeAllowed ($idx insert loops)");
|
||||||
|
}
|
||||||
|
$idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fTotalDuration = microtime(true) - $fStart;
|
||||||
|
$sTotalDuration = sprintf('%.3f s', $fTotalDuration);
|
||||||
|
echo "Total duration: $sTotalDuration\n\n";
|
||||||
|
$this->assertTrue($fTotalDuration < $fMaxExecutionTimeAllowed, "Total execution time $sTotalDuration should be < $fMaxExecutionTimeAllowed");
|
||||||
|
|
||||||
|
//////////////////////
|
||||||
|
// Part 2 Fetch all the created persons
|
||||||
|
echo "Part 1: Fetch Persons\n";
|
||||||
|
|
||||||
|
$oSearch = DBSearch::FromOQL('SELECT Person WHERE org_id=:org_id');
|
||||||
|
$oSet = new DBObjectSet($oSearch, [], ['org_id' => $sOrgId]);
|
||||||
|
$idx = 1;
|
||||||
|
$iInitialPeak = 0;
|
||||||
|
$sInitialPeak = '';
|
||||||
|
$fMaxExecutionTimeAllowed = 0.5;
|
||||||
|
$fStart = microtime(true);
|
||||||
|
$fStartLoop = $fStart;
|
||||||
|
while ($oContact = $oSet->Fetch()) {
|
||||||
|
if (0 == ($idx % 100)) {
|
||||||
|
$fDuration = microtime(true) - $fStartLoop;
|
||||||
|
$iMemoryPeakUsage = memory_get_peak_usage();
|
||||||
|
if ($iInitialPeak === 0) {
|
||||||
|
$iInitialPeak = $iMemoryPeakUsage;
|
||||||
|
$sInitialPeak = \utils::BytesToFriendlyFormat($iInitialPeak, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sCurrPeak = \utils::BytesToFriendlyFormat($iMemoryPeakUsage, 4);
|
||||||
|
echo "$idx ".sprintf('%.1f ms', $fDuration * 1000)." - Peak Memory Usage: $sCurrPeak\n";
|
||||||
|
$this->assertTrue($iMemoryPeakUsage === $iInitialPeak, "Peak memory changed from $sInitialPeak to $sCurrPeak after $idx fetch loops");
|
||||||
|
$fStartLoop = microtime(true);
|
||||||
|
|
||||||
|
$fTotalDuration = microtime(true) - $fStart;
|
||||||
|
$sTotalDuration = sprintf('%.3f s', $fTotalDuration);
|
||||||
|
$this->assertTrue($fTotalDuration < $fMaxExecutionTimeAllowed, "Total execution time $sTotalDuration should be < $fMaxExecutionTimeAllowed ($idx fetch loops)");
|
||||||
|
}
|
||||||
|
$idx++;
|
||||||
|
}
|
||||||
|
$fTotalDuration = microtime(true) - $fStart;
|
||||||
|
$sTotalDuration = sprintf('%.3f s', $fTotalDuration);
|
||||||
|
echo "Total duration: $sTotalDuration\n\n";
|
||||||
|
$this->assertTrue($fTotalDuration < $fMaxExecutionTimeAllowed, "Total execution time $sTotalDuration should be < $fMaxExecutionTimeAllowed");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,12 @@
|
|||||||
printerClass="\Sempro\PHPUnitPrettyPrinter\PrettyPrinterForPhpUnit9"
|
printerClass="\Sempro\PHPUnitPrettyPrinter\PrettyPrinterForPhpUnit9"
|
||||||
>
|
>
|
||||||
|
|
||||||
|
<extensions>
|
||||||
|
<extension class="Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook" />
|
||||||
|
</extensions>
|
||||||
|
|
||||||
<php>
|
<php>
|
||||||
|
<ini name="memory_limit" value="512M"/>
|
||||||
<ini name="error_reporting" value="E_ALL"/>
|
<ini name="error_reporting" value="E_ALL"/>
|
||||||
<ini name="display_errors" value="On"/>
|
<ini name="display_errors" value="On"/>
|
||||||
<ini name="log_errors" value="On"/>
|
<ini name="log_errors" value="On"/>
|
||||||
@@ -29,6 +34,9 @@
|
|||||||
|
|
||||||
<testsuites>
|
<testsuites>
|
||||||
<!-- Unitary tests -->
|
<!-- Unitary tests -->
|
||||||
|
<testsuite name="Perf">
|
||||||
|
<directory>perf-tests</directory>
|
||||||
|
</testsuite>
|
||||||
<testsuite name="Application">
|
<testsuite name="Application">
|
||||||
<directory>unitary-tests/application</directory>
|
<directory>unitary-tests/application</directory>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user