mirror of
https://github.com/Combodo/iTop.git
synced 2026-05-21 16:22:20 +02:00
Merge branch 'support/3.1' into saas/3.1
This commit is contained in:
@@ -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');
|
||||||
|
|||||||
@@ -116,22 +116,39 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -142,7 +159,7 @@ class Dict
|
|||||||
$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
|
||||||
//
|
//
|
||||||
@@ -151,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 ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -177,7 +194,8 @@ 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);
|
||||||
|
|
||||||
@@ -187,7 +205,12 @@ class Dict
|
|||||||
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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',
|
||||||
));
|
));
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -838,7 +838,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~~',
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ 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',
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ use CMDBSource;
|
|||||||
use MySQLTransactionNotClosedException;
|
use MySQLTransactionNotClosedException;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use SetupUtils;
|
use SetupUtils;
|
||||||
|
use const DEBUG_BACKTRACE_IGNORE_ARGS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class to extend for tests that DO NOT need to access the DataModel or the Database
|
* Helper class to extend for tests that DO NOT need to access the DataModel or the Database
|
||||||
@@ -104,22 +105,10 @@ abstract class ItopTestCase extends TestCase
|
|||||||
if (defined('APPROOT')) {
|
if (defined('APPROOT')) {
|
||||||
return APPROOT;
|
return APPROOT;
|
||||||
}
|
}
|
||||||
$sSearchPath = __DIR__;
|
|
||||||
for ($iDepth = 0; $iDepth < 8; $iDepth++) {
|
$sAppRootPath = static::GetFirstDirUpContainingFile(__DIR__, 'approot.inc.php');
|
||||||
if (file_exists($sSearchPath.'/approot.inc.php')) {
|
|
||||||
break;
|
return $sAppRootPath . '/';
|
||||||
}
|
|
||||||
$iOffsetSep = strrpos($sSearchPath, '/');
|
|
||||||
if ($iOffsetSep === false) {
|
|
||||||
$iOffsetSep = strrpos($sSearchPath, '\\');
|
|
||||||
if ($iOffsetSep === false) {
|
|
||||||
// Do not throw an exception here as PHPUnit will not show it clearly when determing the list of test to perform
|
|
||||||
return 'Could not find the approot file in '.$sSearchPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$sSearchPath = substr($sSearchPath, 0, $iOffsetSep);
|
|
||||||
}
|
|
||||||
return $sSearchPath.'/';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -158,6 +147,23 @@ abstract class ItopTestCase extends TestCase
|
|||||||
require_once $this->GetAppRoot() . $sFileRelPath;
|
require_once $this->GetAppRoot() . $sFileRelPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to load a module file. The caller test must be in that module !
|
||||||
|
* Will browse dir up to find a module.*.php
|
||||||
|
*
|
||||||
|
* @param string $sFileRelPath for example 'portal/src/Helper/ApplicationHelper.php'
|
||||||
|
* @since 2.7.10 3.1.1 3.2.0 N°6709 method creation
|
||||||
|
*/
|
||||||
|
protected function RequireOnceCurrentModuleFile(string $sFileRelPath): void
|
||||||
|
{
|
||||||
|
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
|
||||||
|
$sCallerFileFullPath = $aStack[0]['file'];
|
||||||
|
$sCallerDir = dirname($sCallerFileFullPath);
|
||||||
|
|
||||||
|
$sModuleRootPath = static::GetFirstDirUpContainingFile($sCallerDir, 'module.*.php');
|
||||||
|
require_once $sModuleRootPath . $sFileRelPath;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Require once a unit test file (eg. a mock class) from its relative path from the *current* dir.
|
* Require once a unit test file (eg. a mock class) from its relative path from the *current* dir.
|
||||||
* This ensure that required files don't crash when unit tests dir is moved in the iTop structure (see N°5608)
|
* This ensure that required files don't crash when unit tests dir is moved in the iTop structure (see N°5608)
|
||||||
@@ -175,6 +181,26 @@ abstract class ItopTestCase extends TestCase
|
|||||||
require_once $sCallerDirAbsPath . DIRECTORY_SEPARATOR . $sFileRelPath;
|
require_once $sCallerDirAbsPath . DIRECTORY_SEPARATOR . $sFileRelPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function GetFirstDirUpContainingFile(string $sSearchPath, string $sFileToFindGlobPattern): ?string
|
||||||
|
{
|
||||||
|
for ($iDepth = 0; $iDepth < 8; $iDepth++) {
|
||||||
|
$aGlobFiles = glob($sSearchPath . '/' . $sFileToFindGlobPattern);
|
||||||
|
if (is_array($aGlobFiles) && (count($aGlobFiles) > 0)) {
|
||||||
|
return $sSearchPath . '/';
|
||||||
|
}
|
||||||
|
$iOffsetSep = strrpos($sSearchPath, '/');
|
||||||
|
if ($iOffsetSep === false) {
|
||||||
|
$iOffsetSep = strrpos($sSearchPath, '\\');
|
||||||
|
if ($iOffsetSep === false) {
|
||||||
|
// Do not throw an exception here as PHPUnit will not show it clearly when determing the list of test to perform
|
||||||
|
return 'Could not find the approot file in ' . $sSearchPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$sSearchPath = substr($sSearchPath, 0, $iOffsetSep);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
protected function debug($sMsg)
|
protected function debug($sMsg)
|
||||||
{
|
{
|
||||||
if (static::$DEBUG_UNIT_TEST) {
|
if (static::$DEBUG_UNIT_TEST) {
|
||||||
@@ -249,9 +275,8 @@ abstract class ItopTestCase extends TestCase
|
|||||||
return $method->invokeArgs($oObject, $aArgs);
|
return $method->invokeArgs($oObject, $aArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 3.1.0
|
* @since 2.7.10 3.1.0
|
||||||
*/
|
*/
|
||||||
public function GetNonPublicStaticProperty(string $sClass, string $sProperty)
|
public function GetNonPublicStaticProperty(string $sClass, string $sProperty)
|
||||||
{
|
{
|
||||||
@@ -278,7 +303,7 @@ abstract class ItopTestCase extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 3.1.0
|
* @since 2.7.10 3.1.0
|
||||||
*/
|
*/
|
||||||
private function GetProperty(string $sClass, string $sProperty): \ReflectionProperty
|
private function GetProperty(string $sClass, string $sProperty): \ReflectionProperty
|
||||||
{
|
{
|
||||||
@@ -304,7 +329,7 @@ abstract class ItopTestCase extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 3.1.0
|
* @since 2.7.10 3.1.0
|
||||||
*/
|
*/
|
||||||
public function SetNonPublicStaticProperty(string $sClass, string $sProperty, $value)
|
public function SetNonPublicStaticProperty(string $sClass, string $sProperty, $value)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user