Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authorFabian Becker <fabian.becker@uni-tuebingen.de>2013-09-04 03:17:34 +0400
committerFabian Becker <fabian.becker@uni-tuebingen.de>2013-09-04 03:17:34 +0400
commite3bbc07eb21e8a2c1ea53d4ac55d94bf47214353 (patch)
treec206c253843b41eb987780da3b7a0eea9aac1ae0 /core
parent515b74c1e6d83651e56b5c56e5e3699c13ea552e (diff)
parent55def0ef5710771da9677509ff5596efd15365c2 (diff)
Merge branch 'master' of https://github.com/piwik/piwik
Diffstat (limited to 'core')
-rw-r--r--core/Translate.php11
-rw-r--r--core/Translate/Filter/ByBaseTranslations.php68
-rw-r--r--core/Translate/Filter/ByParameterCount.php93
-rw-r--r--core/Translate/Filter/EmptyTranslations.php51
-rw-r--r--core/Translate/Filter/EncodedEntities.php49
-rw-r--r--core/Translate/Filter/FilterAbstract.php41
-rw-r--r--core/Translate/Filter/UnnecassaryWhitespaces.php71
-rw-r--r--core/Translate/Validate/CoreTranslations.php106
-rw-r--r--core/Translate/Validate/NoScripts.php47
-rw-r--r--core/Translate/Validate/ValidateAbstract.php42
-rw-r--r--core/Translate/Writer.php381
-rw-r--r--core/TranslationWriter.php134
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