Merge branch '2.3' into 2.4

* 2.3:
  [Console]Improve formatter for double-width character
  Lower mbstring dep, remove it for Yaml and CssSelector components
  [Security] Add check for supported attributes in AclVoter
  [Form] Fixed TrimListenerTest as of PHP 5.5
  Added more IDE links
  [DependencyInjection] Fix parameter description in ConfigurationExtensionInterface
  [Finder] fixed typehint of the Finder::addAdapter() method
  [TwigBridge][Transchoice] set %count% from the current context.
  [DependencyInjection] Fix travis unit tests
  Update PHPUnit before run
  [Validator] fixed wrong test
  [WebProfilerBundle] added test case for #10773
  [WebProfilerBundle] fixed profiler homepage, fixed #10806
  [WebProfilerBundle] Added test case for #10806
  changed travis to run on the nightly builds of HHVM until everything gets stable
  Fixed issue #5427
  Allow URLs that don't contain a path

Conflicts:
	.travis.yml
This commit is contained in:
Fabien Potencier 2014-05-12 11:27:48 +02:00
commit 934cd28ba6
30 changed files with 386 additions and 126 deletions

View File

@ -6,22 +6,23 @@ php:
- 5.4 - 5.4
- 5.5 - 5.5
- 5.6 - 5.6
- hhvm - hhvm-nightly
matrix: matrix:
allow_failures: allow_failures:
- php: hhvm - php: hhvm-nightly
services: mongodb services: mongodb
before_script: before_script:
- travis_retry sudo apt-get install parallel - 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-nightly" ]; 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-nightly" ]; 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-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" ]; 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 = 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 "extension = memcache.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;'
- COMPOSER_ROOT_VERSION=dev-master composer --prefer-source --dev install - 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: script:
- ls -d src/Symfony/*/* | parallel --gnu --keep-order 'echo "Running {} tests"; phpunit --exclude-group tty,benchmark {};' || exit 1 - ls -d src/Symfony/*/* | parallel --gnu --keep-order 'echo "Running {} tests"; phpunit --exclude-group tty,benchmark {};' || exit 1

View File

@ -98,9 +98,13 @@ class TransNode extends \Twig_Node
foreach ($matches[1] as $var) { foreach ($matches[1] as $var) {
$key = new \Twig_Node_Expression_Constant('%'.$var.'%', $body->getLine()); $key = new \Twig_Node_Expression_Constant('%'.$var.'%', $body->getLine());
if (!$vars->hasElement($key)) { if (!$vars->hasElement($key)) {
$varExpr = new \Twig_Node_Expression_Name($var, $body->getLine()); if ('count' === $var) {
$varExpr->setAttribute('ignore_strict_check', $ignoreStrictCheck); $vars->addElement($this->getNode('count'), $key);
$vars->addElement($varExpr, $key); } else {
$varExpr = new \Twig_Node_Expression_Name($var, $body->getLine());
$varExpr->setAttribute('ignore_strict_check', $ignoreStrictCheck);
$vars->addElement($varExpr, $key);
}
} }
} }

View File

@ -96,6 +96,8 @@ class TranslationExtensionTest extends \PHPUnit_Framework_TestCase
'There is 5 apples (Symfony2)', array('count' => 5)), '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 %}', 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)), '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 // trans filter
array('{{ "Hello"|trans }}', 'Hello'), array('{{ "Hello"|trans }}', 'Hello'),

View File

@ -398,6 +398,8 @@ class FrameworkExtension extends Extension
$links = array( $links = array(
'textmate' => 'txmt://open?url=file://%%f&line=%%l', 'textmate' => 'txmt://open?url=file://%%f&line=%%l',
'macvim' => 'mvim://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); $container->setParameter('templating.helper.code.file_link_format', isset($links[$ide]) ? $links[$ide] : $ide);

View File

@ -232,7 +232,7 @@ class ProfilerController
$session->getFlashBag()->setAll($session->getFlashBag()->peekAll()); $session->getFlashBag()->setAll($session->getFlashBag()->peekAll());
} }
if (null === $token) { if ('empty' === $token || null === $token) {
return new Response('', 200, array('Content-Type' => 'text/html')); return new Response('', 200, array('Content-Type' => 'text/html'));
} }

View File

@ -0,0 +1,77 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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\HttpKernel\Profiler\Profile;
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'),
);
}
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());
}
}

View File

@ -152,12 +152,12 @@ class Cookie
); );
if (null !== $url) { 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)); throw new \InvalidArgumentException(sprintf('The URL "%s" is not valid.', $url));
} }
$values['domain'] = $urlParts['host']; $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) { foreach ($parts as $part) {

View File

@ -75,6 +75,8 @@ class CookieTest extends \PHPUnit_Framework_TestCase
public function testFromStringWithUrl() 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'));
$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=/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.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/')); $this->assertEquals('foo=bar; domain=www.myotherexample.com; path=/', (string) Cookie::FromString('foo=bar; domain=www.myotherexample.com', 'http://www.example.com/'));

View File

@ -99,7 +99,7 @@ class Application
* @param InputInterface $input An Input instance * @param InputInterface $input An Input instance
* @param OutputInterface $output An Output instance * @param OutputInterface $output An Output instance
* *
* @return int 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 * @throws \Exception When doRun returns Exception
* *
@ -159,7 +159,7 @@ class Application
* @param InputInterface $input An Input instance * @param InputInterface $input An Input instance
* @param OutputInterface $output An Output instance * @param OutputInterface $output An Output instance
* *
* @return int 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) public function doRun(InputInterface $input, OutputInterface $output)
{ {
@ -270,7 +270,7 @@ class Application
/** /**
* Sets whether to catch exceptions or not during commands execution. * 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 * @api
*/ */
@ -282,7 +282,7 @@ class Application
/** /**
* Sets whether to automatically exit after a command execution or not. * 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 * @api
*/ */
@ -453,7 +453,7 @@ class Application
* *
* @param string $name The command name or alias * @param string $name The command name or alias
* *
* @return bool true if the command exists, false otherwise * @return bool true if the command exists, false otherwise
* *
* @api * @api
*/ */
@ -632,8 +632,8 @@ class Application
/** /**
* Returns a text representation of the Application. * Returns a text representation of the Application.
* *
* @param string $namespace An optional namespace name * @param string $namespace An optional namespace name
* @param bool $raw Whether to return raw command list * @param bool $raw Whether to return raw command list
* *
* @return string A string representing the Application * @return string A string representing the Application
* *
@ -651,8 +651,8 @@ class Application
/** /**
* Returns an XML representation of the Application. * Returns an XML representation of the Application.
* *
* @param string $namespace An optional namespace name * @param string $namespace An optional namespace name
* @param bool $asDom Whether to return a DOM or an XML string * @param bool $asDom Whether to return a DOM or an XML string
* *
* @return string|\DOMDocument An XML string representing the Application * @return string|\DOMDocument An XML string representing the Application
* *
@ -675,26 +675,16 @@ class Application
/** /**
* Renders a caught exception. * Renders a caught exception.
* *
* @param \Exception $e An exception instance * @param \Exception $e An exception instance
* @param OutputInterface $output An OutputInterface instance * @param OutputInterface $output An OutputInterface instance
*/ */
public function renderException($e, $output) 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 { do {
$title = sprintf(' [%s] ', get_class($e)); $title = sprintf(' [%s] ', get_class($e));
$len = $strlen($title);
$len = $this->stringWidth($title);
$width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX; $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX;
// 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 // 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
if (defined('HHVM_VERSION') && $width > 1 << 31) { if (defined('HHVM_VERSION') && $width > 1 << 31) {
@ -703,9 +693,9 @@ class Application
$formatter = $output->getFormatter(); $formatter = $output->getFormatter();
$lines = array(); $lines = array();
foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) { 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 // 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); $lines[] = array($line, $lineLength);
$len = max($lineLength, $len); $len = max($lineLength, $len);
@ -714,7 +704,7 @@ class Application
$messages = array('', ''); $messages = array('', '');
$messages[] = $emptyLine = $formatter->format(sprintf('<error>%s</error>', str_repeat(' ', $len))); $messages[] = $emptyLine = $formatter->format(sprintf('<error>%s</error>', str_repeat(' ', $len)));
$messages[] = $formatter->format(sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - $strlen($title))))); $messages[] = $formatter->format(sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title)))));
foreach ($lines as $line) { foreach ($lines as $line) {
$messages[] = $formatter->format(sprintf('<error> %s %s</error>', $line[0], str_repeat(' ', $len - $line[1]))); $messages[] = $formatter->format(sprintf('<error> %s %s</error>', $line[0], str_repeat(' ', $len - $line[1])));
} }
@ -881,7 +871,7 @@ class Application
* @param InputInterface $input An Input instance * @param InputInterface $input An Input instance
* @param OutputInterface $output An Output instance * @param OutputInterface $output An Output instance
* *
* @return int 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) protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
{ {
@ -1101,4 +1091,53 @@ class Application
return array_keys($alternatives); 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;
}
} }

View File

@ -49,7 +49,7 @@ abstract class Helper implements HelperInterface
*/ */
protected function strlen($string) protected function strlen($string)
{ {
if (!function_exists('mb_strlen')) { if (!function_exists('mb_strwidth')) {
return strlen($string); return strlen($string);
} }
@ -57,6 +57,6 @@ abstract class Helper implements HelperInterface
return strlen($string); return strlen($string);
} }
return mb_strlen($string, $encoding); return mb_strwidth($string, $encoding);
} }
} }

View File

@ -523,6 +523,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'); $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() public function testRun()
{ {
$application = new Application(); $application = new Application();

View File

@ -0,0 +1,11 @@
[Exception]
エラーメッセージ
foo

View File

@ -0,0 +1,11 @@
 
 [Exception] 
 エラーメッセージ 
 
foo

View File

@ -0,0 +1,12 @@
[Exception]
コマンドの実行中にエラーが
発生しました。
foo

View File

@ -54,7 +54,7 @@ class FormatterHelperTest extends \PHPUnit_Framework_TestCase
public function testFormatBlockWithDiacriticLetters() public function testFormatBlockWithDiacriticLetters()
{ {
if (!extension_loaded('mbstring')) { if (!function_exists('mb_detect_encoding')) {
$this->markTestSkipped('This test requires mbstring to work.'); $this->markTestSkipped('This test requires mbstring to work.');
} }
@ -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(
'<error> </error>'."\n" .
'<error> 表示するテキスト </error>'."\n" .
'<error> </error>',
$formatter->formatBlock('表示するテキスト', 'error', true),
'::formatBlock() formats a message in a block'
);
}
public function testFormatBlockLGEscaping() public function testFormatBlockLGEscaping()
{ {
$formatter = new FormatterHelper(); $formatter = new FormatterHelper();

View File

@ -65,14 +65,18 @@ class TokenizerEscaping
*/ */
private function replaceUnicodeSequences($value) private function replaceUnicodeSequences($value)
{ {
return preg_replace_callback($this->patterns->getUnicodeEscapePattern(), function (array $match) { return preg_replace_callback($this->patterns->getUnicodeEscapePattern(), function ($match) {
$code = $match[1]; $c = hexdec($match[1]);
if (bin2hex($code) > 0xFFFD) { if (0x80 > $c %= 0x200000) {
$code = '\\FFFD'; 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); }, $value);
} }
} }

View File

@ -24,7 +24,7 @@ interface ConfigurationExtensionInterface
/** /**
* Returns extension configuration * 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 * @param ContainerBuilder $container A ContainerBuilder instance
* *
* @return ConfigurationInterface|null The configuration or null * @return ConfigurationInterface|null The configuration or null

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8"?>
<container xmlns="http://symfony.com/schema/dic/services" <container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

View File

@ -94,7 +94,7 @@ class Finder implements \IteratorAggregate, \Countable
* *
* @return Finder The current Finder instance * @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( $this->adapters[$adapter->getName()] = array(
'adapter' => $adapter, 'adapter' => $adapter,

View File

@ -195,25 +195,19 @@ class NumberToLocalizedStringTransformer implements DataTransformerInterface
} }
if (function_exists('mb_detect_encoding') && false !== $encoding = mb_detect_encoding($value)) { if (function_exists('mb_detect_encoding') && false !== $encoding = mb_detect_encoding($value)) {
$strlen = function ($string) use ($encoding) { $length = mb_strlen($value, $encoding);
return mb_strlen($string, $encoding); $remainder = mb_substr($value, $position, $length, $encoding);
};
$substr = function ($string, $offset, $length) use ($encoding) {
return mb_substr($string, $offset, $length, $encoding);
};
} else { } else {
$strlen = 'strlen'; $length = strlen($value);
$substr = 'substr'; $remainder = substr($value, $position, $length);
} }
$length = $strlen($value);
// After parsing, position holds the index of the character where the // After parsing, position holds the index of the character where the
// parsing stopped // parsing stopped
if ($position < $length) { if ($position < $length) {
// Check if there are unrecognized characters at the end of the // Check if there are unrecognized characters at the end of the
// number (excluding whitespace characters) // 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) { if ('' !== $remainder) {
throw new TransformationFailedException( throw new TransformationFailedException(

View File

@ -215,7 +215,7 @@ class NumberToLocalizedStringTransformerTest extends \PHPUnit_Framework_TestCase
// https://github.com/symfony/symfony/issues/7609 // https://github.com/symfony/symfony/issues/7609
public function testReverseTransformWithGroupingAndFixedSpaces() public function testReverseTransformWithGroupingAndFixedSpaces()
{ {
if (!extension_loaded('mbstring')) { if (!function_exists('mb_detect_encoding')) {
$this->markTestSkipped('The "mbstring" extension is required for this test.'); $this->markTestSkipped('The "mbstring" extension is required for this test.');
} }
@ -537,7 +537,7 @@ class NumberToLocalizedStringTransformerTest extends \PHPUnit_Framework_TestCase
*/ */
public function testReverseTransformDisallowsCenteredExtraCharactersMultibyte() public function testReverseTransformDisallowsCenteredExtraCharactersMultibyte()
{ {
if (!extension_loaded('mbstring')) { if (!function_exists('mb_detect_encoding')) {
$this->markTestSkipped('The "mbstring" extension is required for this test.'); $this->markTestSkipped('The "mbstring" extension is required for this test.');
} }
@ -554,7 +554,7 @@ class NumberToLocalizedStringTransformerTest extends \PHPUnit_Framework_TestCase
*/ */
public function testReverseTransformIgnoresTrailingSpacesInExceptionMessage() public function testReverseTransformIgnoresTrailingSpacesInExceptionMessage()
{ {
if (!extension_loaded('mbstring')) { if (!function_exists('mb_detect_encoding')) {
$this->markTestSkipped('The "mbstring" extension is required for this test.'); $this->markTestSkipped('The "mbstring" extension is required for this test.');
} }
@ -582,7 +582,7 @@ class NumberToLocalizedStringTransformerTest extends \PHPUnit_Framework_TestCase
*/ */
public function testReverseTransformDisallowsTrailingExtraCharactersMultibyte() public function testReverseTransformDisallowsTrailingExtraCharactersMultibyte()
{ {
if (!extension_loaded('mbstring')) { if (!function_exists('mb_detect_encoding')) {
$this->markTestSkipped('The "mbstring" extension is required for this test.'); $this->markTestSkipped('The "mbstring" extension is required for this test.');
} }

View File

@ -41,32 +41,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')) { if (!function_exists('mb_convert_encoding')) {
$this->markTestSkipped('The "mb_check_encoding" function is not available'); $this->markTestSkipped('The "mb_convert_encoding" function is not available');
} }
$data = mb_convert_encoding(pack('H*', implode('', $chars)), 'UTF-8', 'UCS-2BE'); // Convert hexadecimal representation into binary
$data = $data."ab\ncd".$data; // 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'); $form = $this->getMock('Symfony\Component\Form\Test\FormInterface');
$event = new FormEvent($form, $data); $event = new FormEvent($form, $symbol);
$filter = new TrimListener(); $filter = new TrimListener();
$filter->preSubmit($event); $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( 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')), // separators
'General category: Other, control' => array(array('0009', '000A', '000B', '000C', '000D', '0085')), array('0020'),
//'General category: Other, format. ZERO WIDTH SPACE' => array(array('200B')), 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'),
); );
} }
} }

View File

@ -27,7 +27,7 @@ class AclVoterTest extends \PHPUnit_Framework_TestCase
*/ */
public function testSupportsAttribute($attribute, $supported) public function testSupportsAttribute($attribute, $supported)
{ {
list($voter,, $permissionMap,,) = $this->getVoter(); list($voter,, $permissionMap,,) = $this->getVoter(true, false);
$permissionMap $permissionMap
->expects($this->once()) ->expects($this->once())
@ -39,6 +39,16 @@ class AclVoterTest extends \PHPUnit_Framework_TestCase
$this->assertSame($supported, $voter->supportsAttribute($attribute)); $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() public function getSupportsAttributeTests()
{ {
return array( 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 * @dataProvider getSupportsClassTests
*/ */
@ -387,13 +407,20 @@ class AclVoterTest extends \PHPUnit_Framework_TestCase
return $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); 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'); $provider = $this->getMock('Symfony\Component\Security\Acl\Model\AclProviderInterface');
$permissionMap = $this->getMock('Symfony\Component\Security\Acl\Permission\PermissionMapInterface'); $permissionMap = $this->getMock('Symfony\Component\Security\Acl\Permission\PermissionMapInterface');
$oidStrategy = $this->getMock('Symfony\Component\Security\Acl\Model\ObjectIdentityRetrievalStrategyInterface'); $oidStrategy = $this->getMock('Symfony\Component\Security\Acl\Model\ObjectIdentityRetrievalStrategyInterface');
$sidStrategy = $this->getMock('Symfony\Component\Security\Acl\Model\SecurityIdentityRetrievalStrategyInterface'); $sidStrategy = $this->getMock('Symfony\Component\Security\Acl\Model\SecurityIdentityRetrievalStrategyInterface');
if ($alwaysContains) {
$permissionMap
->expects($this->any())
->method('contains')
->will($this->returnValue(true));
}
return array( return array(
new AclVoter($provider, $oidStrategy, $sidStrategy, $permissionMap, null, $allowIfObjectIdentityUnavailable), new AclVoter($provider, $oidStrategy, $sidStrategy, $permissionMap, null, $allowIfObjectIdentityUnavailable),
$provider, $provider,

View File

@ -48,12 +48,16 @@ class AclVoter implements VoterInterface
public function supportsAttribute($attribute) 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) public function vote(TokenInterface $token, $object, array $attributes)
{ {
foreach ($attributes as $attribute) { foreach ($attributes as $attribute) {
if (!$this->supportsAttribute($attribute)) {
continue;
}
if (null === $masks = $this->permissionMap->getMasks($attribute, $object)) { if (null === $masks = $this->permissionMap->getMasks($attribute, $object)) {
continue; continue;
} }

View File

@ -157,7 +157,7 @@ class PoFileLoader extends ArrayLoader implements LoaderInterface
private function addMessage(array &$messages, array $item) private function addMessage(array &$messages, array $item)
{ {
if (is_array($item['translated'])) { 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'])) { if (isset($item['ids']['plural'])) {
$plurals = $item['translated']; $plurals = $item['translated'];
// PO are by definition indexed so sort by index. // 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)); $messages[$item['ids']['plural']] = stripcslashes(implode('|', $plurals));
} }
} elseif (!empty($item['ids']['singular'])) { } elseif (!empty($item['ids']['singular'])) {
$messages[$item['ids']['singular']] = stripslashes($item['translated']); $messages[$item['ids']['singular']] = stripcslashes($item['translated']);
} }
} }
} }

View File

@ -18,7 +18,7 @@ class IcuResFileDumperTest extends \PHPUnit_Framework_TestCase
{ {
public function testDump() public function testDump()
{ {
if (!extension_loaded('mbstring')) { if (!function_exists('mb_convert_encoding')) {
$this->markTestSkipped('This test requires mbstring to work.'); $this->markTestSkipped('This test requires mbstring to work.');
} }

View File

@ -79,10 +79,16 @@ class StaticMethodLoaderTest extends \PHPUnit_Framework_TestCase
public function testLoadClassMetadataIgnoresAbstractMethods() public function testLoadClassMetadataIgnoresAbstractMethods()
{ {
$loader = new StaticMethodLoader('loadMetadata'); $loader = new StaticMethodLoader('loadMetadata');
$caught = false;
try { try {
include __DIR__ . '/AbstractMethodStaticLoader.php'; include __DIR__.'/AbstractMethodStaticLoader.php';
$this->fail('AbstractMethodStaticLoader should produce a strict standard error.');
} catch (\Exception $e) { } 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'); $metadata = new ClassMetadata(__NAMESPACE__.'\AbstractMethodStaticLoader');

View File

@ -55,7 +55,7 @@ class Parser
$this->currentLine = ''; $this->currentLine = '';
$this->lines = explode("\n", $this->cleanup($value)); $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.'); throw new ParseException('The YAML value does not appear to be valid UTF-8.');
} }

View File

@ -33,12 +33,6 @@ class ParserTest extends \PHPUnit_Framework_TestCase
*/ */
public function testSpecifications($file, $expected, $yaml, $comment) 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); $this->assertEquals($expected, var_export($this->parser->parse($yaml), true), $comment);
} }
@ -446,8 +440,8 @@ EOF;
public function testNonUtf8Exception() public function testNonUtf8Exception()
{ {
if (!function_exists('mb_detect_encoding') || !function_exists('iconv')) { if (!function_exists('iconv')) {
$this->markTestSkipped('Exceptions for non-utf8 charsets require the mb_detect_encoding() and iconv() functions.'); $this->markTestSkipped('Exceptions for non-utf8 charsets require the iconv() function.');
return; return;
} }

View File

@ -21,6 +21,7 @@ class Unescaper
{ {
// Parser and Inline assume UTF-8 encoding, so escaped Unicode characters // Parser and Inline assume UTF-8 encoding, so escaped Unicode characters
// must be converted to that encoding. // must be converted to that encoding.
// @deprecated since 2.5, to be removed in 3.0
const ENCODING = 'UTF-8'; const ENCODING = 'UTF-8';
// Regex fragment that matches an escaped character in a double quoted // Regex fragment that matches an escaped character in a double quoted
@ -80,13 +81,13 @@ class Unescaper
case 'n': case 'n':
return "\n"; return "\n";
case 'v': case 'v':
return "\xb"; return "\xB";
case 'f': case 'f':
return "\xc"; return "\xC";
case 'r': case 'r':
return "\xd"; return "\r";
case 'e': case 'e':
return "\x1b"; return "\x1B";
case ' ': case ' ':
return ' '; return ' ';
case '"': case '"':
@ -97,50 +98,44 @@ class Unescaper
return '\\'; return '\\';
case 'N': case 'N':
// U+0085 NEXT LINE // U+0085 NEXT LINE
return $this->convertEncoding("\x00\x85", self::ENCODING, 'UCS-2BE'); return "\xC2\x85";
case '_': case '_':
// U+00A0 NO-BREAK SPACE // U+00A0 NO-BREAK SPACE
return $this->convertEncoding("\x00\xA0", self::ENCODING, 'UCS-2BE'); return "\xC2\xA0";
case 'L': case 'L':
// U+2028 LINE SEPARATOR // U+2028 LINE SEPARATOR
return $this->convertEncoding("\x20\x28", self::ENCODING, 'UCS-2BE'); return "\xE2\x80\xA8";
case 'P': case 'P':
// U+2029 PARAGRAPH SEPARATOR // U+2029 PARAGRAPH SEPARATOR
return $this->convertEncoding("\x20\x29", self::ENCODING, 'UCS-2BE'); return "\xE2\x80\xA9";
case 'x': case 'x':
$char = pack('n', hexdec(substr($value, 2, 2))); return self::utf8chr(hexdec(substr($value, 2, 2)));
return $this->convertEncoding($char, self::ENCODING, 'UCS-2BE');
case 'u': case 'u':
$char = pack('n', hexdec(substr($value, 2, 4))); return self::utf8chr(hexdec(substr($value, 2, 4)));
return $this->convertEncoding($char, self::ENCODING, 'UCS-2BE');
case 'U': case 'U':
$char = pack('N', hexdec(substr($value, 2, 8))); return self::utf8chr(hexdec(substr($value, 2, 8)));
return $this->convertEncoding($char, self::ENCODING, 'UCS-4BE');
} }
} }
/** /**
* 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 int $c The unicode code point
* @param string $to The input encoding
* @param string $from The output encoding
* *
* @return string The string with the new encoding * @return string The corresponding UTF-8 character
*
* @throws \RuntimeException if no suitable encoding function is found (iconv or mbstring)
*/ */
private function convertEncoding($value, $to, $from) private static function utf8chr($c)
{ {
if (function_exists('mb_convert_encoding')) { if (0x80 > $c %= 0x200000) {
return mb_convert_encoding($value, $to, $from); return chr($c);
} elseif (function_exists('iconv')) { }
return iconv($from, $to, $value); 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);
} }
} }