From fc1223f9240f0bddd044166ba086f8cb7ec19856 Mon Sep 17 00:00:00 2001 From: thewilkybarkid Date: Tue, 22 Apr 2014 15:58:51 +0100 Subject: [PATCH 01/19] Allow URLs that don't contain a path --- src/Symfony/Component/BrowserKit/Cookie.php | 4 ++-- src/Symfony/Component/BrowserKit/Tests/CookieTest.php | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/BrowserKit/Cookie.php b/src/Symfony/Component/BrowserKit/Cookie.php index 8b1f18ae3e..90636e9a56 100644 --- a/src/Symfony/Component/BrowserKit/Cookie.php +++ b/src/Symfony/Component/BrowserKit/Cookie.php @@ -152,12 +152,12 @@ class Cookie ); if (null !== $url) { - if ((false === $urlParts = parse_url($url)) || !isset($urlParts['host']) || !isset($urlParts['path'])) { + if ((false === $urlParts = parse_url($url)) || !isset($urlParts['host'])) { throw new \InvalidArgumentException(sprintf('The URL "%s" is not valid.', $url)); } $values['domain'] = $urlParts['host']; - $values['path'] = substr($urlParts['path'], 0, strrpos($urlParts['path'], '/')); + $values['path'] = isset($urlParts['path']) ? substr($urlParts['path'], 0, strrpos($urlParts['path'], '/')) : ''; } foreach ($parts as $part) { diff --git a/src/Symfony/Component/BrowserKit/Tests/CookieTest.php b/src/Symfony/Component/BrowserKit/Tests/CookieTest.php index 606b2e2e91..8e3578a28e 100644 --- a/src/Symfony/Component/BrowserKit/Tests/CookieTest.php +++ b/src/Symfony/Component/BrowserKit/Tests/CookieTest.php @@ -75,6 +75,8 @@ class CookieTest extends \PHPUnit_Framework_TestCase public function testFromStringWithUrl() { $this->assertEquals('foo=bar; domain=www.example.com; path=/', (string) Cookie::FromString('foo=bar', 'http://www.example.com/')); + $this->assertEquals('foo=bar; domain=www.example.com; path=/', (string) Cookie::FromString('foo=bar', 'http://www.example.com')); + $this->assertEquals('foo=bar; domain=www.example.com; path=/', (string) Cookie::FromString('foo=bar', 'http://www.example.com?foo')); $this->assertEquals('foo=bar; domain=www.example.com; path=/foo', (string) Cookie::FromString('foo=bar', 'http://www.example.com/foo/bar')); $this->assertEquals('foo=bar; domain=www.example.com; path=/', (string) Cookie::FromString('foo=bar; path=/', 'http://www.example.com/foo/bar')); $this->assertEquals('foo=bar; domain=www.myotherexample.com; path=/', (string) Cookie::FromString('foo=bar; domain=www.myotherexample.com', 'http://www.example.com/')); From f308f521a654900de2f8e35ad0d9c39f2dc49167 Mon Sep 17 00:00:00 2001 From: umpirsky Date: Tue, 29 Apr 2014 17:14:35 +0200 Subject: [PATCH 02/19] Fixed issue #5427 --- src/Symfony/Component/Translation/Loader/PoFileLoader.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Translation/Loader/PoFileLoader.php b/src/Symfony/Component/Translation/Loader/PoFileLoader.php index 73f7f02364..8ba96680aa 100644 --- a/src/Symfony/Component/Translation/Loader/PoFileLoader.php +++ b/src/Symfony/Component/Translation/Loader/PoFileLoader.php @@ -157,7 +157,7 @@ class PoFileLoader extends ArrayLoader implements LoaderInterface private function addMessage(array &$messages, array $item) { if (is_array($item['translated'])) { - $messages[$item['ids']['singular']] = stripslashes($item['translated'][0]); + $messages[$item['ids']['singular']] = stripcslashes($item['translated'][0]); if (isset($item['ids']['plural'])) { $plurals = $item['translated']; // PO are by definition indexed so sort by index. @@ -172,7 +172,7 @@ class PoFileLoader extends ArrayLoader implements LoaderInterface $messages[$item['ids']['plural']] = stripcslashes(implode('|', $plurals)); } } elseif (!empty($item['ids']['singular'])) { - $messages[$item['ids']['singular']] = stripslashes($item['translated']); + $messages[$item['ids']['singular']] = stripcslashes($item['translated']); } } } From e93cbd773562f40dc527f1edd973c73b8b82800c Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 28 Apr 2014 19:51:17 +0200 Subject: [PATCH 03/19] changed travis to run on the nightly builds of HHVM until everything gets stable --- .travis.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6ec8f1434b..2523c2cc96 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,21 +6,21 @@ php: - 5.4 - 5.5 - 5.6 - - hhvm + - hhvm-nightly matrix: allow_failures: - - php: hhvm + - php: hhvm-nightly services: mongodb before_script: - travis_retry sudo apt-get install parallel - - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then echo "" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini; fi;' - - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then echo "extension = mongo.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' - - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ] && [ $(php -r "echo PHP_MINOR_VERSION;") -le 4 ]; then echo "extension = apc.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' - - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then echo "extension = memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' - - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then echo "extension = memcache.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' + - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ]; then echo "" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini; fi;' + - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ]; then echo "extension = mongo.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' + - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ] && [ $(php -r "echo PHP_MINOR_VERSION;") -le 4 ]; then echo "extension = apc.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' + - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ]; then echo "extension = memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' + - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ]; then echo "extension = memcache.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' - sudo locale-gen fr_FR.UTF-8 && sudo update-locale - COMPOSER_ROOT_VERSION=dev-master composer --prefer-source --dev install From 7b425d229d6f2a215fbafb2b22fb3e8513fcce80 Mon Sep 17 00:00:00 2001 From: Tugdual Saunier Date: Wed, 30 Apr 2014 09:30:54 +0200 Subject: [PATCH 04/19] [WebProfilerBundle] Added test case for #10806 --- .../Controller/ProfilerControllerTest.php | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php new file mode 100644 index 0000000000..de8a952fe9 --- /dev/null +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\WebProfilerBundle\Tests\Controller; + +use Symfony\Bundle\FrameworkBundle\Tests\TestCase; +use Symfony\Bundle\WebProfilerBundle\Controller\ProfilerController; +use Symfony\Component\HttpFoundation\Request; + +class ProfilerControllerTest extends TestCase +{ + /** + * @dataProvider getEmptyTokenCases + */ + public function testEmptyToken($token) + { + $urlGenerator = $this->getMock('Symfony\Component\Routing\Generator\UrlGeneratorInterface'); + $twig = $this->getMock('Twig_Environment'); + $profiler = $this + ->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profiler') + ->disableOriginalConstructor() + ->getMock(); + + $controller = new ProfilerController($urlGenerator, $profiler, $twig, array()); + + $response = $controller->toolbarAction(Request::create('/_wdt/empty'), $token); + $this->assertEquals(200, $response->getStatusCode()); + } + + public function getEmptyTokenCases() + { + return array( + array(null), + // "empty" is also a valid empty token case, see https://github.com/symfony/symfony/issues/10806 + array('empty'), + ); + } +} From 5b91e70777c2e3bb72970f0f7b8e217b62074196 Mon Sep 17 00:00:00 2001 From: Tugdual Saunier Date: Wed, 30 Apr 2014 09:32:22 +0200 Subject: [PATCH 05/19] [WebProfilerBundle] fixed profiler homepage, fixed #10806 --- .../Bundle/WebProfilerBundle/Controller/ProfilerController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php index cd3e17c7f5..a106feae99 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php @@ -232,7 +232,7 @@ class ProfilerController $session->getFlashBag()->setAll($session->getFlashBag()->peekAll()); } - if (null === $token) { + if ('empty' === $token || null === $token) { return new Response('', 200, array('Content-Type' => 'text/html')); } From 16dd0e5dda25edd50e2a1a83ac70c03fb8fee673 Mon Sep 17 00:00:00 2001 From: Tugdual Saunier Date: Wed, 30 Apr 2014 09:45:11 +0200 Subject: [PATCH 06/19] [WebProfilerBundle] added test case for #10773 --- .../Controller/ProfilerControllerTest.php | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php index de8a952fe9..bff1919aa9 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php @@ -13,6 +13,7 @@ namespace Symfony\Bundle\WebProfilerBundle\Tests\Controller; use Symfony\Bundle\FrameworkBundle\Tests\TestCase; use Symfony\Bundle\WebProfilerBundle\Controller\ProfilerController; +use Symfony\Component\HttpKernel\Profiler\Profile; use Symfony\Component\HttpFoundation\Request; class ProfilerControllerTest extends TestCase @@ -43,4 +44,34 @@ class ProfilerControllerTest extends TestCase array('empty'), ); } + + public function testReturns404onTokenNotFound() + { + $urlGenerator = $this->getMock('Symfony\Component\Routing\Generator\UrlGeneratorInterface'); + $twig = $this->getMock('Twig_Environment'); + $profiler = $this + ->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profiler') + ->disableOriginalConstructor() + ->getMock(); + + $controller = new ProfilerController($urlGenerator, $profiler, $twig, array()); + + $profiler + ->expects($this->exactly(2)) + ->method('loadProfile') + ->will($this->returnCallback(function ($token) { + if ('found' == $token) { + return new Profile($token); + } + + return; + })) + ; + + $response = $controller->toolbarAction(Request::create('/_wdt/found'), 'found'); + $this->assertEquals(200, $response->getStatusCode()); + + $response = $controller->toolbarAction(Request::create('/_wdt/notFound'), 'notFound'); + $this->assertEquals(404, $response->getStatusCode()); + } } From a94b4e0ae2446a010175185cda9b0febec0fd99c Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 30 Apr 2014 09:15:56 +0200 Subject: [PATCH 07/19] [Validator] fixed wrong test --- .../Tests/Mapping/Loader/StaticMethodLoaderTest.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/StaticMethodLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/StaticMethodLoaderTest.php index f90310cd26..80dac2d866 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/StaticMethodLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/StaticMethodLoaderTest.php @@ -79,10 +79,16 @@ class StaticMethodLoaderTest extends \PHPUnit_Framework_TestCase public function testLoadClassMetadataIgnoresAbstractMethods() { $loader = new StaticMethodLoader('loadMetadata'); + $caught = false; try { - include __DIR__ . '/AbstractMethodStaticLoader.php'; - $this->fail('AbstractMethodStaticLoader should produce a strict standard error.'); + include __DIR__.'/AbstractMethodStaticLoader.php'; } catch (\Exception $e) { + // catching the PHP notice that is converted to an exception by PHPUnit + $caught = true; + } + + if (!$caught) { + $this->fail('AbstractMethodStaticLoader should produce a strict standard error.'); } $metadata = new ClassMetadata(__NAMESPACE__.'\AbstractMethodStaticLoader'); From c7befd53805d2f7534928d16e47636cbf4953c79 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Wed, 30 Apr 2014 14:05:40 +0200 Subject: [PATCH 08/19] Update PHPUnit before run --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 2523c2cc96..ca4c19d381 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,7 @@ before_script: - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ]; then echo "extension = memcache.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' - sudo locale-gen fr_FR.UTF-8 && sudo update-locale - COMPOSER_ROOT_VERSION=dev-master composer --prefer-source --dev install + - wget https://phar.phpunit.de/phpunit.phar && rm `which phpunit` && sudo cp phpunit.phar /usr/local/bin/phpunit && sudo chmod +x /usr/local/bin/phpunit script: - ls -d src/Symfony/*/* | parallel --gnu --keep-order 'echo "Running {} tests"; phpunit --exclude-group tty,benchmark {};' || exit 1 From 32e5f6727a88489647f331b7042ae0c94e270dc8 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Wed, 30 Apr 2014 11:57:12 +0200 Subject: [PATCH 09/19] [DependencyInjection] Fix travis unit tests --- .../DependencyInjection/Tests/Fixtures/xml/services8.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services8.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services8.xml index c5fa5fb52d..b17e50043c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services8.xml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services8.xml @@ -1,4 +1,4 @@ - + Date: Thu, 1 May 2014 19:10:47 +0100 Subject: [PATCH 10/19] [TwigBridge][Transchoice] set %count% from the current context. --- src/Symfony/Bridge/Twig/Node/TransNode.php | 10 +++++++--- .../Twig/Tests/Extension/TranslationExtensionTest.php | 2 ++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Bridge/Twig/Node/TransNode.php b/src/Symfony/Bridge/Twig/Node/TransNode.php index 2409090699..d459290b6f 100644 --- a/src/Symfony/Bridge/Twig/Node/TransNode.php +++ b/src/Symfony/Bridge/Twig/Node/TransNode.php @@ -98,9 +98,13 @@ class TransNode extends \Twig_Node foreach ($matches[1] as $var) { $key = new \Twig_Node_Expression_Constant('%'.$var.'%', $body->getLine()); if (!$vars->hasElement($key)) { - $varExpr = new \Twig_Node_Expression_Name($var, $body->getLine()); - $varExpr->setAttribute('ignore_strict_check', $ignoreStrictCheck); - $vars->addElement($varExpr, $key); + if ('count' === $var) { + $vars->addElement($this->getNode('count'), $key); + } else { + $varExpr = new \Twig_Node_Expression_Name($var, $body->getLine()); + $varExpr->setAttribute('ignore_strict_check', $ignoreStrictCheck); + $vars->addElement($varExpr, $key); + } } } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php index 524b86bc62..11c2d5ad79 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php @@ -110,6 +110,8 @@ class TranslationExtensionTest extends TestCase 'There is 5 apples (Symfony2)', array('count' => 5)), array('{% transchoice count into "fr"%}{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples{% endtranschoice %}', 'There is no apples', array('count' => 0)), + array('{% transchoice 5 into "fr"%}{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples{% endtranschoice %}', + 'There is 5 apples'), // trans filter array('{{ "Hello"|trans }}', 'Hello'), From f3b0ef1a6f74725f30c39553d17d0dbc7cc25ddc Mon Sep 17 00:00:00 2001 From: Hugo Hamon Date: Fri, 2 May 2014 00:38:16 +0200 Subject: [PATCH 11/19] [Finder] fixed typehint of the Finder::addAdapter() method --- src/Symfony/Component/Finder/Finder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php index e945fab13e..db4d851740 100644 --- a/src/Symfony/Component/Finder/Finder.php +++ b/src/Symfony/Component/Finder/Finder.php @@ -94,7 +94,7 @@ class Finder implements \IteratorAggregate, \Countable * * @return Finder The current Finder instance */ - public function addAdapter(Adapter\AdapterInterface $adapter, $priority = 0) + public function addAdapter(AdapterInterface $adapter, $priority = 0) { $this->adapters[$adapter->getName()] = array( 'adapter' => $adapter, From 0e32676032d0f1208913b27cf416ca078de0b4eb Mon Sep 17 00:00:00 2001 From: Hugo Hamon Date: Fri, 2 May 2014 15:39:04 +0200 Subject: [PATCH 12/19] [Security] fixed wrong PHPDoc of the TokenGeneratorInterface --- .../TokenGenerator/TokenGeneratorInterface.php | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/Symfony/Component/Security/Csrf/TokenGenerator/TokenGeneratorInterface.php b/src/Symfony/Component/Security/Csrf/TokenGenerator/TokenGeneratorInterface.php index 4d81da9c40..e02ac66ea0 100644 --- a/src/Symfony/Component/Security/Csrf/TokenGenerator/TokenGeneratorInterface.php +++ b/src/Symfony/Component/Security/Csrf/TokenGenerator/TokenGeneratorInterface.php @@ -12,19 +12,7 @@ namespace Symfony\Component\Security\Csrf\TokenGenerator; /** - * Generates and validates CSRF tokens. - * - * You can generate a CSRF token by using the method {@link generateCsrfToken()}. - * This method expects a unique token ID as argument. The token ID can later be - * used to validate a token provided by the user. - * - * Token IDs do not necessarily have to be secret, but they should NEVER be - * created from data provided by the client. A good practice is to hard-code the - * token IDs for the various CSRF tokens used by your application. - * - * You should use the method {@link isCsrfTokenValid()} to check a CSRF token - * submitted by the client. This method will return true if the CSRF token is - * valid. + * Generates CSRF tokens. * * @since 2.4 * @author Bernhard Schussek From 93544aadfa4021ffb3452759a7fa497e5718b136 Mon Sep 17 00:00:00 2001 From: n-aleha Date: Fri, 2 May 2014 22:42:57 +0000 Subject: [PATCH 13/19] [DependencyInjection] Fix parameter description in ConfigurationExtensionInterface | Q | A | ------------- | --- | Fixed tickets | #10845 | License | MIT This fixes the description of parameter `array $config` in file ConfigurationExtensionInterface.php by removing the extra `$config`. --- .../Extension/ConfigurationExtensionInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/DependencyInjection/Extension/ConfigurationExtensionInterface.php b/src/Symfony/Component/DependencyInjection/Extension/ConfigurationExtensionInterface.php index 6fcd9018dc..51bff08012 100644 --- a/src/Symfony/Component/DependencyInjection/Extension/ConfigurationExtensionInterface.php +++ b/src/Symfony/Component/DependencyInjection/Extension/ConfigurationExtensionInterface.php @@ -24,7 +24,7 @@ interface ConfigurationExtensionInterface /** * Returns extension configuration * - * @param array $config $config An array of configuration values + * @param array $config An array of configuration values * @param ContainerBuilder $container A ContainerBuilder instance * * @return ConfigurationInterface|null The configuration or null From 239b738e983e4a972212449e756cc1bd03333006 Mon Sep 17 00:00:00 2001 From: Wouter J Date: Mon, 5 May 2014 19:22:30 +0200 Subject: [PATCH 14/19] Added more IDE links --- .../FrameworkBundle/DependencyInjection/FrameworkExtension.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 0c22c27e86..1a79e8651d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -359,6 +359,8 @@ class FrameworkExtension extends Extension $links = array( 'textmate' => 'txmt://open?url=file://%%f&line=%%l', 'macvim' => 'mvim://open?url=file://%%f&line=%%l', + 'emacs' => 'emacs://open?url=file://%file&line=%line', + 'sublime' => 'subl://open?url=file://%file&line=%line', ); $container->setParameter('templating.helper.code.file_link_format', isset($links[$ide]) ? $links[$ide] : $ide); From b0bc83d2ac9f7a9b97c95af6e3bc4d543543b4bf Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 8 May 2014 17:33:56 +0200 Subject: [PATCH 15/19] [Form] Fixed TrimListenerTest as of PHP 5.5 --- .../Core/EventListener/TrimListenerTest.php | 53 +++++++++++++++---- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/TrimListenerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/TrimListenerTest.php index 4e36893380..358620b9c4 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/TrimListenerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/TrimListenerTest.php @@ -48,32 +48,65 @@ class TrimListenerTest extends \PHPUnit_Framework_TestCase } /** - * @dataProvider codePointProvider + * @dataProvider spaceProvider */ - public function testTrimUtf8($chars) + public function testTrimUtf8Separators($hex) { if (!function_exists('mb_check_encoding')) { $this->markTestSkipped('The "mb_check_encoding" function is not available'); } - $data = mb_convert_encoding(pack('H*', implode('', $chars)), 'UTF-8', 'UCS-2BE'); - $data = $data."ab\ncd".$data; + // Convert hexadecimal representation into binary + // H: hex string, high nibble first (UCS-2BE) + // *: repeat until end of string + $binary = pack('H*', $hex); + + // Convert UCS-2BE to UTF-8 + $symbol = mb_convert_encoding($binary, 'UTF-8', 'UCS-2BE'); + $symbol = $symbol."ab\ncd".$symbol; $form = $this->getMock('Symfony\Component\Form\Test\FormInterface'); - $event = new FormEvent($form, $data); + $event = new FormEvent($form, $symbol); $filter = new TrimListener(); $filter->preSubmit($event); - $this->assertSame("ab\ncd", $event->getData(), 'TrimListener should trim character(s): '.implode(', ', $chars)); + $this->assertSame("ab\ncd", $event->getData()); } - public function codePointProvider() + public function spaceProvider() { return array( - 'General category: Separator' => array(array('0020', '00A0', '1680', '180E', '2000', '2001', '2002', '2003', '2004', '2005', '2006', '2007', '2008', '2009', '200A', '2028', '2029', '202F', '205F', '3000')), - 'General category: Other, control' => array(array('0009', '000A', '000B', '000C', '000D', '0085')), - //'General category: Other, format. ZERO WIDTH SPACE' => array(array('200B')), + // separators + array('0020'), + array('00A0'), + array('1680'), +// array('180E'), + array('2000'), + array('2001'), + array('2002'), + array('2003'), + array('2004'), + array('2005'), + array('2006'), + array('2007'), + array('2008'), + array('2009'), + array('200A'), + array('2028'), + array('2029'), + array('202F'), + array('205F'), + array('3000'), + // controls + array('0009'), + array('000A'), + array('000B'), + array('000C'), + array('000D'), + array('0085'), + // zero width space +// array('200B'), ); } } From a605a3d925a0f21f88cce540bd4405bf92e89ee9 Mon Sep 17 00:00:00 2001 From: Arturs Vonda Date: Wed, 7 May 2014 11:42:46 +0300 Subject: [PATCH 16/19] [Security] Add check for supported attributes in AclVoter --- .../Component/Security/Acl/Voter/AclVoter.php | 6 +++- .../Security/Tests/Acl/Voter/AclVoterTest.php | 31 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Security/Acl/Voter/AclVoter.php b/src/Symfony/Component/Security/Acl/Voter/AclVoter.php index d401ef3bed..b21b1e675b 100644 --- a/src/Symfony/Component/Security/Acl/Voter/AclVoter.php +++ b/src/Symfony/Component/Security/Acl/Voter/AclVoter.php @@ -48,12 +48,16 @@ class AclVoter implements VoterInterface public function supportsAttribute($attribute) { - return $this->permissionMap->contains($attribute); + return is_string($attribute) && $this->permissionMap->contains($attribute); } public function vote(TokenInterface $token, $object, array $attributes) { foreach ($attributes as $attribute) { + if (!$this->supportsAttribute($attribute)) { + continue; + } + if (null === $masks = $this->permissionMap->getMasks($attribute, $object)) { continue; } diff --git a/src/Symfony/Component/Security/Tests/Acl/Voter/AclVoterTest.php b/src/Symfony/Component/Security/Tests/Acl/Voter/AclVoterTest.php index 2474515b5c..98e5ab9c55 100644 --- a/src/Symfony/Component/Security/Tests/Acl/Voter/AclVoterTest.php +++ b/src/Symfony/Component/Security/Tests/Acl/Voter/AclVoterTest.php @@ -27,7 +27,7 @@ class AclVoterTest extends \PHPUnit_Framework_TestCase */ public function testSupportsAttribute($attribute, $supported) { - list($voter,, $permissionMap,,) = $this->getVoter(); + list($voter,, $permissionMap,,) = $this->getVoter(true, false); $permissionMap ->expects($this->once()) @@ -39,6 +39,16 @@ class AclVoterTest extends \PHPUnit_Framework_TestCase $this->assertSame($supported, $voter->supportsAttribute($attribute)); } + /** + * @dataProvider getSupportsAttributeNonStringTests + */ + public function testSupportsAttributeNonString($attribute) + { + list($voter,,,,,) = $this->getVoter(true, false); + + $this->assertFalse($voter->supportsAttribute($attribute)); + } + public function getSupportsAttributeTests() { return array( @@ -47,6 +57,16 @@ class AclVoterTest extends \PHPUnit_Framework_TestCase ); } + public function getSupportsAttributeNonStringTests() + { + return array( + array(new \stdClass()), + array(1), + array(true), + array(array()), + ); + } + /** * @dataProvider getSupportsClassTests */ @@ -387,13 +407,20 @@ class AclVoterTest extends \PHPUnit_Framework_TestCase return $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); } - protected function getVoter($allowIfObjectIdentityUnavailable = true) + protected function getVoter($allowIfObjectIdentityUnavailable = true, $alwaysContains = true) { $provider = $this->getMock('Symfony\Component\Security\Acl\Model\AclProviderInterface'); $permissionMap = $this->getMock('Symfony\Component\Security\Acl\Permission\PermissionMapInterface'); $oidStrategy = $this->getMock('Symfony\Component\Security\Acl\Model\ObjectIdentityRetrievalStrategyInterface'); $sidStrategy = $this->getMock('Symfony\Component\Security\Acl\Model\SecurityIdentityRetrievalStrategyInterface'); + if ($alwaysContains) { + $permissionMap + ->expects($this->any()) + ->method('contains') + ->will($this->returnValue(true)); + } + return array( new AclVoter($provider, $oidStrategy, $sidStrategy, $permissionMap, null, $allowIfObjectIdentityUnavailable), $provider, From a4b805dbf2b7d1cab0e1cde09f60eed496468c28 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 12 May 2014 10:37:25 +0200 Subject: [PATCH 17/19] Lower mbstring dep, remove it for Yaml and CssSelector components --- .../Tests/Helper/FormatterHelperTest.php | 2 +- .../Parser/Tokenizer/TokenizerEscaping.php | 16 ++++-- .../NumberToLocalizedStringTransformer.php | 16 ++---- ...NumberToLocalizedStringTransformerTest.php | 8 +-- .../Core/EventListener/TrimListenerTest.php | 4 +- .../Tests/Dumper/IcuResFileDumperTest.php | 2 +- src/Symfony/Component/Yaml/Parser.php | 2 +- .../Component/Yaml/Tests/ParserTest.php | 10 +--- src/Symfony/Component/Yaml/Unescaper.php | 55 +++++++++---------- 9 files changed, 51 insertions(+), 64 deletions(-) diff --git a/src/Symfony/Component/Console/Tests/Helper/FormatterHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/FormatterHelperTest.php index 87a45e1d44..a0105761ce 100644 --- a/src/Symfony/Component/Console/Tests/Helper/FormatterHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/FormatterHelperTest.php @@ -54,7 +54,7 @@ class FormatterHelperTest extends \PHPUnit_Framework_TestCase public function testFormatBlockWithDiacriticLetters() { - if (!extension_loaded('mbstring')) { + if (!function_exists('mb_detect_encoding')) { $this->markTestSkipped('This test requires mbstring to work.'); } diff --git a/src/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerEscaping.php b/src/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerEscaping.php index c90fc1044d..921fc393a9 100644 --- a/src/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerEscaping.php +++ b/src/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerEscaping.php @@ -65,14 +65,18 @@ class TokenizerEscaping */ private function replaceUnicodeSequences($value) { - return preg_replace_callback($this->patterns->getUnicodeEscapePattern(), function (array $match) { - $code = $match[1]; + return preg_replace_callback($this->patterns->getUnicodeEscapePattern(), function ($match) { + $c = hexdec($match[1]); - if (bin2hex($code) > 0xFFFD) { - $code = '\\FFFD'; + if (0x80 > $c %= 0x200000) { + return chr($c); + } + if (0x800 > $c) { + return chr(0xC0 | $c>>6).chr(0x80 | $c & 0x3F); + } + if (0x10000 > $c) { + return chr(0xE0 | $c>>12).chr(0x80 | $c>>6 & 0x3F).chr(0x80 | $c & 0x3F); } - - return mb_convert_encoding(pack('H*', $code), 'UTF-8', 'UCS-2BE'); }, $value); } } diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php index bb8da2fc75..c3a7609a5f 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php @@ -133,25 +133,19 @@ class NumberToLocalizedStringTransformer implements DataTransformerInterface } if (function_exists('mb_detect_encoding') && false !== $encoding = mb_detect_encoding($value)) { - $strlen = function ($string) use ($encoding) { - return mb_strlen($string, $encoding); - }; - $substr = function ($string, $offset, $length) use ($encoding) { - return mb_substr($string, $offset, $length, $encoding); - }; + $length = mb_strlen($value, $encoding); + $remainder = mb_substr($value, $position, $length, $encoding); } else { - $strlen = 'strlen'; - $substr = 'substr'; + $length = strlen($value); + $remainder = substr($value, $position, $length); } - $length = $strlen($value); - // After parsing, position holds the index of the character where the // parsing stopped if ($position < $length) { // Check if there are unrecognized characters at the end of the // number (excluding whitespace characters) - $remainder = trim($substr($value, $position, $length), " \t\n\r\0\x0b\xc2\xa0"); + $remainder = trim($remainder, " \t\n\r\0\x0b\xc2\xa0"); if ('' !== $remainder) { throw new TransformationFailedException( diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php index c58e3f60e7..971dd0a738 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php @@ -119,7 +119,7 @@ class NumberToLocalizedStringTransformerTest extends \PHPUnit_Framework_TestCase // https://github.com/symfony/symfony/issues/7609 public function testReverseTransformWithGroupingAndFixedSpaces() { - if (!extension_loaded('mbstring')) { + if (!function_exists('mb_detect_encoding')) { $this->markTestSkipped('The "mbstring" extension is required for this test.'); } @@ -335,7 +335,7 @@ class NumberToLocalizedStringTransformerTest extends \PHPUnit_Framework_TestCase */ public function testReverseTransformDisallowsCenteredExtraCharactersMultibyte() { - if (!extension_loaded('mbstring')) { + if (!function_exists('mb_detect_encoding')) { $this->markTestSkipped('The "mbstring" extension is required for this test.'); } @@ -352,7 +352,7 @@ class NumberToLocalizedStringTransformerTest extends \PHPUnit_Framework_TestCase */ public function testReverseTransformIgnoresTrailingSpacesInExceptionMessage() { - if (!extension_loaded('mbstring')) { + if (!function_exists('mb_detect_encoding')) { $this->markTestSkipped('The "mbstring" extension is required for this test.'); } @@ -380,7 +380,7 @@ class NumberToLocalizedStringTransformerTest extends \PHPUnit_Framework_TestCase */ public function testReverseTransformDisallowsTrailingExtraCharactersMultibyte() { - if (!extension_loaded('mbstring')) { + if (!function_exists('mb_detect_encoding')) { $this->markTestSkipped('The "mbstring" extension is required for this test.'); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/TrimListenerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/TrimListenerTest.php index 358620b9c4..606f6a2b05 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/TrimListenerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/TrimListenerTest.php @@ -52,8 +52,8 @@ class TrimListenerTest extends \PHPUnit_Framework_TestCase */ public function testTrimUtf8Separators($hex) { - if (!function_exists('mb_check_encoding')) { - $this->markTestSkipped('The "mb_check_encoding" function is not available'); + if (!function_exists('mb_convert_encoding')) { + $this->markTestSkipped('The "mb_convert_encoding" function is not available'); } // Convert hexadecimal representation into binary diff --git a/src/Symfony/Component/Translation/Tests/Dumper/IcuResFileDumperTest.php b/src/Symfony/Component/Translation/Tests/Dumper/IcuResFileDumperTest.php index 7d969ce272..cb30c6661c 100644 --- a/src/Symfony/Component/Translation/Tests/Dumper/IcuResFileDumperTest.php +++ b/src/Symfony/Component/Translation/Tests/Dumper/IcuResFileDumperTest.php @@ -18,7 +18,7 @@ class IcuResFileDumperTest extends \PHPUnit_Framework_TestCase { public function testDump() { - if (!extension_loaded('mbstring')) { + if (!function_exists('mb_convert_encoding')) { $this->markTestSkipped('This test requires mbstring to work.'); } diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php index d4e0a9fa66..9bde67b57b 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -55,7 +55,7 @@ class Parser $this->currentLine = ''; $this->lines = explode("\n", $this->cleanup($value)); - if (function_exists('mb_detect_encoding') && false === mb_detect_encoding($value, 'UTF-8', true)) { + if (!preg_match('//u', $value)) { throw new ParseException('The YAML value does not appear to be valid UTF-8.'); } diff --git a/src/Symfony/Component/Yaml/Tests/ParserTest.php b/src/Symfony/Component/Yaml/Tests/ParserTest.php index 07e6222d7e..2335efc559 100644 --- a/src/Symfony/Component/Yaml/Tests/ParserTest.php +++ b/src/Symfony/Component/Yaml/Tests/ParserTest.php @@ -33,12 +33,6 @@ class ParserTest extends \PHPUnit_Framework_TestCase */ public function testSpecifications($file, $expected, $yaml, $comment) { - if ('escapedCharacters' == $file) { - if (!function_exists('iconv') && !function_exists('mb_convert_encoding')) { - $this->markTestSkipped('The iconv and mbstring extensions are not available.'); - } - } - $this->assertEquals($expected, var_export($this->parser->parse($yaml), true), $comment); } @@ -446,8 +440,8 @@ EOF; public function testNonUtf8Exception() { - if (!function_exists('mb_detect_encoding') || !function_exists('iconv')) { - $this->markTestSkipped('Exceptions for non-utf8 charsets require the mb_detect_encoding() and iconv() functions.'); + if (!function_exists('iconv')) { + $this->markTestSkipped('Exceptions for non-utf8 charsets require the iconv() function.'); return; } diff --git a/src/Symfony/Component/Yaml/Unescaper.php b/src/Symfony/Component/Yaml/Unescaper.php index 1b8eeed57a..b47d4a5928 100644 --- a/src/Symfony/Component/Yaml/Unescaper.php +++ b/src/Symfony/Component/Yaml/Unescaper.php @@ -21,6 +21,7 @@ class Unescaper { // Parser and Inline assume UTF-8 encoding, so escaped Unicode characters // must be converted to that encoding. + // @deprecated since 2.5, to be removed in 3.0 const ENCODING = 'UTF-8'; // Regex fragment that matches an escaped character in a double quoted @@ -80,13 +81,13 @@ class Unescaper case 'n': return "\n"; case 'v': - return "\xb"; + return "\xB"; case 'f': - return "\xc"; + return "\xC"; case 'r': - return "\xd"; + return "\r"; case 'e': - return "\x1b"; + return "\x1B"; case ' ': return ' '; case '"': @@ -97,50 +98,44 @@ class Unescaper return '\\'; case 'N': // U+0085 NEXT LINE - return $this->convertEncoding("\x00\x85", self::ENCODING, 'UCS-2BE'); + return "\xC2\x85"; case '_': // U+00A0 NO-BREAK SPACE - return $this->convertEncoding("\x00\xA0", self::ENCODING, 'UCS-2BE'); + return "\xC2\xA0"; case 'L': // U+2028 LINE SEPARATOR - return $this->convertEncoding("\x20\x28", self::ENCODING, 'UCS-2BE'); + return "\xE2\x80\xA8"; case 'P': // U+2029 PARAGRAPH SEPARATOR - return $this->convertEncoding("\x20\x29", self::ENCODING, 'UCS-2BE'); + return "\xE2\x80\xA9"; case 'x': - $char = pack('n', hexdec(substr($value, 2, 2))); - - return $this->convertEncoding($char, self::ENCODING, 'UCS-2BE'); + return self::utf8chr(hexdec(substr($value, 2, 2))); case 'u': - $char = pack('n', hexdec(substr($value, 2, 4))); - - return $this->convertEncoding($char, self::ENCODING, 'UCS-2BE'); + return self::utf8chr(hexdec(substr($value, 2, 4))); case 'U': - $char = pack('N', hexdec(substr($value, 2, 8))); - - return $this->convertEncoding($char, self::ENCODING, 'UCS-4BE'); + return self::utf8chr(hexdec(substr($value, 2, 8))); } } /** - * Convert a string from one encoding to another. + * Get the UTF-8 character for the given code point. * - * @param string $value The string to convert - * @param string $to The input encoding - * @param string $from The output encoding + * @param int $c The unicode code point * - * @return string The string with the new encoding - * - * @throws \RuntimeException if no suitable encoding function is found (iconv or mbstring) + * @return string The corresponding UTF-8 character */ - private function convertEncoding($value, $to, $from) + private static function utf8chr($c) { - if (function_exists('mb_convert_encoding')) { - return mb_convert_encoding($value, $to, $from); - } elseif (function_exists('iconv')) { - return iconv($from, $to, $value); + if (0x80 > $c %= 0x200000) { + return chr($c); + } + if (0x800 > $c) { + return chr(0xC0 | $c>>6).chr(0x80 | $c & 0x3F); + } + if (0x10000 > $c) { + return chr(0xE0 | $c>>12).chr(0x80 | $c>>6 & 0x3F).chr(0x80 | $c & 0x3F); } - throw new \RuntimeException('No suitable convert encoding function (install the iconv or mbstring extension).'); + return chr(0xF0 | $c>>18).chr(0x80 | $c>>12 & 0x3F).chr(0x80 | $c>>6 & 0x3F).chr(0x80 | $c & 0x3F); } } From a52f41d4142e882ba628832d9a2666b3e62d6e65 Mon Sep 17 00:00:00 2001 From: Daisuke Ohata Date: Tue, 15 Apr 2014 19:04:31 +0900 Subject: [PATCH 18/19] [Console]Improve formatter for double-width character --- src/Symfony/Component/Console/Application.php | 91 +++++++++++++------ .../Component/Console/Helper/Helper.php | 6 +- .../Console/Tests/ApplicationTest.php | 27 ++++++ ...plication_renderexception_doublewidth1.txt | 11 +++ ..._renderexception_doublewidth1decorated.txt | 11 +++ ...plication_renderexception_doublewidth2.txt | 12 +++ .../Tests/Helper/FormatterHelperTest.php | 15 +++ 7 files changed, 143 insertions(+), 30 deletions(-) create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth1.txt create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth1decorated.txt create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth2.txt diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index 0753e19ed9..2833bd23de 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -99,7 +99,7 @@ class Application * @param InputInterface $input An Input instance * @param OutputInterface $output An Output instance * - * @return integer 0 if everything went fine, or an error code + * @return int 0 if everything went fine, or an error code * * @throws \Exception When doRun returns Exception * @@ -159,7 +159,7 @@ class Application * @param InputInterface $input An Input instance * @param OutputInterface $output An Output instance * - * @return integer 0 if everything went fine, or an error code + * @return int 0 if everything went fine, or an error code */ public function doRun(InputInterface $input, OutputInterface $output) { @@ -270,7 +270,7 @@ class Application /** * Sets whether to catch exceptions or not during commands execution. * - * @param bool $boolean Whether to catch exceptions or not during commands execution + * @param bool $boolean Whether to catch exceptions or not during commands execution * * @api */ @@ -282,7 +282,7 @@ class Application /** * Sets whether to automatically exit after a command execution or not. * - * @param bool $boolean Whether to automatically exit after a command execution or not + * @param bool $boolean Whether to automatically exit after a command execution or not * * @api */ @@ -449,7 +449,7 @@ class Application * * @param string $name The command name or alias * - * @return Boolean true if the command exists, false otherwise + * @return bool true if the command exists, false otherwise * * @api */ @@ -674,8 +674,8 @@ class Application /** * Returns a text representation of the Application. * - * @param string $namespace An optional namespace name - * @param bool $raw Whether to return raw command list + * @param string $namespace An optional namespace name + * @param bool $raw Whether to return raw command list * * @return string A string representing the Application * @@ -691,8 +691,8 @@ class Application /** * Returns an XML representation of the Application. * - * @param string $namespace An optional namespace name - * @param bool $asDom Whether to return a DOM or an XML string + * @param string $namespace An optional namespace name + * @param bool $asDom Whether to return a DOM or an XML string * * @return string|\DOMDocument An XML string representing the Application * @@ -708,34 +708,22 @@ class Application /** * Renders a caught exception. * - * @param \Exception $e An exception instance + * @param \Exception $e An exception instance * @param OutputInterface $output An OutputInterface instance */ public function renderException($e, $output) { - $strlen = function ($string) { - if (!function_exists('mb_strlen')) { - return strlen($string); - } - - if (false === $encoding = mb_detect_encoding($string)) { - return strlen($string); - } - - return mb_strlen($string, $encoding); - }; - do { $title = sprintf(' [%s] ', get_class($e)); - $len = $strlen($title); + $len = $this->stringWidth($title); // HHVM only accepts 32 bits integer in str_split, even when PHP_INT_MAX is a 64 bit integer: https://github.com/facebook/hhvm/issues/1327 $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : (defined('HHVM_VERSION') ? 1 << 31 : PHP_INT_MAX); $formatter = $output->getFormatter(); $lines = array(); foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) { - foreach (str_split($line, $width - 4) as $line) { + foreach ($this->splitStringByWidth($line, $width - 4) as $line) { // pre-format lines to get the right string length - $lineLength = $strlen(preg_replace('/\[[^m]*m/', '', $formatter->format($line))) + 4; + $lineLength = $this->stringWidth(preg_replace('/\[[^m]*m/', '', $formatter->format($line))) + 4; $lines[] = array($line, $lineLength); $len = max($lineLength, $len); @@ -744,7 +732,7 @@ class Application $messages = array('', ''); $messages[] = $emptyLine = $formatter->format(sprintf('%s', str_repeat(' ', $len))); - $messages[] = $formatter->format(sprintf('%s%s', $title, str_repeat(' ', max(0, $len - $strlen($title))))); + $messages[] = $formatter->format(sprintf('%s%s', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title))))); foreach ($lines as $line) { $messages[] = $formatter->format(sprintf(' %s %s', $line[0], str_repeat(' ', $len - $line[1]))); } @@ -890,7 +878,7 @@ class Application * @param InputInterface $input An Input instance * @param OutputInterface $output An Output instance * - * @return integer 0 if everything went fine, or an error code + * @return int 0 if everything went fine, or an error code */ protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) { @@ -1125,4 +1113,53 @@ class Application return array_keys($alternatives); } + + private function stringWidth($string) + { + if (!function_exists('mb_strwidth')) { + return strlen($string); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return strlen($string); + } + + return mb_strwidth($string, $encoding); + } + + private function splitStringByWidth($string, $width) + { + // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly. + // additionally, array_slice() is not enough as some character has doubled width. + // we need a function to split string not by character count but by string width + + if (!function_exists('mb_strwidth')) { + return str_split($string, $width); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return str_split($string, $width); + } + + $utf8String = mb_convert_encoding($string, 'utf8', $encoding); + $lines = array(); + $line = ''; + foreach (preg_split('//u', $utf8String) as $char) { + // test if $char could be appended to current line + if (mb_strwidth($line.$char) <= $width) { + $line .= $char; + continue; + } + // if not, push current line to array and make new line + $lines[] = str_pad($line, $width); + $line = $char; + } + if (strlen($line)) { + $lines[] = count($lines) ? str_pad($line, $width) : $line; + } + + mb_convert_variables($encoding, 'utf8', $lines); + + return $lines; + } } diff --git a/src/Symfony/Component/Console/Helper/Helper.php b/src/Symfony/Component/Console/Helper/Helper.php index 534b9f4319..b2a8389fa0 100644 --- a/src/Symfony/Component/Console/Helper/Helper.php +++ b/src/Symfony/Component/Console/Helper/Helper.php @@ -45,11 +45,11 @@ abstract class Helper implements HelperInterface * * @param string $string The string to check its length * - * @return integer The length of the string + * @return int The length of the string */ protected function strlen($string) { - if (!function_exists('mb_strlen')) { + if (!function_exists('mb_strwidth')) { return strlen($string); } @@ -57,6 +57,6 @@ abstract class Helper implements HelperInterface return strlen($string); } - return mb_strlen($string, $encoding); + return mb_strwidth($string, $encoding); } } diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index 10dcb30951..0965d24ad5 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -469,6 +469,33 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception4.txt', $tester->getDisplay(true), '->renderException() wraps messages when they are bigger than the terminal'); } + public function testRenderExceptionWithDoubleWidthCharacters() + { + $application = $this->getMock('Symfony\Component\Console\Application', array('getTerminalWidth')); + $application->setAutoExit(false); + $application->expects($this->any()) + ->method('getTerminalWidth') + ->will($this->returnValue(120)); + $application->register('foo')->setCode(function () {throw new \Exception('エラーメッセージ');}); + $tester = new ApplicationTester($application); + + $tester->run(array('command' => 'foo'), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth1.txt', $tester->getDisplay(true), '->renderException() renderes a pretty exceptions with previous exceptions'); + + $tester->run(array('command' => 'foo'), array('decorated' => true)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth1decorated.txt', $tester->getDisplay(true), '->renderException() renderes a pretty exceptions with previous exceptions'); + + $application = $this->getMock('Symfony\Component\Console\Application', array('getTerminalWidth')); + $application->setAutoExit(false); + $application->expects($this->any()) + ->method('getTerminalWidth') + ->will($this->returnValue(32)); + $application->register('foo')->setCode(function () {throw new \Exception('コマンドの実行中にエラーが発生しました。');}); + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo'), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth2.txt', $tester->getDisplay(true), '->renderException() wraps messages when they are bigger than the terminal'); + } + public function testRun() { $application = new Application(); diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth1.txt b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth1.txt new file mode 100644 index 0000000000..6a98660364 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth1.txt @@ -0,0 +1,11 @@ + + + + [Exception] + エラーメッセージ + + + +foo + + diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth1decorated.txt b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth1decorated.txt new file mode 100644 index 0000000000..c68a60f564 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth1decorated.txt @@ -0,0 +1,11 @@ + + +  + [Exception]  + エラーメッセージ  +  + + +foo + + diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth2.txt b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth2.txt new file mode 100644 index 0000000000..545cd7b0b4 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth2.txt @@ -0,0 +1,12 @@ + + + + [Exception] + コマンドの実行中にエラーが + 発生しました。 + + + +foo + + diff --git a/src/Symfony/Component/Console/Tests/Helper/FormatterHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/FormatterHelperTest.php index 87a45e1d44..34d70d8fc2 100644 --- a/src/Symfony/Component/Console/Tests/Helper/FormatterHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/FormatterHelperTest.php @@ -69,6 +69,21 @@ class FormatterHelperTest extends \PHPUnit_Framework_TestCase ); } + public function testFormatBlockWithDoubleWidthDiacriticLetters() + { + if (!extension_loaded('mbstring')) { + $this->markTestSkipped('This test requires mbstring to work.'); + } + $formatter = new FormatterHelper(); + $this->assertEquals( + ' '."\n" . + ' 表示するテキスト '."\n" . + ' ', + $formatter->formatBlock('表示するテキスト', 'error', true), + '::formatBlock() formats a message in a block' + ); + } + public function testFormatBlockLGEscaping() { $formatter = new FormatterHelper(); From 6ab671361b0052aec2db20f2fa0e8ce435886978 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 12 May 2014 11:22:51 +0200 Subject: [PATCH 19/19] Lower mbstring dependency --- .../FrameworkBundle/Console/Descriptor/Descriptor.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php index 3b3b28f570..c6c3ebb284 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php @@ -211,12 +211,8 @@ abstract class Descriptor implements DescriptorInterface if (is_bool($value) || is_array($value) || (null === $value)) { $jsonString = json_encode($value); - if (!function_exists('mb_strlen')) { - return substr($jsonString, 0, 60).(strlen($jsonString) > 60 ? ' ...' : ''); - } - - if (mb_strlen($jsonString) > 60) { - return mb_substr($jsonString, 0, 60).' ...'; + if (preg_match('/^(.{60})./us', $jsonString, $matches)) { + return $matches[1].'...'; } return $jsonString;