diff options
author | Fabian Becker <fabian.becker@uni-tuebingen.de> | 2013-09-04 03:17:34 +0400 |
---|---|---|
committer | Fabian Becker <fabian.becker@uni-tuebingen.de> | 2013-09-04 03:17:34 +0400 |
commit | e3bbc07eb21e8a2c1ea53d4ac55d94bf47214353 (patch) | |
tree | c206c253843b41eb987780da3b7a0eea9aac1ae0 /core | |
parent | 515b74c1e6d83651e56b5c56e5e3699c13ea552e (diff) | |
parent | 55def0ef5710771da9677509ff5596efd15365c2 (diff) |
Merge branch 'master' of https://github.com/piwik/piwik
Diffstat (limited to 'core')
-rw-r--r-- | core/Translate.php | 11 | ||||
-rw-r--r-- | core/Translate/Filter/ByBaseTranslations.php | 68 | ||||
-rw-r--r-- | core/Translate/Filter/ByParameterCount.php | 93 | ||||
-rw-r--r-- | core/Translate/Filter/EmptyTranslations.php | 51 | ||||
-rw-r--r-- | core/Translate/Filter/EncodedEntities.php | 49 | ||||
-rw-r--r-- | core/Translate/Filter/FilterAbstract.php | 41 | ||||
-rw-r--r-- | core/Translate/Filter/UnnecassaryWhitespaces.php | 71 | ||||
-rw-r--r-- | core/Translate/Validate/CoreTranslations.php | 106 | ||||
-rw-r--r-- | core/Translate/Validate/NoScripts.php | 47 | ||||
-rw-r--r-- | core/Translate/Validate/ValidateAbstract.php | 42 | ||||
-rw-r--r-- | core/Translate/Writer.php | 381 | ||||
-rw-r--r-- | core/TranslationWriter.php | 134 |
12 files changed, 960 insertions, 134 deletions
diff --git a/core/Translate.php b/core/Translate.php index 5348c1a1b3..09d9734da6 100644 --- a/core/Translate.php +++ b/core/Translate.php @@ -33,6 +33,17 @@ class Translate return self::$instance; } + /** + * Clean a string that may contain HTML special chars, single/double quotes, HTML entities, leading/trailing whitespace + * + * @param string $s + * @return string + */ + static public function clean($s) + { + return html_entity_decode(trim($s), ENT_QUOTES, 'UTF-8'); + } + public function loadEnglishTranslation() { $this->loadCoreTranslationFile('en'); diff --git a/core/Translate/Filter/ByBaseTranslations.php b/core/Translate/Filter/ByBaseTranslations.php new file mode 100644 index 0000000000..001a293c2e --- /dev/null +++ b/core/Translate/Filter/ByBaseTranslations.php @@ -0,0 +1,68 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik + * @package Piwik + */ + +namespace Piwik\Translate\Filter; + +use Piwik\Translate\Filter\FilterAbstract; + +/** + * @package Piwik + * @subpackage Piwik_Translate + */ +class ByBaseTranslations extends FilterAbstract +{ + protected $_baseTranslations = array(); + + /** + * Sets base translations + * + * @param array $baseTranslations + */ + public function __construct($baseTranslations=array()) + { + $this->_baseTranslations = $baseTranslations; + } + + /** + * Removes all translations that aren't present in the base translations set in constructor + * + * @param array $translations + * + * @return array filtered translations + */ + public function filter($translations) + { + $cleanedTranslations = array(); + + foreach ($translations AS $pluginName => $pluginTranslations) { + + if (empty($this->_baseTranslations[$pluginName])) { + $this->_filteredData[$pluginName] = $pluginTranslations; + continue; + } + + foreach ($pluginTranslations as $key => $translation) { + if (isset($this->_baseTranslations[$pluginName][$key])) { + $cleanedTranslations[$pluginName][$key] = $translation; + } + } + + if (!empty($cleanedTranslations[$pluginName])) { + $diff = array_diff($translations[$pluginName], $cleanedTranslations[$pluginName]); + } else { + $diff = $translations[$pluginName]; + } + if (!empty($diff)) $this->_filteredData[$pluginName] = $diff; + } + + return $cleanedTranslations; + } +}
\ No newline at end of file diff --git a/core/Translate/Filter/ByParameterCount.php b/core/Translate/Filter/ByParameterCount.php new file mode 100644 index 0000000000..1a1e716294 --- /dev/null +++ b/core/Translate/Filter/ByParameterCount.php @@ -0,0 +1,93 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik + * @package Piwik + */ + +namespace Piwik\Translate\Filter; + +use Piwik\Translate\Filter\FilterAbstract; + +/** + * @package Piwik + * @subpackage Piwik_Translate + */ +class ByParameterCount extends FilterAbstract +{ + protected $_baseTranslations = array(); + + /** + * Sets base translations + * + * @param array $baseTranslations + */ + public function __construct($baseTranslations=array()) + { + $this->_baseTranslations = $baseTranslations; + } + + /** + * Removes all translations where the placeholder parameter count differs to base translation + * + * @param array $translations + * + * @return array filtered translations + * + */ + public function filter($translations) + { + $cleanedTranslations = array(); + + foreach ($translations AS $pluginName => $pluginTranslations) { + + foreach ($pluginTranslations AS $key => $translation) { + + if (isset($this->_baseTranslations[$pluginName][$key])) { + $baseTranslation = $this->_baseTranslations[$pluginName][$key]; + } else { + $baseTranslation = ''; + } + + // ensure that translated strings have the same number of %s as the english source strings + $baseCount = $this->_getParametersCountToReplace($baseTranslation); + $translationCount = $this->_getParametersCountToReplace($translation); + + if ($baseCount != $translationCount) { + + $this->_filteredData[$pluginName][$key] = $translation; + continue; + } + + $cleanedTranslations[$pluginName][$key] = $translation; + } + } + + return $cleanedTranslations; + } + + /** + * Counts the placeholder parameters n given string + * + * @param string $string + * @return array + */ + protected function _getParametersCountToReplace($string) + { + $sprintfParameters = array('%s', '%1$s', '%2$s', '%3$s', '%4$s', '%5$s', '%6$s', '%7$s', '%8$s', '%9$s'); + $count = array(); + foreach ($sprintfParameters as $parameter) { + + $placeholderCount = substr_count($string, $parameter); + if ($placeholderCount > 0) { + + $count[$parameter] = $placeholderCount; + } + } + return $count; + } +}
\ No newline at end of file diff --git a/core/Translate/Filter/EmptyTranslations.php b/core/Translate/Filter/EmptyTranslations.php new file mode 100644 index 0000000000..b5a6d17134 --- /dev/null +++ b/core/Translate/Filter/EmptyTranslations.php @@ -0,0 +1,51 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik + * @package Piwik + */ + +namespace Piwik\Translate\Filter; + +use Piwik\Translate\Filter\FilterAbstract; + +/** + * @package Piwik + * @subpackage Piwik_Translate + */ +class EmptyTranslations extends FilterAbstract +{ + /** + * Removes all empty translations + * + * @param array $translations + * + * @return array filtered translations + * + */ + public function filter($translations) + { + $translationsBefore = $translations; + + foreach ($translations AS $plugin => &$pluginTranslations) { + + $pluginTranslations = array_filter($pluginTranslations, function($value) { + return !empty($value) && '' != trim($value); + }); + + $diff = array_diff($translationsBefore[$plugin], $pluginTranslations); + if (!empty($diff)) $this->_filteredData[$plugin] = $diff; + } + + // remove plugins without translations + $translations = array_filter($translations, function($value) { + return !empty($value) && count($value); + }); + + return $translations; + } +}
\ No newline at end of file diff --git a/core/Translate/Filter/EncodedEntities.php b/core/Translate/Filter/EncodedEntities.php new file mode 100644 index 0000000000..4a8b757c1f --- /dev/null +++ b/core/Translate/Filter/EncodedEntities.php @@ -0,0 +1,49 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik + * @package Piwik + */ + +namespace Piwik\Translate\Filter; + +use Piwik\Translate\Filter\FilterAbstract; +use Piwik\Translate; + +/** + * @package Piwik + * @subpackage Piwik_Translate + */ +class EncodedEntities extends FilterAbstract +{ + /** + * Decodes all encoded entities in the given translations + * + * @param array $translations + * + * @return array filtered translations + * + */ + public function filter($translations) + { + foreach ($translations AS $pluginName => $pluginTranslations) { + foreach ($pluginTranslations AS $key => $translation) { + + // remove encoded entities + $decoded = Translate::clean($translation); + if ($translation != $decoded) { + $this->_filteredData[$pluginName][$key] = $translation; + $translations[$pluginName][$key] = $decoded; + continue; + } + + } + } + + return $translations; + } +}
\ No newline at end of file diff --git a/core/Translate/Filter/FilterAbstract.php b/core/Translate/Filter/FilterAbstract.php new file mode 100644 index 0000000000..eca3489bdf --- /dev/null +++ b/core/Translate/Filter/FilterAbstract.php @@ -0,0 +1,41 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik + * @package Piwik + */ + +namespace Piwik\Translate\Filter; + +/** + * @package Piwik + * @subpackage Piwik_Db + */ +abstract class FilterAbstract +{ + protected $_filteredData = array(); + + /** + * Filter the given translations + * + * @param array $translations + * + * @return array filtered translations + * + */ + abstract public function filter($translations); + + /** + * Returnes the data filtered out by the filter + * + * @return array + */ + public function getFilteredData() + { + return $this->_filteredData; + } +}
\ No newline at end of file diff --git a/core/Translate/Filter/UnnecassaryWhitespaces.php b/core/Translate/Filter/UnnecassaryWhitespaces.php new file mode 100644 index 0000000000..1de5c50a1f --- /dev/null +++ b/core/Translate/Filter/UnnecassaryWhitespaces.php @@ -0,0 +1,71 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik + * @package Piwik + */ + +namespace Piwik\Translate\Filter; + +use Piwik\Translate\Filter\FilterAbstract; + +/** + * @package Piwik + * @subpackage Piwik_Translate + */ +class UnnecassaryWhitespaces extends FilterAbstract +{ + protected $_baseTranslations = array(); + + /** + * Sets base translations + * + * @param array $baseTranslations + */ + public function __construct($baseTranslations=array()) + { + $this->_baseTranslations = $baseTranslations; + } + + /** + * Removes all unnecassary whitespaces and newlines from the given translations + * + * @param array $translations + * + * @return array filtered translations + * + */ + public function filter($translations) + { + foreach ($translations AS $pluginName => $pluginTranslations) { + foreach ($pluginTranslations AS $key => $translation) { + + $baseTranslation = ''; + if (isset($this->_baseTranslations[$pluginName][$key])) { + $baseTranslation = $this->_baseTranslations[$pluginName][$key]; + } + + // remove excessive line breaks (and leading/trailing whitespace) from translations + $stringNoLineBreak = trim($translation); + $stringNoLineBreak = str_replace("\r", "", $stringNoLineBreak); # remove useless carrige renturns + $stringNoLineBreak = preg_replace('/(\n[ ]+)/', "\n", $stringNoLineBreak); # remove useless white spaces after line breaks + $stringNoLineBreak = preg_replace('/([\n]{2,})/', "\n\n", $stringNoLineBreak); # remove excessive line breaks + if (empty($baseTranslation) || !substr_count($baseTranslation, "\n")) { + $stringNoLineBreak = preg_replace("/[\n]+/", " ", $stringNoLineBreak); # remove all line breaks if english string doesn't contain any + } + $stringNoLineBreak = preg_replace('/([ ]{2,})/', " ", $stringNoLineBreak); # remove excessive white spaces again as there might be any now, after removing line breaks + if ($translation !== $stringNoLineBreak) { + $this->_filteredData[$pluginName][$key] = $translation; + $translations[$pluginName][$key] = $stringNoLineBreak; + continue; + } + } + } + + return $translations; + } +}
\ No newline at end of file diff --git a/core/Translate/Validate/CoreTranslations.php b/core/Translate/Validate/CoreTranslations.php new file mode 100644 index 0000000000..068d1c3cbc --- /dev/null +++ b/core/Translate/Validate/CoreTranslations.php @@ -0,0 +1,106 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik + * @package Piwik + */ + +namespace Piwik\Translate\Validate; + +use Piwik\Translate\Validate\ValidateAbstract; +use Piwik\Common; + +/** + * @package Piwik + * @subpackage Piwik_Translate + */ +class CoreTranslations extends ValidateAbstract +{ + /** + * Error States + */ + const __ERRORSTATE_MINIMUMTRANSLATIONS__ = 'At least 250 translations required'; + const __ERRORSTATE_LOCALEREQUIRED__ = 'Locale required'; + const __ERRORSTATE_TRANSLATORINFOREQUIRED__ = 'Translator info required'; + const __ERRORSTATE_TRANSLATOREMAILREQUIRED__ = 'Translator email required'; + const __ERRORSTATE_LAYOUTDIRECTIONINVALID__ = 'Layout direction must be rtl or ltr'; + const __ERRORSTATE_LOCALEINVALID__ = 'Locale is invalid'; + const __ERRORSTATE_LOCALEINVALIDLANGUAGE__ = 'Locale is invalid - invalid language code'; + const __ERRORSTATE_LOCALEINVALIDCOUNTRY__ = 'Locale is invalid - invalid country code'; + + protected $_baseTranslations = array(); + + /** + * Sets base translations + * + * @param array $baseTranslations + */ + public function __construct($baseTranslations=array()) + { + $this->_baseTranslations = $baseTranslations; + } + + /** + * Validates the given translations + * * There need to be more than 250 translations presen + * * Locale, TranslatorName and TranslatorEmail needs to be set in plugin General + * * LayoutDirection needs to be ltr or rtl if present + * * Locale must be valid (format, language & country) + * + * @param array $translations + * + * @return boolean + * + */ + public function isValid($translations) + { + $this->_message = null; + + if (250 > count($translations, COUNT_RECURSIVE)) { + $this->_message = self::__ERRORSTATE_MINIMUMTRANSLATIONS__; + return false; + } + + if (empty($translations['General']['Locale'])) { + $this->_message = self::__ERRORSTATE_LOCALEREQUIRED__; + return false; + } + + if (empty($translations['General']['TranslatorName'])) { + $this->_message = self::__ERRORSTATE_TRANSLATORINFOREQUIRED__; + return false; + } + + if (empty($translations['General']['TranslatorEmail'])) { + $this->_message = self::__ERRORSTATE_TRANSLATOREMAILREQUIRED__; + return false; + } + + if (!empty($translations['General']['LayoutDirection']) && + !in_array($translations['General']['LayoutDirection'], array('ltr', 'rtl')) + ) { + $this->_message = self::__ERRORSTATE_LAYOUTDIRECTIONINVALID__; + return false; + } + + $allLanguages = Common::getLanguagesList(); + $allCountries = Common::getCountriesList(); + + if (!preg_match('/^([a-z]{2})_([A-Z]{2})\.UTF-8$/', $translations['General']['Locale'], $matches)) { + $this->_message = self::__ERRORSTATE_LOCALEINVALID__; + return false; + } else if (!array_key_exists($matches[1], $allLanguages)) { + $this->_message = self::__ERRORSTATE_LOCALEINVALIDLANGUAGE__; + return false; + } else if (!array_key_exists(strtolower($matches[2]), $allCountries)) { + $this->_message = self::__ERRORSTATE_LOCALEINVALIDCOUNTRY__; + return false; + } + + return true; + } +}
\ No newline at end of file diff --git a/core/Translate/Validate/NoScripts.php b/core/Translate/Validate/NoScripts.php new file mode 100644 index 0000000000..7116e409a8 --- /dev/null +++ b/core/Translate/Validate/NoScripts.php @@ -0,0 +1,47 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik + * @package Piwik + */ + +namespace Piwik\Translate\Validate; + +use Piwik\Translate\Validate\ValidateAbstract; +use Piwik\Common; + +/** + * @package Piwik + * @subpackage Piwik_Translate + */ +class NoScripts extends ValidateAbstract +{ + /** + * Validates the given translations + * * No script like parts should be present in any part of the translations + * + * @param array $translations + * + * @return boolean + * + */ + public function isValid($translations) + { + $this->_message = null; + + // check if any translation contains restricted script tags + $serializedStrings = serialize($translations); + $invalids = array("<script", 'document.', 'javascript:', 'src=', 'background=', 'onload='); + foreach ($invalids as $invalid) { + if (stripos($serializedStrings, $invalid) !== false) { + $this->_message = 'script tags restricted for language files'; + return false; + } + } + return true; + } +}
\ No newline at end of file diff --git a/core/Translate/Validate/ValidateAbstract.php b/core/Translate/Validate/ValidateAbstract.php new file mode 100644 index 0000000000..ca71850ed3 --- /dev/null +++ b/core/Translate/Validate/ValidateAbstract.php @@ -0,0 +1,42 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik + * @package Piwik + */ + +namespace Piwik\Translate\Validate; + +/** + * @package Piwik + * @subpackage Piwik_Db + */ +abstract class ValidateAbstract +{ + protected $_message = null; + + /** + * Returns if the given translations are valid + * + * @param array $translations + * + * @return boolean + * + */ + abstract public function isValid($translations); + + /** + * Returns an array of messages that explain why the most recent isValid() + * call returned false. + * + * @return array + */ + public function getMessage() + { + return $this->_message; + } +}
\ No newline at end of file diff --git a/core/Translate/Writer.php b/core/Translate/Writer.php new file mode 100644 index 0000000000..dea88a7103 --- /dev/null +++ b/core/Translate/Writer.php @@ -0,0 +1,381 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik + * @package Piwik + * + */ +namespace Piwik\Translate; + +use Exception; +use Piwik\Common; +use Piwik\PluginsManager; +use Piwik\Translate\Filter\FilterAbstract; +use Piwik\Translate\Validate\ValidateAbstract; + +/** + * Writes clean translations to file + * + * @package Piwik + * @package Piwik_Translate + */ +class Writer +{ + /** + * current language to write files for + * + * @var string + */ + protected $_language = ''; + + /** + * Name of a plugin (if set in contructor) + * + * @var string|null + */ + protected $_pluginName = null; + + /** + * translations to write to file + * + * @var array + */ + protected $_translations = array(); + + /** + * Validators to check translations with + * + * @var ValidateAbstract[] + */ + protected $_validators = array(); + + /** + * Message why validation failed + * + * @var string|null + */ + protected $_validationMessage = null; + + /** + * Filters to to apply to translations + * + * @var FilterAbstract[] + */ + protected $_filters = array(); + + /** + * Messages which filter changed the data + * + * @var array + */ + protected $_filterMessages = array(); + + const __UNFILTERED__ = 'unfiltered'; + const __FILTERED__ = 'filtered'; + + protected $_currentState = self::__UNFILTERED__; + + /** + * If $pluginName is given, Writer will be initialized for the given plugin if it exists + * Otherwise it will be initialized for core translations + * + * @param string $language ISO 639-1 alpha-2 language code + * @param string $pluginName optional plugin name + * @throws \Exception + */ + public function __construct($language, $pluginName=null) + { + $this->setLanguage($language); + + if (!empty($pluginName)) { + $installedPlugins = PluginsManager::getInstance()->readPluginsDirectory(); + + if (!in_array($pluginName, $installedPlugins)) { + + throw new Exception(Piwik_TranslateException('General_ExceptionLanguageFileNotFound', array($pluginName))); + } + + $this->_pluginName = $pluginName; + } + } + + /** + * @param string $language ISO 639-1 alpha-2 language code + * + * @throws \Exception + */ + public function setLanguage($language) + { + if (!preg_match('/^([a-z]{2,3}(-[a-z]{2,3})?)$/i', $language)) { + throw new Exception(Piwik_TranslateException('General_ExceptionLanguageFileNotFound', array($language))); + } + + $this->_language = strtolower($language); + } + + /** + * @return string ISO 639-1 alpha-2 language code + */ + public function getLanguage() + { + return $this->_language; + } + + /** + * Returns if there are translations available or not + * @return bool + */ + public function hasTranslations() + { + return !empty($this->_translations); + } + + /** + * Set the translations to write (and cleans them) + * + * @param $translations + */ + public function setTranslations($translations) + { + $this->_currentState = self::__UNFILTERED__; + $this->_translations = $translations; + $this->_applyFilters(); + } + + /** + * Get translations from file + * + * @param string $lang ISO 639-1 alpha-2 language code + * @throws Exception + * @return array Array of translations ( plugin => ( key => translated string ) ) + */ + public function getTranslations($lang) + { + $path = $this->_getTranslationPath('lang', $lang); + if (!is_readable($path)) { + return array(); + } + + $data = file_get_contents($path); + $translations = json_decode($data, true); + return $translations; + } + + /** + * Returns the temporary path for translations + * + * @return string + */ + public function getTemporaryTranslationPath() + { + return $this->_getTranslationPath('tmp'); + } + + /** + * Returns the path to translation files + * + * @return string + */ + public function getTranslationPath() + { + return $this->_getTranslationPath('lang'); + } + + /** + * Get translation file path based on given params + * + * @param string $base Optional base directory (either 'lang' or 'tmp') + * @param string|null $lang forced language + * @throws \Exception + * @return string path + */ + protected function _getTranslationPath($base, $lang=null) + { + if (empty($lang)) $lang = $this->getLanguage(); + + if (!empty($this->_pluginName)) { + + if ($base == 'tmp') { + return sprintf('%s/tmp/plugins/%s/lang/%s.json', PIWIK_INCLUDE_PATH, $this->_pluginName, $lang); + } else { + return sprintf('%s/plugins/%s/lang/%s.json', PIWIK_INCLUDE_PATH, $this->_pluginName, $lang); + } + } + + return sprintf('%s/%s/%s.json', PIWIK_INCLUDE_PATH, $base, $lang); + } + + + /** + * Converts translations to a string that can be written to a file + * + * @return string + */ + public function __toString() + { + /* + * Use JSON_UNESCAPED_UNICODE and JSON_PRETTY_PRINT for PHP >= 5.4 + */ + $options = 0; + if (defined('JSON_UNESCAPED_UNICODE')) $options |= JSON_UNESCAPED_UNICODE; + if (defined('JSON_PRETTY_PRINT')) $options |= JSON_PRETTY_PRINT; + + return json_encode($this->_translations, $options); + } + + /** + * Save translations to file; translations should already be cleaned. + * + * @throws \Exception + * @return bool|int False if failure, or number of bytes written + */ + public function save() + { + $this->_applyFilters(); + + if (!$this->hasTranslations() || !$this->isValid()) { + throw new Exception('unable to save empty or invalid translations'); + } + + $path = $this->getTranslationPath(); + + Common::mkdir(dirname($path)); + + return file_put_contents($path, $this->__toString()); + } + + /** + * Save translations to temporary file; translations should already be cleansed. + * + * @throws \Exception + * @return bool|int False if failure, or number of bytes written + */ + public function saveTemporary() + { + $this->_applyFilters(); + + if (!$this->hasTranslations() || !$this->isValid()) { + throw new Exception('unable to save empty or invalid translations'); + } + + $path = $this->getTemporaryTranslationPath(); + + Common::mkdir(dirname($path)); + + return file_put_contents($path, $this->__toString()); + } + + /** + * Adds an validator to check before saving + * + * @param ValidateAbstract $validator + */ + public function addValidator(ValidateAbstract $validator) + { + $this->_validators[] = $validator; + } + + /** + * Returns if translations are valid to save or not + * + * @return bool + */ + public function isValid() + { + $this->_applyFilters(); + + $this->_validationMessage = null; + + foreach ($this->_validators AS $validator) { + if (!$validator->isValid($this->_translations)) { + $this->_validationMessage = $validator->getMessage(); + return false; + } + } + + return true; + } + + /** + * Returns last validation message + * + * @return null|string + */ + public function getValidationMessage() + { + return $this->_validationMessage; + } + + /** + * Returns if the were translations removed while cleaning + * + * @return bool + */ + public function wasFiltered() + { + return !empty($this->_filterMessages); + } + + /** + * Returns the cleaning errors + * + * @return array + */ + public function getFilterMessages() + { + return $this->_filterMessages; + } + + /** + * @param FilterAbstract $filter + */ + public function addFilter(FilterAbstract $filter) + { + $this->_filters[] = $filter; + } + + /** + * @throws \Exception + * + * @return bool error state + */ + protected function _applyFilters() + { + // skip if already cleaned + if ($this->_currentState == self::__FILTERED__) { + return $this->wasFiltered(); + } + + $this->_filterMessages = array(); + + // skip if not translations available + if (!$this->hasTranslations()) { + $this->_currentState = self::__FILTERED__; + return false; + } + + $cleanedTranslations = $this->_translations; + + foreach ($this->_filters AS $filter) { + + $cleanedTranslations = $filter->filter($cleanedTranslations); + $filteredData = $filter->getFilteredData(); + if (!empty($filteredData)) { + $this->_filterMessages[] = get_class($filter) . " changed: " .var_export($filteredData, 1); + } + } + + $this->_currentState = self::__FILTERED__; + + if ($cleanedTranslations != $this->_translations) { + $this->_filterMessages[] = 'translations have been cleaned'; + } + + $this->_translations = $cleanedTranslations; + return $this->wasFiltered(); + } +} diff --git a/core/TranslationWriter.php b/core/TranslationWriter.php deleted file mode 100644 index f627e601f7..0000000000 --- a/core/TranslationWriter.php +++ /dev/null @@ -1,134 +0,0 @@ -<?php -/** - * Piwik - Open source web analytics - * - * @link http://piwik.org - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - * - * @category Piwik - * @package Piwik - * - */ -namespace Piwik; - -use Exception; -use Piwik\Common; - -/** - * Write translations to file - * - * @package Piwik - */ -class TranslationWriter -{ - static private $baseTranslation = null; - static public $disableJsonOptions = false; - - /** - * Clean a string that may contain HTML special chars, single/double quotes, HTML entities, leading/trailing whitespace - * - * @param string $s - * @return string - */ - static public function clean($s) - { - return html_entity_decode(trim($s), ENT_QUOTES, 'UTF-8'); - } - - /** - * Quote a "C" string - * - * @param string $s - * @return string - */ - static public function quote($s) - { - return "'" . addcslashes($s, "'") . "'"; - } - - /** - * Get translation file path - * - * @param string $lang ISO 639-1 alpha-2 language code - * @param string $base Optional base directory (either 'lang' or 'tmp') - * @throws Exception - * @return string path - */ - static public function getTranslationPath($lang, $base = 'lang') - { - if (!Common::isValidFilename($lang) || - ($base !== 'lang' && $base !== 'tmp') - ) { - throw new Exception(Piwik_TranslateException('General_ExceptionLanguageFileNotFound', array($lang))); - } - - return PIWIK_INCLUDE_PATH . '/' . $base . '/' . $lang . '.json'; - } - - /** - * Load translations from file - * - * @param string $lang ISO 639-1 alpha-2 language code - * @throws Exception - * @return array $translations Array of translations ( key => translated string ) - */ - static public function loadTranslation($lang) - { - $path = self::getTranslationPath($lang); - if (!is_readable($path)) { - throw new Exception(Piwik_TranslateException('General_ExceptionLanguageFileNotFound', array($lang))); - } - - $data = file_get_contents($path); - $translations = json_decode($data, true); - return $translations; - } - - /** - * Output translations to string - * - * @param array $translations multidimensional Array of translations ( plugin => array (key => translated string ) ) - * @return string - */ - static public function outputTranslation($translations) - { - if (!self::$baseTranslation) { - self::$baseTranslation = self::loadTranslation('en'); - } - $en = self::$baseTranslation; - - $cleanedTranslations = array(); - - // filter out all translations that don't exist in english translations - foreach ($en AS $plugin => $enTranslations) { - foreach ($enTranslations as $key => $en_translation) { - if (isset($translations[$plugin][$key]) && !empty($translations[$plugin][$key])) { - $cleanedTranslations[$plugin][$key] = $translations[$plugin][$key]; - } - } - } - - $options = 0; - if (!self::$disableJsonOptions) { - if (defined('JSON_UNESCAPED_UNICODE')) { - $options |= JSON_UNESCAPED_UNICODE; - } - if (defined('JSON_PRETTY_PRINT')) { - $options |= JSON_PRETTY_PRINT; - } - } - return json_encode($cleanedTranslations, $options); - } - - /** - * Save translations to file; translations should already be cleansed. - * - * @param array $translations Array of translations ( key => translated string ) - * @param string $destinationPath Path of file to save translations to - * @return bool|int False if failure, or number of bytes written - */ - static public function saveTranslation($translations, $destinationPath) - { - return file_put_contents($destinationPath, self::outputTranslation($translations)); - } -}
\ No newline at end of file |