diff options
author | Matthieu Napoli <matthieu@mnapoli.fr> | 2014-12-30 07:54:01 +0300 |
---|---|---|
committer | Matthieu Napoli <matthieu@mnapoli.fr> | 2015-01-05 05:27:55 +0300 |
commit | 46db2355d4706b75dcfa0a6d900be2e7c31f0716 (patch) | |
tree | 8b2118285b5ecb07b11be202cffd587ff0b52a75 /plugins/LanguagesManager | |
parent | 16d531f9b78ee18105a43741b308e04f4fdde673 (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')
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&st' + ) + ), + array( + 'test' => array( + 'test' => 'te&st' + ) + ), + array( + 'test' => array( + 'test' => 'te&st' + ) + ), + ), + array( + array( + 'empty' => array( + 'test' => 'tüsest' + ), + 'test' => array( + 'test' => '%1$stest', + 'empty' => '˜', + ) + ), + array( + 'empty' => array( + 'test' => 'tรผsest' + ), + 'test' => array( + 'test' => '%1$stest', + 'empty' => 'ห', + ) + ), + array( + 'empty' => array( + 'test' => 'tüsest' + ), + 'test' => array( + 'empty' => '˜', + ) + ), + ), + ); + } + + /** + * @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üsest' + ), + 'test' => array( + 'test' => 'bla <a href="javascript:alert();"> link </a>', + 'empty' => '˜', + ) + ), + ), + 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(); + } +} |