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 | |
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')
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(); + } +} |