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
diff options
context:
space:
mode:
authorMatthieu Napoli <matthieu@mnapoli.fr>2014-12-30 07:54:01 +0300
committerMatthieu Napoli <matthieu@mnapoli.fr>2015-01-05 05:27:55 +0300
commit46db2355d4706b75dcfa0a6d900be2e7c31f0716 (patch)
tree8b2118285b5ecb07b11be202cffd587ff0b52a75 /plugins/LanguagesManager
parent16d531f9b78ee18105a43741b308e04f4fdde673 (diff)
Moved `Piwik\Translate\Writer` and its subclasses to the LanguagesManager plugin
These classes are only used in that plugin, so it doesn't make sense to keep then in Core.
Diffstat (limited to 'plugins/LanguagesManager')
-rw-r--r--plugins/LanguagesManager/Commands/FetchFromOTrance.php4
-rw-r--r--plugins/LanguagesManager/Commands/SetTranslations.php20
-rw-r--r--plugins/LanguagesManager/Commands/Update.php4
-rwxr-xr-xplugins/LanguagesManager/Test/Integration/LanguagesManagerTest.php (renamed from plugins/LanguagesManager/tests/Integration/LanguagesManagerTest.php)19
-rw-r--r--plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/ByBaseTranslationsTest.php162
-rw-r--r--plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/ByParameterCountTest.php121
-rw-r--r--plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/EmptyTranslationsTest.php99
-rw-r--r--plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/EncodedEntitiesTest.php113
-rw-r--r--plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/UnnecassaryWhitespacesTest.php158
-rw-r--r--plugins/LanguagesManager/Test/Unit/TranslationWriter/Validate/CoreTranslationsTest.php137
-rw-r--r--plugins/LanguagesManager/Test/Unit/TranslationWriter/Validate/NoScriptsTest.php113
-rw-r--r--plugins/LanguagesManager/Test/Unit/TranslationWriter/WriterTest.php283
-rw-r--r--plugins/LanguagesManager/TranslationWriter/Filter/ByBaseTranslations.php62
-rw-r--r--plugins/LanguagesManager/TranslationWriter/Filter/ByParameterCount.php85
-rw-r--r--plugins/LanguagesManager/TranslationWriter/Filter/EmptyTranslations.php44
-rw-r--r--plugins/LanguagesManager/TranslationWriter/Filter/EncodedEntities.php41
-rw-r--r--plugins/LanguagesManager/TranslationWriter/Filter/FilterAbstract.php34
-rw-r--r--plugins/LanguagesManager/TranslationWriter/Filter/UnnecassaryWhitespaces.php62
-rw-r--r--plugins/LanguagesManager/TranslationWriter/Validate/CoreTranslations.php92
-rw-r--r--plugins/LanguagesManager/TranslationWriter/Validate/NoScripts.php39
-rw-r--r--plugins/LanguagesManager/TranslationWriter/Validate/ValidateAbstract.php35
-rw-r--r--plugins/LanguagesManager/TranslationWriter/Writer.php387
22 files changed, 2096 insertions, 18 deletions
diff --git a/plugins/LanguagesManager/Commands/FetchFromOTrance.php b/plugins/LanguagesManager/Commands/FetchFromOTrance.php
index a28f62df23..ae130b0bc6 100644
--- a/plugins/LanguagesManager/Commands/FetchFromOTrance.php
+++ b/plugins/LanguagesManager/Commands/FetchFromOTrance.php
@@ -11,6 +11,8 @@ namespace Piwik\Plugins\LanguagesManager\Commands;
use Piwik\Container\StaticContainer;
use Piwik\Unzip;
+use Symfony\Component\Console\Helper\DialogHelper;
+use Symfony\Component\Console\Helper\ProgressHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@@ -36,6 +38,7 @@ class FetchFromOTrance extends TranslationBase
{
$output->writeln("Starting to fetch latest language pack");
+ /** @var DialogHelper $dialog */
$dialog = $this->getHelperSet()->get('dialog');
$cookieFile = self::getDownloadPath() . DIRECTORY_SEPARATOR . 'cookie.txt';
@@ -139,6 +142,7 @@ class FetchFromOTrance extends TranslationBase
$output->writeln("Converting downloaded php files to json");
+ /** @var ProgressHelper $progress */
$progress = $this->getHelperSet()->get('progress');
$progress->start($output, count($filesToConvert));
diff --git a/plugins/LanguagesManager/Commands/SetTranslations.php b/plugins/LanguagesManager/Commands/SetTranslations.php
index 89a4994670..ef4ebdc22a 100644
--- a/plugins/LanguagesManager/Commands/SetTranslations.php
+++ b/plugins/LanguagesManager/Commands/SetTranslations.php
@@ -10,20 +10,19 @@
namespace Piwik\Plugins\LanguagesManager\Commands;
use Piwik\Plugins\LanguagesManager\API;
-use Piwik\Translate\Filter\ByBaseTranslations;
-use Piwik\Translate\Filter\ByParameterCount;
-use Piwik\Translate\Filter\EmptyTranslations;
-use Piwik\Translate\Filter\EncodedEntities;
-use Piwik\Translate\Filter\UnnecassaryWhitespaces;
-use Piwik\Translate\Validate\CoreTranslations;
-use Piwik\Translate\Validate\NoScripts;
-use Piwik\Translate\Writer;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Filter\ByBaseTranslations;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Filter\ByParameterCount;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Filter\EmptyTranslations;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Filter\EncodedEntities;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Filter\UnnecassaryWhitespaces;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Validate\CoreTranslations;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Validate\NoScripts;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Writer;
+use Symfony\Component\Console\Helper\DialogHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-/**
- */
class SetTranslations extends TranslationBase
{
protected function configure()
@@ -37,6 +36,7 @@ class SetTranslations extends TranslationBase
protected function execute(InputInterface $input, OutputInterface $output)
{
+ /** @var DialogHelper $dialog */
$dialog = $this->getHelperSet()->get('dialog');
$languageCode = $input->getOption('code');
diff --git a/plugins/LanguagesManager/Commands/Update.php b/plugins/LanguagesManager/Commands/Update.php
index 7014ab7241..4d0c28bd76 100644
--- a/plugins/LanguagesManager/Commands/Update.php
+++ b/plugins/LanguagesManager/Commands/Update.php
@@ -10,6 +10,8 @@
namespace Piwik\Plugins\LanguagesManager\Commands;
use Piwik\Plugins\LanguagesManager\API;
+use Symfony\Component\Console\Helper\DialogHelper;
+use Symfony\Component\Console\Helper\ProgressHelper;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -31,6 +33,7 @@ class Update extends TranslationBase
protected function execute(InputInterface $input, OutputInterface $output)
{
+ /** @var DialogHelper $dialog */
$dialog = $this->getHelperSet()->get('dialog');
$command = $this->getApplication()->find('translations:fetch');
@@ -60,6 +63,7 @@ class Update extends TranslationBase
$output->writeln("(!) Non interactive mode: New languages will be skipped");
}
+ /** @var ProgressHelper $progress */
$progress = $this->getHelperSet()->get('progress');
$progress->start($output, count($files));
diff --git a/plugins/LanguagesManager/tests/Integration/LanguagesManagerTest.php b/plugins/LanguagesManager/Test/Integration/LanguagesManagerTest.php
index 7b41c3f55e..4ea84a0362 100755
--- a/plugins/LanguagesManager/tests/Integration/LanguagesManagerTest.php
+++ b/plugins/LanguagesManager/Test/Integration/LanguagesManagerTest.php
@@ -6,21 +6,24 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-namespace Piwik\Plugins\LanguagesManager\tests;
+namespace Piwik\Plugins\LanguagesManager\Test\Integration;
use Piwik\Common;
use Piwik\Plugins\LanguagesManager\API;
-use Piwik\Translate\Filter\ByParameterCount;
-use Piwik\Translate\Filter\EmptyTranslations;
-use Piwik\Translate\Filter\EncodedEntities;
-use Piwik\Translate\Filter\UnnecassaryWhitespaces;
-use Piwik\Translate\Validate\CoreTranslations;
-use Piwik\Translate\Validate\NoScripts;
-use Piwik\Translate\Writer;
use \Exception;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Filter\ByParameterCount;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Filter\EmptyTranslations;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Filter\EncodedEntities;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Filter\UnnecassaryWhitespaces;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Validate\CoreTranslations;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Validate\NoScripts;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Writer;
require_once PIWIK_INCLUDE_PATH . '/plugins/LanguagesManager/API.php';
+/**
+ * @group LanguagesManager
+ */
class LanguagesManagerTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
diff --git a/plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/ByBaseTranslationsTest.php b/plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/ByBaseTranslationsTest.php
new file mode 100644
index 0000000000..aba41991b6
--- /dev/null
+++ b/plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/ByBaseTranslationsTest.php
@@ -0,0 +1,162 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\LanguagesManager\Test\Unit\TranslationWriter\Filter;
+
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Filter\ByBaseTranslations;
+
+/**
+ * @group LanguagesManager
+ */
+class ByBaseTranslationsTest extends \PHPUnit_Framework_TestCase
+{
+ public function getFilterTestData()
+ {
+ return array(
+ // empty stays empty
+ array(
+ array(),
+ array(),
+ array(),
+ array()
+ ),
+ // empty plugin is removed
+ array(
+ array(
+ 'test' => array()
+ ),
+ array(),
+ array(),
+ array(
+ 'test' => array()
+ ),
+ ),
+ // not existing values/plugins are removed
+ array(
+ array(
+ 'test' => array(
+ 'key' => 'value',
+ 'test' => 'test'
+ )
+ ),
+ array(
+ 'test' => array(
+ 'key' => 'value',
+ 'x' => 'y'
+ )
+ ),
+ array(
+ 'test' => array(
+ 'key' => 'value',
+ )
+ ),
+ array(
+ 'test' => array(
+ 'test' => 'test',
+ )
+ ),
+ ),
+ // no change if all exist
+ array(
+ array(
+ 'test' => array(
+ 'test' => 'test'
+ )
+ ),
+ array(
+ 'test' => array(
+ 'test' => 'test'
+ )
+ ),
+ array(
+ 'test' => array(
+ 'test' => 'test'
+ )
+ ),
+ array()
+ ),
+ // unavailable removed, others stay
+ array(
+ array(
+ 'empty' => array(
+ 'test' => 'test'
+ ),
+ 'test' => array(
+ 'test' => 'test',
+ 'empty' => ' ',
+ )
+ ),
+ array(
+ 'empty' => array(
+ 'test' => 'test'
+ ),
+ 'test' => array(
+ 'test' => 'test',
+ )
+ ),
+ array(
+ 'empty' => array(
+ 'test' => 'test'
+ ),
+ 'test' => array(
+ 'test' => 'test'
+ )
+ ),
+ array(
+ 'test' => array(
+ 'empty' => ' ',
+ )
+ )
+ ),
+ array(
+ array(
+ 'empty' => array(
+ 'test' => 'test'
+ ),
+ 'test' => array(
+ 'test' => 'test',
+ 'empty' => ' ',
+ )
+ ),
+ array(
+ 'empty' => array(
+ 'bla' => 'test'
+ ),
+ 'test' => array(
+ 'test' => 'test',
+ )
+ ),
+ array(
+ 'test' => array(
+ 'test' => 'test'
+ )
+ ),
+ array(
+ 'empty' => array(
+ 'test' => 'test'
+ ),
+ 'test' => array(
+ 'empty' => ' ',
+ )
+ )
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider getFilterTestData
+ * @group Core
+ */
+ public function testFilter($translations, $baseTranslations, $expected, $filteredData)
+ {
+ $filter = new ByBaseTranslations($baseTranslations);
+ $result = $filter->filter($translations);
+ $this->assertEquals($expected, $result);
+ $this->assertEquals($filteredData, $filter->getFilteredData());
+ }
+}
diff --git a/plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/ByParameterCountTest.php b/plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/ByParameterCountTest.php
new file mode 100644
index 0000000000..f8b57e9106
--- /dev/null
+++ b/plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/ByParameterCountTest.php
@@ -0,0 +1,121 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\LanguagesManager\Test\Unit\TranslationWriter\Filter;
+
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Filter\ByParameterCount;
+
+/**
+ * @group LanguagesManager
+ */
+class ByParameterCountTest extends \PHPUnit_Framework_TestCase
+{
+ public function getFilterTestData()
+ {
+ return array(
+ // empty stays empty - nothing to filter
+ array(
+ array(),
+ array(),
+ array(),
+ array()
+ ),
+ // empty plugin is removed
+ array(
+ array(
+ 'test' => array()
+ ),
+ array(),
+ array(),
+ array(),
+ ),
+ // value with %s will be removed, as it isn't there in base
+ array(
+ array(
+ 'test' => array(
+ 'key' => 'val%sue',
+ 'test' => 'test'
+ )
+ ),
+ array(
+ 'test' => array(
+ 'key' => 'value',
+ )
+ ),
+ array(),
+ array(
+ 'test' => array(
+ 'key' => 'val%sue',
+ )
+ ),
+ ),
+ // no change if placeholder count is the same
+ array(
+ array(
+ 'test' => array(
+ 'test' => 'te%sst'
+ )
+ ),
+ array(
+ 'test' => array(
+ 'test' => 'test%s'
+ )
+ ),
+ array(
+ 'test' => array(
+ 'test' => 'te%sst'
+ )
+ ),
+ array()
+ ),
+ // missing placeholder will be removed
+ array(
+ array(
+ 'empty' => array(
+ 'test' => 't%1$sest'
+ ),
+ 'test' => array(
+ 'test' => '%1$stest',
+ 'empty' => ' ',
+ )
+ ),
+ array(
+ 'empty' => array(
+ 'test' => 'test%1$s'
+ ),
+ 'test' => array(
+ 'test' => '%1$stest%2$s',
+ )
+ ),
+ array(
+ 'empty' => array(
+ 'test' => 't%1$sest'
+ ),
+ ),
+ array(
+ 'test' => array(
+ 'test' => '%1$stest',
+ )
+ )
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider getFilterTestData
+ * @group Core
+ */
+ public function testFilter($translations, $baseTranslations, $expected, $filteredData)
+ {
+ $filter = new ByParameterCount($baseTranslations);
+ $result = $filter->filter($translations);
+ $message = sprintf("got %s but expected %s", var_export($result, true), var_export($expected, true));
+ $this->assertEquals($expected, $result, $message);
+ $this->assertEquals($filteredData, $filter->getFilteredData());
+ }
+}
diff --git a/plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/EmptyTranslationsTest.php b/plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/EmptyTranslationsTest.php
new file mode 100644
index 0000000000..da250c0d79
--- /dev/null
+++ b/plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/EmptyTranslationsTest.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\LanguagesManager\Test\Unit\TranslationWriter\Filter;
+
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Filter\EmptyTranslations;
+
+/**
+ * @group LanguagesManager
+ */
+class EmptyTranslationsTest extends \PHPUnit_Framework_TestCase
+{
+ public function getFilterTestData()
+ {
+ return array(
+ // empty stays empty
+ array(
+ array(),
+ array(),
+ array()
+ ),
+ // empty plugin is removed
+ array(
+ array(
+ 'test' => array()
+ ),
+ array(),
+ array(),
+ ),
+ // empty values/plugins are removed
+ array(
+ array(
+ 'test' => array(
+ 'empty' => '',
+ 'whitespace' => ' '
+ )
+ ),
+ array(),
+ array(
+ 'test' => array(
+ 'empty' => '',
+ 'whitespace' => ' '
+ )
+ ),
+ ),
+ // no change if no empty value
+ array(
+ array(
+ 'test' => array(
+ 'test' => 'test'
+ )
+ ),
+ array(
+ 'test' => array(
+ 'test' => 'test'
+ )
+ ),
+ array()
+ ),
+ // empty values are removed, others stay
+ array(
+ array(
+ 'empty' => array(),
+ 'test' => array(
+ 'test' => 'test',
+ 'empty' => ' ',
+ )
+ ),
+ array(
+ 'test' => array(
+ 'test' => 'test'
+ )
+ ),
+ array(
+ 'test' => array(
+ 'empty' => ' ',
+ )
+ )
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider getFilterTestData
+ * @group Core
+ */
+ public function testFilter($translations, $expected, $filteredData)
+ {
+ $filter = new EmptyTranslations();
+ $result = $filter->filter($translations);
+ $this->assertEquals($expected, $result);
+ $this->assertEquals($filteredData, $filter->getFilteredData());
+ }
+}
diff --git a/plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/EncodedEntitiesTest.php b/plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/EncodedEntitiesTest.php
new file mode 100644
index 0000000000..9411a65124
--- /dev/null
+++ b/plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/EncodedEntitiesTest.php
@@ -0,0 +1,113 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\LanguagesManager\Test\Unit\TranslationWriter\Filter;
+
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Filter\EncodedEntities;
+
+/**
+ * @group LanguagesManager
+ */
+class EncodedEntitiesTest extends \PHPUnit_Framework_TestCase
+{
+ public function getFilterTestData()
+ {
+ return array(
+ // empty stays empty - nothing to filter
+ array(
+ array(),
+ array(),
+ array()
+ ),
+ // empty plugin is removed
+ array(
+ array(
+ 'test' => array()
+ ),
+ array(
+ 'test' => array()
+ ),
+ array(),
+ ),
+ // no entites - nothing to filter
+ array(
+ array(
+ 'test' => array(
+ 'key' => 'val%sue',
+ 'test' => 'test'
+ )
+ ),
+ array(
+ 'test' => array(
+ 'key' => 'val%sue',
+ 'test' => 'test'
+ )
+ ),
+ array(),
+ ),
+ // entities needs to be decodded
+ array(
+ array(
+ 'test' => array(
+ 'test' => 'te&amp;st'
+ )
+ ),
+ array(
+ 'test' => array(
+ 'test' => 'te&st'
+ )
+ ),
+ array(
+ 'test' => array(
+ 'test' => 'te&amp;st'
+ )
+ ),
+ ),
+ array(
+ array(
+ 'empty' => array(
+ 'test' => 't&uuml;sest'
+ ),
+ 'test' => array(
+ 'test' => '%1$stest',
+ 'empty' => '&tilde;',
+ )
+ ),
+ array(
+ 'empty' => array(
+ 'test' => 'tรผsest'
+ ),
+ 'test' => array(
+ 'test' => '%1$stest',
+ 'empty' => 'หœ',
+ )
+ ),
+ array(
+ 'empty' => array(
+ 'test' => 't&uuml;sest'
+ ),
+ 'test' => array(
+ 'empty' => '&tilde;',
+ )
+ ),
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider getFilterTestData
+ * @group Core
+ */
+ public function testFilter($translations, $expected, $filteredData)
+ {
+ $filter = new EncodedEntities();
+ $result = $filter->filter($translations);
+ $this->assertEquals($expected, $result);
+ $this->assertEquals($filteredData, $filter->getFilteredData());
+ }
+}
diff --git a/plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/UnnecassaryWhitespacesTest.php b/plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/UnnecassaryWhitespacesTest.php
new file mode 100644
index 0000000000..701df50144
--- /dev/null
+++ b/plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/UnnecassaryWhitespacesTest.php
@@ -0,0 +1,158 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\LanguagesManager\Test\Unit\TranslationWriter\Filter;
+
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Filter\UnnecassaryWhitespaces;
+
+/**
+ * @group LanguagesManager
+ */
+class UnnecassaryWhitepsacesTest extends \PHPUnit_Framework_TestCase
+{
+ public function getFilterTestData()
+ {
+ return array(
+ // empty stays empty - nothing to filter
+ array(
+ array(),
+ array(),
+ array(),
+ array()
+ ),
+ // no entites - nothing to filter
+ array(
+ array(
+ 'test' => array(
+ 'key' => "val\n\n\r\n\nue",
+ 'test' => 'test'
+ )
+ ),
+ array(
+ 'test' => array(
+ 'key' => "base val\n\nue",
+ 'test' => 'test'
+ )
+ ),
+ array(
+ 'test' => array(
+ 'key' => "val\n\nue",
+ 'test' => 'test'
+ )
+ ),
+ array(
+ 'test' => array(
+ 'key' => "val\n\n\r\n\nue",
+ )
+
+ ),
+ ),
+ // entities needs to be decodded
+ array(
+ array(
+ 'test' => array(
+ 'test' => 'test palim'
+ )
+ ),
+ array(
+ 'test' => array(
+ 'test' => 'no line breaks'
+ )
+ ),
+ array(
+ 'test' => array(
+ 'test' => 'test palim'
+ )
+ ),
+ array(
+ 'test' => array(
+ 'test' => 'test palim'
+ )
+ ),
+ ),
+ array(
+ array(
+ 'empty' => array(
+ 'test' => "test\n\n\ntest"
+ ),
+ ),
+ array(
+ 'empty' => array(
+ 'test' => 'no line break'
+ ),
+ ),
+ array(
+ 'empty' => array(
+ 'test' => 'test test'
+ ),
+ ),
+ array(
+ 'empty' => array(
+ 'test' => "test\n\n\ntest"
+ ),
+ ),
+ ),
+ array(
+ array(
+ 'empty' => array(
+ 'test' => "test\n \n\n test"
+ ),
+ ),
+ array(
+ 'empty' => array(
+ 'test' => 'no line break'
+ ),
+ ),
+ array(
+ 'empty' => array(
+ 'test' => 'test test'
+ ),
+ ),
+ array(
+ 'empty' => array(
+ 'test' => "test\n \n\n test"
+ ),
+ ),
+ ),
+ array(
+ array(
+ 'empty' => array(
+ 'test' => "test\n \n\n test"
+ ),
+ ),
+ array(
+ 'empty' => array(
+ 'test' => "line\n break"
+ ),
+ ),
+ array(
+ 'empty' => array(
+ 'test' => "test\n\ntest"
+ ),
+ ),
+ array(
+ 'empty' => array(
+ 'test' => "test\n \n\n test"
+ ),
+ ),
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider getFilterTestData
+ * @group Core
+ */
+ public function testFilter($translations, $baseTranslations, $expected, $filteredData)
+ {
+ $filter = new UnnecassaryWhitespaces($baseTranslations);
+ $result = $filter->filter($translations);
+ $this->assertEquals($expected, $result);
+ $this->assertEquals($filteredData, $filter->getFilteredData());
+ }
+}
diff --git a/plugins/LanguagesManager/Test/Unit/TranslationWriter/Validate/CoreTranslationsTest.php b/plugins/LanguagesManager/Test/Unit/TranslationWriter/Validate/CoreTranslationsTest.php
new file mode 100644
index 0000000000..2a82991fcd
--- /dev/null
+++ b/plugins/LanguagesManager/Test/Unit/TranslationWriter/Validate/CoreTranslationsTest.php
@@ -0,0 +1,137 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\LanguagesManager\Test\Unit\TranslationWriter\Validate;
+
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Validate\CoreTranslations;
+
+/**
+ * @group LanguagesManager
+ */
+class CoreTranslationsTest extends \PHPUnit_Framework_TestCase
+{
+ public function setUp()
+ {
+ include PIWIK_INCLUDE_PATH . '/core/DataFiles/Languages.php';
+ include PIWIK_INCLUDE_PATH . '/core/DataFiles/Countries.php';
+ }
+
+ public function getFilterTestDataValid()
+ {
+ return array(
+ array(
+ array(
+ 'General' => array_merge(array_fill(0, 251, 'test'), array(
+ 'Locale' => 'de_DE.UTF-8',
+ 'TranslatorName' => 'name',
+ 'TranslatorEmail' => 'email',
+ )
+ )
+ ),
+ )
+ );
+ }
+
+ /**
+ * @dataProvider getFilterTestDataValid
+ * @group Core
+ */
+ public function testFilterValid($translations)
+ {
+ $filter = new CoreTranslations();
+ $result = $filter->isValid($translations);
+ $this->assertTrue($result);
+ }
+
+ public function getFilterTestDataInvalid()
+ {
+ return array(
+ array(
+ array(
+ 'General' => array(
+ 'bla' => 'test text'
+ )
+ ),
+ CoreTranslations::ERRORSTATE_LOCALEREQUIRED
+ ),
+ array(
+ array(
+ 'General' => array(
+ 'Locale' => 'de_DE.UTF-8'
+ )
+ ),
+ CoreTranslations::ERRORSTATE_TRANSLATORINFOREQUIRED
+ ),
+ array(
+ array(
+ 'General' => array(
+ 'Locale' => 'de_DE.UTF-8',
+ 'TranslatorName' => 'name',
+ )
+ ),
+ CoreTranslations::ERRORSTATE_TRANSLATOREMAILREQUIRED
+ ),
+ array(
+ array(
+ 'General' => array(
+ 'Locale' => 'de_DE.UTF-8',
+ 'TranslatorName' => 'name',
+ 'TranslatorEmail' => 'emails',
+ 'LayoutDirection' => 'afd'
+ )
+ ),
+ CoreTranslations::ERRORSTATE_LAYOUTDIRECTIONINVALID
+ ),
+ array(
+ array(
+ 'General' => array(
+ 'Locale' => 'invalid',
+ 'TranslatorName' => 'name',
+ 'TranslatorEmail' => 'emails',
+ 'LayoutDirection' => 'ltr'
+ )
+ ),
+ CoreTranslations::ERRORSTATE_LOCALEINVALID
+ ),
+ array(
+ array(
+ 'General' => array(
+ 'Locale' => 'xx_DE.UTF-8',
+ 'TranslatorName' => 'name',
+ 'TranslatorEmail' => 'emails',
+ 'LayoutDirection' => 'ltr'
+ )
+ ),
+ CoreTranslations::ERRORSTATE_LOCALEINVALIDLANGUAGE
+ ),
+ array(
+ array(
+ 'General' => array(
+ 'Locale' => 'de_XX.UTF-8',
+ 'TranslatorName' => 'name',
+ 'TranslatorEmail' => 'emails',
+ 'LayoutDirection' => 'ltr'
+ )
+ ),
+ CoreTranslations::ERRORSTATE_LOCALEINVALIDCOUNTRY
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider getFilterTestDataInvalid
+ * @group Core
+ */
+ public function testFilterInvalid($translations, $msg)
+ {
+ $filter = new CoreTranslations();
+ $result = $filter->isValid($translations);
+ $this->assertFalse($result);
+ $this->assertEquals($msg, $filter->getMessage());
+ }
+}
diff --git a/plugins/LanguagesManager/Test/Unit/TranslationWriter/Validate/NoScriptsTest.php b/plugins/LanguagesManager/Test/Unit/TranslationWriter/Validate/NoScriptsTest.php
new file mode 100644
index 0000000000..fcf5201d76
--- /dev/null
+++ b/plugins/LanguagesManager/Test/Unit/TranslationWriter/Validate/NoScriptsTest.php
@@ -0,0 +1,113 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\LanguagesManager\Test\Unit\TranslationWriter\Validate;
+
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Validate\NoScripts;
+
+/**
+ * @group LanguagesManager
+ */
+class NoScriptsTest extends \PHPUnit_Framework_TestCase
+{
+ public function getFilterTestDataValid()
+ {
+ return array(
+ array(
+ array(),
+ ),
+ array(
+ array(
+ 'test' => array()
+ ),
+ ),
+ array(
+ array(
+ 'test' => array(
+ 'key' => 'val%sue',
+ 'test' => 'test'
+ )
+ ),
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider getFilterTestDataValid
+ * @group Core
+ */
+ public function testFilterValid($translations)
+ {
+ $filter = new NoScripts();
+ $result = $filter->isValid($translations);
+ $this->assertTrue($result);
+ }
+
+ public function getFilterTestDataInvalid()
+ {
+ return array(
+ array(
+ array(
+ 'test' => array(
+ 'test' => 'test text <script'
+ )
+ ),
+ ),
+ array(
+ array(
+ 'empty' => array(
+ 'test' => 't&uuml;sest'
+ ),
+ 'test' => array(
+ 'test' => 'bla <a href="javascript:alert();"> link </a>',
+ 'empty' => '&tilde;',
+ )
+ ),
+ ),
+ array(
+ array(
+ 'test' => array(
+ 'test' => 'bla <a onload="alert(\'test\');">link</a>'
+ )
+ ),
+ ),
+ array(
+ array(
+ 'test' => array(
+ 'test' => 'no <img src="test" />'
+ )
+ ),
+ ),
+ array(
+ array(
+ 'test' => array(
+ 'test' => 'that will fail on document. or not?'
+ )
+ ),
+ ),
+ array(
+ array(
+ 'test' => array(
+ 'test' => 'bla <a background="yellow">link</a>'
+ )
+ ),
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider getFilterTestDataInvalid
+ * @group Core
+ */
+ public function testFilterInvalid($translations)
+ {
+ $filter = new NoScripts();
+ $result = $filter->isValid($translations);
+ $this->assertFalse($result);
+ }
+}
diff --git a/plugins/LanguagesManager/Test/Unit/TranslationWriter/WriterTest.php b/plugins/LanguagesManager/Test/Unit/TranslationWriter/WriterTest.php
new file mode 100644
index 0000000000..41b754f55b
--- /dev/null
+++ b/plugins/LanguagesManager/Test/Unit/TranslationWriter/WriterTest.php
@@ -0,0 +1,283 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\LanguagesManager\Test\Unit\TranslationWriter;
+
+use Piwik\Container\StaticContainer;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Filter\ByBaseTranslations;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Filter\ByParameterCount;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Filter\UnnecassaryWhitespaces;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Validate\CoreTranslations;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Validate\NoScripts;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Writer;
+
+/**
+ * @group LanguagesManager
+ */
+class WriterTest extends \PHPUnit_Framework_TestCase
+{
+ public function setUp()
+ {
+ parent::setUp();
+ include PIWIK_INCLUDE_PATH . '/core/DataFiles/Languages.php';
+ include PIWIK_INCLUDE_PATH . '/core/DataFiles/Countries.php';
+ }
+
+ /**
+ * @group Core
+ *
+ * @dataProvider getValidConstructorData
+ */
+ public function testConstructorValid($language, $plugin)
+ {
+ $translationWriter = new Writer($language, $plugin);
+ $this->assertEquals($language, $translationWriter->getLanguage());
+ $this->assertFalse($translationWriter->hasTranslations());
+ }
+
+ public function getValidConstructorData()
+ {
+ return array(
+ array('en', ''),
+ array('de', ''),
+ array('en', 'ExamplePlugin'),
+ );
+ }
+
+ /**
+ * @group Core
+ *
+ * @expectedException \Exception
+ */
+ public function testConstructorInvalid()
+ {
+ new Writer('en', 'InValIdPlUGin');
+ }
+
+ /**
+ * @group Core
+ */
+ public function testHasTranslations()
+ {
+ $writer = new Writer('de');
+ $writer->setTranslations(array('General' => array('test' => 'test')));
+ $this->assertTrue($writer->hasTranslations());
+ }
+
+ /**
+ * @group Core
+ */
+ public function testHasNoTranslations()
+ {
+ $writer = new Writer('de');
+ $this->assertFalse($writer->hasTranslations());
+ }
+
+ /**
+ * @group Core
+ */
+ public function testSetTranslationsEmpty()
+ {
+ $writer = new Writer('de');
+ $writer->setTranslations(array());
+ $this->assertTrue($writer->isValid());
+ $this->assertFalse($writer->hasTranslations());
+ }
+
+ /**
+ * @group Core
+ *
+ * @dataProvider getInvalidTranslations
+ */
+ public function testSetTranslationsInvalid($translations, $error)
+ {
+ $writer = new Writer('de');
+ $writer->setTranslations($translations);
+ $writer->addValidator(new NoScripts());
+ $writer->addValidator(new CoreTranslations());
+ $this->assertFalse($writer->isValid());
+ $this->assertEquals($error, $writer->getValidationMessage());
+ }
+
+ public function getInvalidTranslations()
+ {
+ $translations = json_decode(file_get_contents(PIWIK_INCLUDE_PATH.'/lang/de.json'), true);
+ return array(
+ array(array('General' => array('Locale' => '')) + $translations, CoreTranslations::ERRORSTATE_LOCALEREQUIRED),
+ array(array('General' => array('Locale' => 'de_DE.UTF-8')) + $translations, CoreTranslations::ERRORSTATE_TRANSLATORINFOREQUIRED),
+ array(array('General' => array('Locale' => 'de_DE.UTF-8',
+ 'TranslatorName' => 'name')) + $translations, CoreTranslations::ERRORSTATE_TRANSLATOREMAILREQUIRED),
+ array(array('General' => array('Locale' => 'de_DE.UTF-8',
+ 'TranslatorName' => 'name',
+ 'TranslatorEmail' => 'name@domain.com',
+ 'LayoutDirection' => 'fail')) + $translations, CoreTranslations::ERRORSTATE_LAYOUTDIRECTIONINVALID),
+ array(array('General' => array('Locale' => 'invalid',
+ 'TranslatorName' => 'name',
+ 'TranslatorEmail' => 'name@domain.com')) + $translations, CoreTranslations::ERRORSTATE_LOCALEINVALID),
+ array(array('General' => array('Locale' => 'xx_DE.UTF-8',
+ 'TranslatorName' => 'name',
+ 'TranslatorEmail' => 'name@domain.com',)) + $translations, CoreTranslations::ERRORSTATE_LOCALEINVALIDLANGUAGE),
+ array(array('General' => array('Locale' => 'de_XX.UTF-8',
+ 'TranslatorName' => 'name',
+ 'TranslatorEmail' => 'name@domain.com',)) + $translations, CoreTranslations::ERRORSTATE_LOCALEINVALIDCOUNTRY),
+ array(array('General' => array('Locale' => '<script>')) + $translations, 'script tags restricted for language files'),
+ );
+ }
+
+ /**
+ * @group Core
+ *
+ * @expectedException \Exception
+ */
+ public function testSaveException()
+ {
+ $writer = new Writer('it');
+ $writer->save();
+ }
+
+ /**
+ * @group Core
+ *
+ * @expectedException \Exception
+ */
+ public function testSaveTemporaryException()
+ {
+ $writer = new Writer('it');
+ $writer->saveTemporary();
+ }
+
+ /**
+ * @group Core
+ */
+ public function testSaveTranslation()
+ {
+ $translations = json_decode(file_get_contents(PIWIK_INCLUDE_PATH.'/lang/en.json'), true);
+
+ $translationsToWrite = array();
+ $translationsToWrite['General'] = $translations['General'];
+ $translationsToWrite['Mobile'] = $translations['Mobile'];
+
+ $translationsToWrite['General']['Yes'] = 'string with %1$s';
+ $translationsToWrite['Plugin'] = array(
+ 'Body' => "Message\nBody"
+ );
+
+ $translationWriter = new Writer('fr');
+
+ $translationWriter->addFilter(new UnnecassaryWhitespaces($translations));
+ $translationWriter->addFilter(new ByBaseTranslations($translations));
+ $translationWriter->addFilter(new ByParameterCount($translations));
+
+ $translationWriter->setTranslations($translationsToWrite);
+
+ $rc = $translationWriter->saveTemporary();
+
+ @unlink(PIWIK_INCLUDE_PATH.'/tmp/fr.json');
+
+ $this->assertGreaterThan(25000, $rc);
+
+ $this->assertCount(4, $translationWriter->getFilterMessages());
+ }
+
+ /**
+ * @group Core
+ *
+ * @dataProvider getTranslationPathTestData
+ */
+ public function testGetTranslationsPath($language, $plugin, $path)
+ {
+ $writer = new Writer($language, $plugin);
+ $this->assertEquals($path, $writer->getTranslationPath());
+ }
+
+ public function getTranslationPathTestData()
+ {
+ return array(
+ array('de', null, PIWIK_INCLUDE_PATH . '/lang/de.json'),
+ array('te', null, PIWIK_INCLUDE_PATH . '/lang/te.json'),
+ array('de', 'CoreHome', PIWIK_INCLUDE_PATH . '/plugins/CoreHome/lang/de.json'),
+ array('pt-br', 'Actions', PIWIK_INCLUDE_PATH . '/plugins/Actions/lang/pt-br.json'),
+ );
+ }
+
+ /**
+ * @group Core
+ *
+ * @dataProvider getTranslationPathTemporaryTestData
+ */
+ public function testGetTemporaryTranslationPath($language, $plugin, $path)
+ {
+ $writer = new Writer($language, $plugin);
+ $this->assertEquals($path, $writer->getTemporaryTranslationPath());
+ }
+
+ public function getTranslationPathTemporaryTestData()
+ {
+ $tmpPath = StaticContainer::getContainer()->get('path.tmp');
+
+ return array(
+ array('de', null, $tmpPath . '/de.json'),
+ array('te', null, $tmpPath . '/te.json'),
+ array('de', 'CoreHome', $tmpPath . '/plugins/CoreHome/lang/de.json'),
+ array('pt-br', 'Actions', $tmpPath . '/plugins/Actions/lang/pt-br.json'),
+ );
+ }
+
+ /**
+ * @group Core
+ *
+ * @dataProvider getValidLanguages
+ */
+ public function testSetLanguageValid($language)
+ {
+ $writer = new Writer('en', null);
+ $writer->setLanguage($language);
+ $this->assertEquals(strtolower($language), $writer->getLanguage());
+ }
+
+ public function getValidLanguages()
+ {
+ return array(
+ array('de'),
+ array('te'),
+ array('pt-br'),
+ array('tzm'),
+ array('abc'),
+ array('de-de'),
+ array('DE'),
+ array('DE-DE'),
+ array('DE-de'),
+ );
+ }
+ /**
+ * @group Core
+ *
+ * @expectedException \Exception
+ * @dataProvider getInvalidLanguages
+ */
+ public function testSetLanguageInvalid($language)
+ {
+ $writer = new Writer('en', null);
+ $writer->setLanguage($language);
+ }
+
+ public function getInvalidLanguages()
+ {
+ return array(
+ array(''),
+ array('abcd'),
+ array('pt-brfr'),
+ array('00'),
+ array('a-b'),
+ array('x3'),
+ array('X4-fd'),
+ array('12-34'),
+ array('$ยง'),
+ );
+ }
+}
diff --git a/plugins/LanguagesManager/TranslationWriter/Filter/ByBaseTranslations.php b/plugins/LanguagesManager/TranslationWriter/Filter/ByBaseTranslations.php
new file mode 100644
index 0000000000..1504f49e0d
--- /dev/null
+++ b/plugins/LanguagesManager/TranslationWriter/Filter/ByBaseTranslations.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\LanguagesManager\TranslationWriter\Filter;
+
+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;
+ }
+}
diff --git a/plugins/LanguagesManager/TranslationWriter/Filter/ByParameterCount.php b/plugins/LanguagesManager/TranslationWriter/Filter/ByParameterCount.php
new file mode 100644
index 0000000000..0d8a3cd482
--- /dev/null
+++ b/plugins/LanguagesManager/TranslationWriter/Filter/ByParameterCount.php
@@ -0,0 +1,85 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\LanguagesManager\TranslationWriter\Filter;
+
+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 {
+ // english string was deleted, do not error
+ continue;
+ }
+
+ // 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;
+ }
+}
diff --git a/plugins/LanguagesManager/TranslationWriter/Filter/EmptyTranslations.php b/plugins/LanguagesManager/TranslationWriter/Filter/EmptyTranslations.php
new file mode 100644
index 0000000000..15e17b2cc9
--- /dev/null
+++ b/plugins/LanguagesManager/TranslationWriter/Filter/EmptyTranslations.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\LanguagesManager\TranslationWriter\Filter;
+
+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;
+ }
+}
diff --git a/plugins/LanguagesManager/TranslationWriter/Filter/EncodedEntities.php b/plugins/LanguagesManager/TranslationWriter/Filter/EncodedEntities.php
new file mode 100644
index 0000000000..492ad6953a
--- /dev/null
+++ b/plugins/LanguagesManager/TranslationWriter/Filter/EncodedEntities.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\LanguagesManager\TranslationWriter\Filter;
+
+use 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;
+ }
+}
diff --git a/plugins/LanguagesManager/TranslationWriter/Filter/FilterAbstract.php b/plugins/LanguagesManager/TranslationWriter/Filter/FilterAbstract.php
new file mode 100644
index 0000000000..0f157fa5cc
--- /dev/null
+++ b/plugins/LanguagesManager/TranslationWriter/Filter/FilterAbstract.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\LanguagesManager\TranslationWriter\Filter;
+
+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;
+ }
+}
diff --git a/plugins/LanguagesManager/TranslationWriter/Filter/UnnecassaryWhitespaces.php b/plugins/LanguagesManager/TranslationWriter/Filter/UnnecassaryWhitespaces.php
new file mode 100644
index 0000000000..ce665b165a
--- /dev/null
+++ b/plugins/LanguagesManager/TranslationWriter/Filter/UnnecassaryWhitespaces.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\LanguagesManager\TranslationWriter\Filter;
+
+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;
+ }
+}
diff --git a/plugins/LanguagesManager/TranslationWriter/Validate/CoreTranslations.php b/plugins/LanguagesManager/TranslationWriter/Validate/CoreTranslations.php
new file mode 100644
index 0000000000..eb888bf434
--- /dev/null
+++ b/plugins/LanguagesManager/TranslationWriter/Validate/CoreTranslations.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\LanguagesManager\TranslationWriter\Validate;
+
+use Piwik\Common;
+
+class CoreTranslations extends ValidateAbstract
+{
+ /**
+ * Error States
+ */
+ 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 (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;
+ }
+}
diff --git a/plugins/LanguagesManager/TranslationWriter/Validate/NoScripts.php b/plugins/LanguagesManager/TranslationWriter/Validate/NoScripts.php
new file mode 100644
index 0000000000..7705cd02d7
--- /dev/null
+++ b/plugins/LanguagesManager/TranslationWriter/Validate/NoScripts.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\LanguagesManager\TranslationWriter\Validate;
+
+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;
+ }
+}
diff --git a/plugins/LanguagesManager/TranslationWriter/Validate/ValidateAbstract.php b/plugins/LanguagesManager/TranslationWriter/Validate/ValidateAbstract.php
new file mode 100644
index 0000000000..df36123839
--- /dev/null
+++ b/plugins/LanguagesManager/TranslationWriter/Validate/ValidateAbstract.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\LanguagesManager\TranslationWriter\Validate;
+
+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;
+ }
+}
diff --git a/plugins/LanguagesManager/TranslationWriter/Writer.php b/plugins/LanguagesManager/TranslationWriter/Writer.php
new file mode 100644
index 0000000000..a4f0f06a23
--- /dev/null
+++ b/plugins/LanguagesManager/TranslationWriter/Writer.php
@@ -0,0 +1,387 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\LanguagesManager\TranslationWriter;
+
+use Exception;
+use Piwik\Container\StaticContainer;
+use Piwik\Filesystem;
+use Piwik\Piwik;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Filter\FilterAbstract;
+use Piwik\Plugins\LanguagesManager\TranslationWriter\Validate\ValidateAbstract;
+
+/**
+ * Writes translations to file.
+ */
+class Writer
+{
+ /**
+ * current language to write files for
+ *
+ * @var string
+ */
+ protected $language = '';
+
+ /**
+ * Name of a plugin (if set in constructor)
+ *
+ * @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 = \Piwik\Plugin\Manager::getInstance()->readPluginsDirectory();
+
+ if (!in_array($pluginName, $installedPlugins)) {
+
+ throw new Exception(Piwik::translate('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::translate('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->getTranslationPathBaseDirectory('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->getTranslationPathBaseDirectory('tmp');
+ }
+
+ /**
+ * Returns the path to translation files
+ *
+ * @return string
+ */
+ public function getTranslationPath()
+ {
+ return $this->getTranslationPathBaseDirectory('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 getTranslationPathBaseDirectory($base, $lang = null)
+ {
+ if (empty($lang)) {
+ $lang = $this->getLanguage();
+ }
+
+ if (!empty($this->pluginName)) {
+
+ if ($base == 'tmp') {
+ return sprintf('%s/plugins/%s/lang/%s.json', StaticContainer::getContainer()->get('path.tmp'), $this->pluginName, $lang);
+ } else {
+ return sprintf('%s/plugins/%s/lang/%s.json', PIWIK_INCLUDE_PATH, $this->pluginName, $lang);
+ }
+ }
+
+ if ($base == 'tmp') {
+ return sprintf('%s/%s.json', StaticContainer::getContainer()->get('path.tmp'), $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();
+
+ Filesystem::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();
+
+ Filesystem::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();
+ }
+}