Merge branch 'support/2.7' into support/3.0

This commit is contained in:
odain
2023-09-13 10:01:15 +02:00
10 changed files with 360 additions and 53 deletions

View File

@@ -3,7 +3,7 @@
//
// 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
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
@@ -116,33 +116,50 @@ class Dict
* @return string
*/
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
//
$sLangCode = self::GetUserLanguage();
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
return $sStringCode;
return [ 'label' => $sStringCode, 'lang' => $sLangCode ];
}
$aCurrentDictionary = self::$m_aData[$sLangCode];
if (is_array($aCurrentDictionary) && array_key_exists($sStringCode, $aCurrentDictionary))
{
return $aCurrentDictionary[$sStringCode];
return [ 'label' => $aCurrentDictionary[$sStringCode], 'lang' => $sLangCode ];
}
if (!$bUserLanguageOnly)
{
// Attempt to find the string in the default language
//
self::InitLangIfNeeded(self::$m_sDefaultLanguage);
$aDefaultDictionary = self::$m_aData[self::$m_sDefaultLanguage];
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
//
@@ -151,17 +168,17 @@ class Dict
$aDefaultDictionary = self::$m_aData['EN US'];
if (is_array($aDefaultDictionary) && array_key_exists($sStringCode, $aDefaultDictionary))
{
return $aDefaultDictionary[$sStringCode];
return [ 'label' => $aDefaultDictionary[$sStringCode], 'lang' => 'EN US' ];
}
}
// Could not find the string...
//
if (is_null($sDefault))
{
return $sStringCode;
return [ 'label' => $sStringCode, 'lang' => null ];
}
return $sDefault;
return [ 'label' => $sDefault, 'lang' => null ];
}
@@ -177,19 +194,25 @@ class Dict
*/
public static function Format($sFormatCode /*, ... arguments ....*/)
{
$sLocalizedFormat = self::S($sFormatCode);
['label' => $sLocalizedFormat, 'lang' => $sLangCode] = self::GetLabelAndLangCode($sFormatCode);
$aArguments = func_get_args();
array_shift($aArguments);
if ($sLocalizedFormat == $sFormatCode)
{
// Make sure the information will be displayed (ex: an error occuring before the dictionary gets loaded)
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)
* @param string $sLanguageCode Code identifying the language i.e. 'FR-FR', 'EN-US'
@@ -199,7 +222,7 @@ class Dict
{
self::$m_aData[$sLanguageCode] = $aEntries;
}
/**
* Set the list of available languages
* @param hash $aLanguagesList
@@ -260,7 +283,7 @@ class Dict
{
$sDictFile = APPROOT.'env-'.utils::GetCurrentEnvironment().'/dictionaries/'.str_replace(' ', '-', strtolower($sLangCode)).'.dict.php';
require_once($sDictFile);
if (self::GetApcService()->function_exists('apc_store')
&& (self::$m_sApplicationPrefix !== null))
{
@@ -270,7 +293,7 @@ class Dict
}
return $bResult;
}
/**
* Enable caching (cached using APC)
* @param string $sApplicationPrefix The prefix for uniquely identiying this iTop instance
@@ -313,14 +336,14 @@ class Dict
}
}
}
public static function MakeStats($sLanguageCode, $sLanguageRef = 'EN US')
{
$aMissing = array(); // Strings missing for the target language
$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
$aOK = array(); // Strings having different values in both dictionaries
foreach (self::$m_aData[$sLanguageRef] as $sStringCode => $sValue)
{
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageCode]))
@@ -328,7 +351,7 @@ class Dict
$aMissing[$sStringCode] = $sValue;
}
}
foreach (self::$m_aData[$sLanguageCode] as $sStringCode => $sValue)
{
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageRef]))
@@ -351,7 +374,7 @@ class Dict
}
return array($aMissing, $aUnexpected, $aNotTranslated, $aOK);
}
public static function Dump()
{
MyHelpers::var_dump_html(self::$m_aData);
@@ -374,7 +397,7 @@ class Dict
// No need to actually load the strings since it's only used to know the list of languages
// at setup time !!
}
/**
* 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
@@ -387,7 +410,7 @@ class Dict
self::InitLangIfNeeded(self::$m_sDefaultLanguage);
$aEntries = array();
$iLength = strlen($sStartingWith);
// First prefill the array with entries from the default language
foreach(self::$m_aData[self::$m_sDefaultLanguage] as $sCode => $sEntry)
{
@@ -396,7 +419,7 @@ class Dict
$aEntries[$sCode] = $sEntry;
}
}
// Now put (overwrite) the entries for the user language
foreach(self::$m_aData[self::GetUserLanguage()] as $sCode => $sEntry)
{

View File

@@ -20,7 +20,7 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
'Core:DeletedObjectLabel' => '%1s (deleted)~~',
'Core:DeletedObjectLabel' => '%1$s (deleted)~~',
'Core:DeletedObjectTip' => 'The object has been deleted on %1$s (%2$s)~~',
'Core:UnknownObjectLabel' => 'Object not found (class: %1$s, id: %2$d)~~',
@@ -204,8 +204,8 @@ Operators:<br/>
'Core:FriendlyName-Description' => 'Friendly name~~',
'Core:AttributeTag' => 'Tags~~',
'Core:AttributeTag+' => '',
'Core:AttributeTag+' => 'Tags~~',
'Core:Context=REST/JSON' => 'REST~~',
'Core:Context=Synchro' => 'Synchro~~',
'Core:Context=Setup' => 'Setup~~',

View File

@@ -443,7 +443,7 @@ We hope youll enjoy this version as much as we enjoyed imagining and creating
'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: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érdezs futtatása közben: %1$s',
'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ő.',
@@ -1050,7 +1050,7 @@ Akció kiváltó okhoz rendelésekor kap egy sorszámot , amely meghatározza az
'UI:Deadline_LessThan1Min' => '< 1 perc',
'UI:Deadline_Minutes' => '%1$d perc',
'UI:Deadline_Hours_Minutes' => '%1$dóra %2$dperc',
'UI:Deadline_Days_Hours_Minutes' => '%1$nap %2$dóra %3$dperc',
'UI:Deadline_Days_Hours_Minutes' => '%1$dnap %2$dóra %3$dperc',
'UI:Help' => 'Segítség',
'UI:PasswordConfirm' => 'Jóváhagyás',
'UI:BeforeAdding_Class_ObjectsSaveThisObject' => '%1$s objektumok hozzáadása előtt mentse ezt az objektumot',

View File

@@ -20,8 +20,8 @@
* @licence http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Core:DeletedObjectLabel' => '%1s (削除されました)',
'Core:DeletedObjectTip' => 'オブジェクトは削除されました %1$s (%2$s)',
'Core:DeletedObjectLabel' => '%1$s (削除されました)',
'Core:DeletedObjectTip' => 'オブジェクトは削除されました %1$s (%2$s)',
'Core:UnknownObjectLabel' => 'オブジェクトは見つかりません (クラス: %1$s, id: %2$d)',
'Core:UnknownObjectTip' => 'オブジェクトは見つかりません。しばらく前に削除され、その後ログが削除されたかもしれません。',
@@ -205,7 +205,7 @@ Operators:<br/>
'Core:AttributeTag' => 'Tags~~',
'Core:AttributeTag+' => 'Tags~~',
'Core:Context=REST/JSON' => 'REST~~',
'Core:Context=Synchro' => 'Synchro~~',
'Core:Context=Setup' => 'Setup~~',

View File

@@ -820,7 +820,7 @@ We hope youll enjoy this version as much as we enjoyed imagining and creating
'UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class' => '%2$sクラスの%1$dオブジェクトの削除',
'UI:Delete:CannotDeleteBecause' => '削除できません: %1$s',
'UI:Delete:ShouldBeDeletedAtomaticallyButNotPossible' => '自動的に削除されるべきですが、出来ません。: %1$s',
'UI:Delete:MustBeDeletedManuallyButNotPossible' => '手動で削除されるべきですが、出来ません。: %1$',
'UI:Delete:MustBeDeletedManuallyButNotPossible' => '手動で削除されるべきですが、出来ません。: %1$s',
'UI:Delete:WillBeDeletedAutomatically' => '自動的に削除されます。',
'UI:Delete:MustBeDeletedManually' => '手動で削除されるべきです。',
'UI:Delete:CannotUpdateBecause_Issue' => '自動的に更新されるべきですが、しかし: %1$s',
@@ -1124,7 +1124,7 @@ We hope youll enjoy this version as much as we enjoyed imagining and creating
'Enum:Undefined' => '未定義',
'UI:DurationForm_Days_Hours_Minutes_Seconds' => '%1$s 日 %2$s 時 %3$s 分 %4$s 秒',
'UI:ModifyAllPageTitle' => '全てを修正',
'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:Menu:ModifyAll' => '修正...',
'UI:Button:ModifyAll' => '全て修正',
@@ -1141,7 +1141,7 @@ We hope youll enjoy this version as much as we enjoyed imagining and creating
'UI:BulkModify_Count_DistinctValues' => '%1$d 個の個別の値:',
'UI:BulkModify:Value_Exists_N_Times' => '%1$s, %2$d 回存在',
'UI:BulkModify:N_MoreValues' => '%1$d 個以上の値...',
'UI:AttemptingToSetAReadOnlyAttribute_Name' => '読み込み専用フィールド %1$にセットしょうとしています。',
'UI:AttemptingToSetAReadOnlyAttribute_Name' => '読み込み専用フィールド %1$sにセットしょうとしています。',
'UI:FailedToApplyStimuli' => 'アクションは失敗しました。',
'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: クラス%3$sの%2$dオブジェクトを修正',
'UI:CaseLogTypeYourTextHere' => 'テキストを入力ください:',

View File

@@ -9,8 +9,8 @@
*
*/
Dict::Add('RU RU', 'Russian', 'Русский', array(
'Core:DeletedObjectLabel' => '%1ы (удален)',
'Core:DeletedObjectTip' => 'Объект был удален %1$s (%2$s)',
'Core:DeletedObjectLabel' => '%1$sы (удален)',
'Core:DeletedObjectTip' => 'Объект был удален %1$s (%2$s)',
'Core:UnknownObjectLabel' => 'Объект не найден (class: %1$s, id: %2$d)',
'Core:UnknownObjectTip' => 'Объект не удается найти. Возможно, он был удален некоторое время назад, и журнал с тех пор был очищен.',
@@ -193,8 +193,8 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
'Core:FriendlyName-Description' => 'Полное название',
'Core:AttributeTag' => 'Тег',
'Core:AttributeTag+' => '',
'Core:AttributeTag+' => 'Тег',
'Core:Context=REST/JSON' => 'REST',
'Core:Context=Synchro' => 'Synchro',
'Core:Context=Setup' => 'Setup',

View File

@@ -430,7 +430,7 @@ We hope youll 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:InvalidGroupByFields' => 'Neplatný zoznam polí pre skupinu podľa: "%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: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).',
@@ -1275,7 +1275,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:Select-Hour' => '%1$s (hodina)',
'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:MissingGroupBy' => 'Prosím zvoľte pole na ktorom objekty budú zoskupené spolu',

View File

@@ -214,8 +214,8 @@ Operators:<br/>
'Core:FriendlyName-Description' => 'Yaygın Adı',
'Core:AttributeTag' => 'Tags~~',
'Core:AttributeTag+' => '',
'Core:AttributeTag+' => 'Tags~~',
'Core:Context=REST/JSON' => 'REST~~',
'Core:Context=Synchro' => 'Synchro~~',
'Core:Context=Setup' => 'Setup~~',
@@ -324,7 +324,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' => '%1$s\'nin değeri %2$s olarak atandı',
'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$snin değeri deiştirildi, önceki değer: %2$s',
'Change:AttName_Changed' => '%1$s değiştirildi',
'Change:AttName_EntryAdded' => '%1$s değiştirilmiş, yeni giriş eklendi.',
'Change:State_Changed_NewValue_OldValue' => 'Changed from %2$s to %1$s~~',

View File

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

View File

@@ -277,6 +277,21 @@ abstract class ItopTestCase extends TestCase
return $method->invokeArgs($oObject, $aArgs);
}
/**
* @param string $sClass
* @param string $sProperty
*
* @return mixed property
*
* @throws \ReflectionException
* @since 2.7.10 3.1.0
*/
public function GetNonPublicStaticProperty(string $sClass, string $sProperty)
{
$oProperty = $this->GetProperty($sClass, $sProperty);
return $oProperty->getValue();
}
/**
* @param object $oObject
@@ -289,11 +304,27 @@ abstract class ItopTestCase extends TestCase
*/
public function GetNonPublicProperty(object $oObject, string $sProperty)
{
$class = new \ReflectionClass(get_class($oObject));
$property = $class->getProperty($sProperty);
$property->setAccessible(true);
$oProperty = $this->GetProperty(get_class($oObject), $sProperty);
return $property->getValue($oObject);
return $oProperty->getValue($oObject);
}
/**
* @param string $sClass
* @param string $sProperty
*
* @return \ReflectionProperty
*
* @throws \ReflectionException
* @since 2.7.10 3.1.0
*/
private function GetProperty(string $sClass, string $sProperty)
{
$oClass = new \ReflectionClass($sClass);
$oProperty = $oClass->getProperty($sProperty);
$oProperty->setAccessible(true);
return $oProperty;
}
/**
@@ -306,11 +337,22 @@ abstract class ItopTestCase extends TestCase
*/
public function SetNonPublicProperty(object $oObject, string $sProperty, $value)
{
$class = new \ReflectionClass(get_class($oObject));
$property = $class->getProperty($sProperty);
$property->setAccessible(true);
$oProperty = $this->GetProperty(get_class($oObject), $sProperty);
$oProperty->setValue($oObject, $value);
}
$property->setValue($oObject, $value);
/**
* @param string $sClass
* @param string $sProperty
* @param $value
*
* @throws \ReflectionException
* @since 2.7.10 3.1.0
*/
public function SetNonPublicStaticProperty(string $sClass, string $sProperty, $value)
{
$oProperty = $this->GetProperty($sClass, $sProperty);
$oProperty->setValue($value);
}
public function RecurseRmdir($dir) {
@@ -383,4 +425,4 @@ abstract class ItopTestCase extends TestCase
}
closedir($dir);
}
}
}