From f3773f6047f42981eeafa5a9f302677a7f060fad Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 4 Apr 2016 13:44:15 +0000 Subject: [PATCH] Cleanup and optimization of the handling/loading of the dictionary files. SVN:trunk[3978] --- core/config.class.inc.php | 58 +------ core/dict.class.inc.php | 207 +++++++++++++---------- core/metamodel.class.php | 19 +-- setup/applicationinstaller.class.inc.php | 3 + setup/compiler.class.inc.php | 71 +++++--- setup/modelfactory.class.inc.php | 61 ++++++- setup/runtimeenv.class.inc.php | 3 + 7 files changed, 234 insertions(+), 188 deletions(-) diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 11f88ae25..1f59406cb 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -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 [.]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); } /** diff --git a/core/dict.class.inc.php b/core/dict.class.inc.php index 2e7ecbf34..ae37ac9b5 100644 --- a/core/dict.class.inc.php +++ b/core/dict.class.inc.php @@ -1,5 +1,5 @@ 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); } } ?> diff --git a/core/metamodel.class.php b/core/metamodel.class.php index f36e64896..79e9ed96f 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -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; diff --git a/setup/applicationinstaller.class.inc.php b/setup/applicationinstaller.class.inc.php index 6224630fc..094a81bfb 100644 --- a/setup/applicationinstaller.class.inc.php +++ b/setup/applicationinstaller.class.inc.php @@ -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)) { diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index 543bfae4c..3b514eae6 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -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 = <<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 .dict..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; } } } diff --git a/setup/runtimeenv.class.inc.php b/setup/runtimeenv.class.inc.php index 8d6560784..7f1c756c3 100644 --- a/setup/runtimeenv.class.inc.php +++ b/setup/runtimeenv.class.inc.php @@ -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))