Cleanup and optimization of the handling/loading of the dictionary files.

SVN:trunk[3978]
This commit is contained in:
Denis Flaven
2016-04-04 13:44:15 +00:00
parent b741532231
commit f3773f6047
7 changed files with 234 additions and 188 deletions

View File

@@ -77,7 +77,6 @@ class Config
protected $m_aDataModels;
protected $m_aWebServiceCategories;
protected $m_aAddons;
protected $m_aDictionaries;
protected $m_aModuleSettings;
@@ -1053,7 +1052,6 @@ class Config
// Default AddOn, always present can be moved to an official iTop Module later if needed
'user rights' => 'addons/userrights/userrightsprofile.class.inc.php',
);
$this->m_aDictionaries = self::ScanDictionariesDir();
foreach($this->m_aSettings as $sPropCode => $aSettingInfo)
{
@@ -1173,10 +1171,7 @@ class Config
// Add one, by default
$MyModules['addons']['user rights'] = '/addons/userrights/userrightsnull.class.inc.php';
}
if (!array_key_exists('dictionaries', $MyModules))
{
throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules[\'dictionaries\']'));
}
$this->m_aAppModules = $MyModules['application'];
$this->m_aDataModels = $MyModules['business'];
if (isset($MyModules['webservices']))
@@ -1184,7 +1179,6 @@ class Config
$this->m_aWebServiceCategories = $MyModules['webservices'];
}
$this->m_aAddons = $MyModules['addons'];
$this->m_aDictionaries = $MyModules['dictionaries'];
foreach($MySettings as $sPropCode => $rawvalue)
{
@@ -1304,15 +1298,6 @@ class Config
$this->m_aAddons = $aAddons;
}
public function GetDictionaries()
{
return $this->m_aDictionaries;
}
public function SetDictionaries($aDictionaries)
{
$this->m_aDictionaries = $aDictionaries;
}
public function GetDBHost()
{
return $this->m_sDBHost;
@@ -1608,10 +1593,6 @@ class Config
{
$aSettings['addon_list'][] = $sFile;
}
foreach($this->m_aDictionaries as $sFile)
{
$aSettings['dictionary_list'][] = $sFile;
}
return $aSettings;
}
@@ -1780,12 +1761,6 @@ class Config
fwrite($hFile, "\t\t'$sKey' => '$sFile',\n");
}
fwrite($hFile, "\t),\n");
fwrite($hFile, "\t'dictionaries' => array (\n");
foreach($this->m_aDictionaries as $sFile)
{
fwrite($hFile, "\t\t'$sFile',\n");
}
fwrite($hFile, "\t),\n");
fwrite($hFile, ");\n");
fwrite($hFile, '?'.'>'); // Avoid perturbing the syntax highlighting !
return fclose($hFile);
@@ -1795,26 +1770,6 @@ class Config
throw new ConfigException("Could not write to configuration file", array('file' => $sFileName));
}
}
protected static function ScanDictionariesDir()
{
$aResult = array();
// Populate automatically the list of dictionary files
$sDir = APPROOT.'/dictionaries';
if ($hDir = @opendir($sDir))
{
while (($sFile = readdir($hDir)) !== false)
{
$aMatches = array();
if (preg_match("/^([^\.]+\.)?dictionary\.itop\.(ui|core)\.php$/i", $sFile, $aMatches)) // Dictionary files named like [<Lang>.]dictionary.[core|ui].php are loaded automatically
{
$aResult[] = 'dictionaries/'.$sFile;
}
}
closedir($hDir);
}
return $aResult;
}
/**
* Helper function to initialize a configuration from the page arguments
@@ -1882,7 +1837,6 @@ class Config
}
$aDataModels = $oEmptyConfig->GetDataModels();
$aWebServiceCategories = $oEmptyConfig->GetWebServiceCategories();
$aDictionaries = $oEmptyConfig->GetDictionaries();
// Merge the values with the ones provided by the modules
// Make sure when don't load the same file twice...
@@ -1935,15 +1889,6 @@ class Config
$this->SetAppModules($aAppModules);
$this->SetDataModels($aDataModels);
$this->SetWebServiceCategories($aWebServiceCategories);
// Scan dictionaries
//
foreach (glob(APPROOT.$sModulesDir.'/dictionaries/*.dict.php') as $sFilePath)
{
$sFile = basename($sFilePath);
$aDictionaries[] = $sModulesDir.'/dictionaries/'.$sFile;
}
$this->SetDictionaries($aDictionaries);
}
}
@@ -1970,7 +1915,6 @@ class Config
$sNewPrefix = 'env-'.$sTargetEnv.'/';
self::ChangePrefix($this->m_aDataModels, $sSearchPrefix, $sNewPrefix);
self::ChangePrefix($this->m_aWebServiceCategories, $sSearchPrefix, $sNewPrefix);
self::ChangePrefix($this->m_aDictionaries, $sSearchPrefix, $sNewPrefix);
}
/**

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -57,26 +57,13 @@ define('DICT_ERR_EXCEPTION', 2); // when a string is missing, throw an exception
class Dict
{
protected static $m_bTraceFiles = false;
protected static $m_aEntryFiles = array();
protected static $m_iErrorMode = DICT_ERR_STRING;
protected static $m_sDefaultLanguage = 'EN US';
protected static $m_sCurrentLanguage = null; // No language selected by default
protected static $m_aLanguages = array(); // array( code => array( 'description' => '...', 'localized_description' => '...') ...)
protected static $m_aData = array();
public static function EnableTraceFiles()
{
self::$m_bTraceFiles = true;
}
public static function GetEntryFiles()
{
return self::$m_aEntryFiles;
}
protected static $m_sApplicationPrefix = null;
public static function SetDefaultLanguage($sLanguageCode)
{
@@ -119,11 +106,20 @@ class Dict
self::$m_iErrorMode = $iErrorMode;
}
/**
* Returns a localised string from the dictonary
* @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
* @throws DictExceptionMissingString
* @return unknown|Ambigous <>|string
*/
public static function S($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
{
// Attempt to find the string in the user language
//
self::InitLangIfNeeded(self::GetUserLanguage());
if (!array_key_exists(self::GetUserLanguage(), self::$m_aData))
{
// It may happen, when something happens before the dictionnaries get loaded
@@ -138,6 +134,8 @@ class Dict
{
// Attempt to find the string in the default language
//
self::InitLangIfNeeded(self::$m_sDefaultLanguage);
$aDefaultDictionary = self::$m_aData[self::$m_sDefaultLanguage];
if (array_key_exists($sStringCode, $aDefaultDictionary))
{
@@ -175,6 +173,12 @@ class Dict
}
/**
* Formats a localized string with numbered placeholders (%1$s...) for the additional arguments
* See vsprintf for more information about the syntax of the placeholders
* @param string $sFormatCode
* @return string
*/
public static function Format($sFormatCode /*, ... arguments ....*/)
{
$sLocalizedFormat = self::S($sFormatCode);
@@ -189,43 +193,95 @@ class Dict
return vsprintf($sLocalizedFormat, $aArguments);
}
// sLanguageCode: Code identifying the language i.e. FR-FR
// sEnglishLanguageDesc: Description of the language code, in English. i.e. French (France)
// sLocalizedLanguageDesc: Description of the language code, in its own language. i.e. Français (France)
// aEntries: Hash array of dictionnary entries
// ~~ or ~* can be used to indicate entries still to be translated.
public static function Add($sLanguageCode, $sEnglishLanguageDesc, $sLocalizedLanguageDesc, $aEntries)
/**
* 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 hash $aEntries Hash array of dictionnary entries
*/
public static function SetEntries($sLanguageCode, $aEntries)
{
if (self::$m_bTraceFiles)
self::$m_aData[$sLanguageCode] = $aEntries;
}
/**
* Set the list of available languages
* @param hash $aLanguagesList
*/
public static function SetLanguagesList($aLanguagesList)
{
self::$m_aLanguages = $aLanguagesList;
}
/**
* Load a language from the language dictionary, if not already loaded
* @param string $sLangCode Language code
* @return boolean
*/
public static function InitLangIfNeeded($sLangCode)
{
if (array_key_exists($sLangCode, self::$m_aData)) return true;
$bResult = false;
if (function_exists('apc_fetch') && (self::$m_sApplicationPrefix !== null))
{
$aBacktrace = debug_backtrace();
$sFile = $aBacktrace[0]["file"];
foreach($aEntries as $sKey => $sValue)
// Note: For versions of APC older than 3.0.17, fetch() accepts only one parameter
//
self::$m_aData[$sLangCode] = apc_fetch(self::$m_sApplicationPrefix.'-dict-'.$sLangCode);
if (self::$m_aData[$sLangCode] === false)
{
self::$m_aEntryFiles[$sLanguageCode][$sKey] = array(
'file' => $sFile,
'value' => $sValue
);
unset(self::$m_aData[$sLangCode]);
}
else
{
$bResult = true;
}
}
if (!array_key_exists($sLanguageCode, self::$m_aLanguages))
if (!$bResult)
{
self::$m_aLanguages[$sLanguageCode] = array('description' => $sEnglishLanguageDesc, 'localized_description' => $sLocalizedLanguageDesc);
self::$m_aData[$sLanguageCode] = array();
}
foreach($aEntries as $sCode => $sValue)
{
self::$m_aData[$sLanguageCode][$sCode] = self::FilterString($sValue);
$sDictFile = APPROOT.'env-'.utils::GetCurrentEnvironment().'/dictionaries/'.str_replace(' ', '-', strtolower($sLangCode)).'.dict.php';
require_once($sDictFile);
if (function_exists('apc_store') && (self::$m_sApplicationPrefix !== null))
{
apc_store(self::$m_sApplicationPrefix.'-dict-'.$sLangCode, self::$m_aData[$sLangCode]);
}
$bResult = true;
}
return $bResult;
}
/**
* Enable caching (cached using APC)
* @param string $sApplicationPrefix The prefix for uniquely identiying this iTop instance
*/
public static function EnableCache($sApplicationPrefix)
{
self::$m_sApplicationPrefix = $sApplicationPrefix;
}
/**
* Reset the cached entries (cached using APC)
* @param string $sApplicationPrefix The prefix for uniquely identiying this iTop instance
*/
public static function ResetCache($sApplicationPrefix)
{
if (function_exists('apc_delete'))
{
foreach(self::$m_aLanguages as $sLang => $void)
{
apc_delete($sApplicationPrefix.'-dict-'.$sLang);
}
}
}
/////////////////////////////////////////////////////////////////////////
/**
* Clone a string in every language (if it exists in that language)
*/
*/
public static function CloneString($sSourceCode, $sDestCode)
{
foreach(self::$m_aLanguages as $sLanguageCode => $foo)
@@ -236,14 +292,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]))
@@ -251,7 +307,7 @@ class Dict
$aMissing[$sStringCode] = $sValue;
}
}
foreach (self::$m_aData[$sLanguageCode] as $sStringCode => $sValue)
{
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageRef]))
@@ -279,57 +335,24 @@ class Dict
{
MyHelpers::var_dump_html(self::$m_aData);
}
public static function InCache($sApplicationPrefix)
{
if (function_exists('apc_fetch'))
{
$bResult = false;
// Note: For versions of APC older than 3.0.17, fetch() accepts only one parameter
//
self::$m_aData = apc_fetch($sApplicationPrefix.'-dict');
if (is_bool(self::$m_aData) && (self::$m_aData === false))
{
self::$m_aData = array();
}
else
{
self::$m_aLanguages = apc_fetch($sApplicationPrefix.'-languages');
if (is_bool(self::$m_aLanguages) && (self::$m_aLanguages === false))
{
self::$m_aLanguages = array();
}
else
{
$bResult = true;
}
}
return $bResult;
}
return false;
}
public static function InitCache($sApplicationPrefix)
{
if (function_exists('apc_store'))
{
apc_store($sApplicationPrefix.'-languages', self::$m_aLanguages);
apc_store($sApplicationPrefix.'-dict', self::$m_aData);
}
}
public static function ResetCache($sApplicationPrefix)
// Obsolete: only used by the setup/compiler which replaces this method invocation by its own handler !!
// sLanguageCode: Code identifying the language i.e. FR-FR
// sEnglishLanguageDesc: Description of the language code, in English. i.e. French (France)
// sLocalizedLanguageDesc: Description of the language code, in its own language. i.e. Français (France)
// aEntries: Hash array of dictionnary entries
// ~~ or ~* can be used to indicate entries still to be translated.
public static function Add($sLanguageCode, $sEnglishLanguageDesc, $sLocalizedLanguageDesc, $aEntries)
{
if (function_exists('apc_delete'))
if (!array_key_exists($sLanguageCode, self::$m_aLanguages))
{
apc_delete($sApplicationPrefix.'-languages');
apc_delete($sApplicationPrefix.'-dict');
self::$m_aLanguages[$sLanguageCode] = array('description' => $sEnglishLanguageDesc, 'localized_description' => $sLocalizedLanguageDesc);
self::$m_aData[$sLanguageCode] = array();
}
foreach($aEntries as $sCode => $sValue)
{
self::$m_aData[$sLanguageCode][$sCode] = self::FilterString($sValue);
}
}
protected static function FilterString($s)
{
return str_replace(array('~~', '~*'), '', $s);
}
}
?>

View File

@@ -4219,15 +4219,13 @@ abstract class MetaModel
// needed when some error occur
$sAppIdentity = 'itop-'.MetaModel::GetEnvironmentId();
$bDictInitializedFromData = false;
if (!self::$m_bUseAPCCache || !Dict::InCache($sAppIdentity))
if (self::$m_bUseAPCCache)
{
$bDictInitializedFromData = true;
foreach (self::$m_oConfig->GetDictionaries() as $sModule => $sToInclude)
{
self::IncludeModule('dictionaries', $sToInclude);
}
}
// Set the language... after the dictionaries have been loaded!
Dict::EnableCache($sAppIdentity);
}
require_once(APPROOT.'env-'.utils::GetCurrentEnvironment().'/dictionaries/languages.php');
// Set the default language...
Dict::SetDefaultLanguage(self::$m_oConfig->GetDefaultLanguage());
// Romain: this is the only way I've found to cope with the fact that
@@ -4332,11 +4330,6 @@ abstract class MetaModel
$oKPI->ComputeAndReport('Metamodel APC (store)');
}
}
if (self::$m_bUseAPCCache && $bDictInitializedFromData)
{
Dict::InitCache($sAppIdentity);
}
self::$m_sDBName = $sSource;
self::$m_sTablePrefix = $sTablePrefix;

View File

@@ -473,6 +473,9 @@ class ApplicationInstaller
$oFactory = new ModelFactory($aDirsToScan);
$oDictModule = new MFDictModule('dictionaries', 'iTop Dictionaries', APPROOT.'dictionaries');
$oFactory->LoadModule($oDictModule);
$sDeltaFile = APPROOT.'core/datamodel.core.xml';
if (file_exists($sDeltaFile))
{

View File

@@ -424,10 +424,7 @@ EOF;
}
$oDictionaries = $this->oFactory->GetNodes('dictionaries/dictionary');
foreach($oDictionaries as $oDictionaryNode)
{
$this->CompileDictionary($oDictionaryNode, $sTempTargetDir, $sFinalTargetDir);
}
$this->CompileDictionaries($oDictionaries, $sTempTargetDir, $sFinalTargetDir);
// Compile the branding
//
@@ -2084,37 +2081,63 @@ EOF;
return $sPHP;
} // function CompileUserRights
protected function CompileDictionary($oDictionaryNode, $sTempTargetDir, $sFinalTargetDir)
protected function CompileDictionaries($oDictionaries, $sTempTargetDir, $sFinalTargetDir)
{
$sLang = $oDictionaryNode->getAttribute('id');
$sEnglishLanguageDesc = $oDictionaryNode->GetChildText('english_description');
$sLocalizedLanguageDesc = $oDictionaryNode->GetChildText('localized_description');
$aEntriesPHP = array();
$oEntries = $oDictionaryNode->GetUniqueElement('entries');
foreach($oEntries->getElementsByTagName('entry') as $oEntry)
$aLanguages = array();
foreach($oDictionaries as $oDictionaryNode)
{
$sStringCode = $oEntry->getAttribute('id');
$sValue = $oEntry->GetText();
$aEntriesPHP[] = "\t'$sStringCode' => ".self::QuoteForPHP($sValue, true).",";
}
$sEntriesPHP = implode("\n", $aEntriesPHP);
$sLang = $oDictionaryNode->getAttribute('id');
$sEnglishLanguageDesc = $oDictionaryNode->GetChildText('english_description');
$sLocalizedLanguageDesc = $oDictionaryNode->GetChildText('localized_description');
$aLanguages[$sLang] = array('description' => $sEnglishLanguageDesc, 'localized_description' => $sLocalizedLanguageDesc);
$sEscEnglishLanguageDesc = self::QuoteForPHP($sEnglishLanguageDesc);
$sEscLocalizedLanguageDesc = self::QuoteForPHP($sLocalizedLanguageDesc);
$sPHPDict =
$aEntriesPHP = array();
$oEntries = $oDictionaryNode->GetUniqueElement('entries');
foreach ($oEntries->getElementsByTagName('entry') as $oEntry)
{
$sStringCode = $oEntry->getAttribute('id');
$sValue = $oEntry->GetText();
$aEntriesPHP[] = "\t'$sStringCode' => ".self::QuoteForPHP(self::FilterDictString($sValue), true).",";
}
$sEntriesPHP = implode("\n", $aEntriesPHP);
$sPHPDict =
<<<EOF
<?php
//
// Dictionary built by the compiler for the language "$sLang"
//
Dict::Add('$sLang', $sEscEnglishLanguageDesc, $sEscLocalizedLanguageDesc, array(
Dict::SetEntries('$sLang', array(
$sEntriesPHP
));
EOF;
$sSafeLang = str_replace(' ', '-', strtolower(trim($sLang)));
$sDictFile = $sTempTargetDir.'/dictionaries/'.$sSafeLang.'.dict.php';
file_put_contents($sDictFile, $sPHPDict);
$sSafeLang = str_replace(' ', '-', strtolower(trim($sLang)));
$sDictFile = $sTempTargetDir.'/dictionaries/'.$sSafeLang.'.dict.php';
file_put_contents($sDictFile, $sPHPDict);
}
$sLanguagesFile = $sTempTargetDir.'/dictionaries/languages.php';
$sLanguagesDump = var_export($aLanguages, true);
$sLanguagesFileContent =
<<<EOF
<?php
//
// Dictionary index built by the compiler
//
Dict::SetLanguagesList(
$sLanguagesDump
);
EOF;
file_put_contents($sLanguagesFile, $sLanguagesFileContent);
}
protected static function FilterDictString($s)
{
if (strpos($s, '~') !== false)
{
return str_replace(array('~~', '~*'), '', $s);
}
return $s;
}
// Transform the file references into the corresponding filename (and create the file in the relevant directory)

View File

@@ -206,6 +206,54 @@ class MFCoreModule extends MFModule
}
}
/**
* MFDictModule: an optional module, consisting only of dictionaries
* @package ModelFactory
*/
class MFDictModule extends MFModule
{
public function __construct($sName, $sLabel, $sRootDir)
{
$this->sId = $sName;
$this->sName = $sName;
$this->sVersion = '1.0';
$this->sRootDir = $sRootDir;
$this->sLabel = $sLabel;
$this->aDataModels = array();
}
public function GetRootDir()
{
return '';
}
public function GetModuleDir()
{
return '';
}
public function GetDictionaryFiles()
{
$aDictionaries = array();
if ($hDir = opendir($this->sRootDir))
{
while (($sFile = readdir($hDir)) !== false)
{
$aMatches = array();
if (preg_match("/^.*dictionary\\.itop.*.php$/i", $sFile, $aMatches)) // Dictionary files are named like <Lang>.dict.<ModuleName>.php
{
$aDictionaries[] = $this->sRootDir.'/'.$sFile;
}
}
closedir($hDir);
}
return $aDictionaries;
}
}
/**
* ModelFactory: the class that manages the in-memory representation of the XML MetaModel
* @package ModelFactory
@@ -549,14 +597,23 @@ class ModelFactory
$oXmlEntry->appendChild($oXmlValue);
if (array_key_exists($sLanguageCode, $this->aDictKeys) && array_key_exists($sCode, $this->aDictKeys[$sLanguageCode]))
{
$oXmlEntries->RedefineChildNode($oXmlEntry);
$oMe = $this->aDictKeys[$sLanguageCode][$sCode];
$sFlag = $oMe->getAttribute('_alteration');
$oMe->parentNode->replaceChild($oXmlEntry, $oMe);
$sNewFlag = $sFlag;
if ($sFlag == '')
{
$sNewFlag = 'replaced';
}
$oXmlEntry->setAttribute('_alteration', $sNewFlag);
}
else
{
$oXmlEntry->setAttribute('_alteration', 'added');
$oXmlEntries->appendChild($oXmlEntry);
}
$this->aDictKeys[$sLanguageCode][$sCode] = true;
$this->aDictKeys[$sLanguageCode][$sCode] = $oXmlEntry;
}
}
}

View File

@@ -341,6 +341,9 @@ class RunTimeEnvironment
// Do load the required modules
//
$oDictModule = new MFDictModule('dictionaries', 'iTop Dictionaries', APPROOT.'dictionaries');
$aRet[] = $oDictModule;
$oFactory = new ModelFactory($aDirsToCompile);
$sDeltaFile = APPROOT.'core/datamodel.core.xml';
if (file_exists($sDeltaFile))