diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index 80ab7fb5ed..c7798bf2c5 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -491,6 +491,10 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase public function testRenderExceptionWithDoubleWidthCharacters() { + if (!function_exists('mb_strwidth')) { + $this->markTestSkipped('The "mb_strwidth" function is not available'); + } + $application = $this->getMock('Symfony\Component\Console\Application', array('getTerminalWidth')); $application->setAutoExit(false); $application->expects($this->any()) diff --git a/src/Symfony/Component/DomCrawler/Crawler.php b/src/Symfony/Component/DomCrawler/Crawler.php index ebe89c0ded..823f135239 100644 --- a/src/Symfony/Component/DomCrawler/Crawler.php +++ b/src/Symfony/Component/DomCrawler/Crawler.php @@ -156,20 +156,43 @@ class Crawler extends \SplObjectStorage $dom = new \DOMDocument('1.0', $charset); $dom->validateOnParse = true; - if (function_exists('mb_convert_encoding')) { - $hasError = false; - set_error_handler(function () use (&$hasError) { - $hasError = true; - }); - $tmpContent = @mb_convert_encoding($content, 'HTML-ENTITIES', $charset); + set_error_handler(function () {throw new \Exception();}); - restore_error_handler(); + try { + // Convert charset to HTML-entities to work around bugs in DOMDocument::loadHTML() - if (!$hasError) { - $content = $tmpContent; + if (function_exists('mb_convert_encoding')) { + $content = mb_convert_encoding($content, 'HTML-ENTITIES', $charset); + } elseif (function_exists('iconv')) { + $content = preg_replace_callback( + '/[\x80-\xFF]+/', + function ($m) { + $m = unpack('C*', $m[0]); + $i = 1; + $entities = ''; + + while (isset($m[$i])) { + if (0xF0 <= $m[$i]) { + $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } elseif (0xE0 <= $m[$i]) { + $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } else { + $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; + } + + $entities .= '&#'.$c.';'; + } + + return $entities; + }, + iconv($charset, 'UTF-8', $content) + ); } + } catch (\Exception $e) { } + restore_error_handler(); + if ('' !== trim($content)) { @$dom->loadHTML($content); } diff --git a/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php b/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php index 9c57bdef4a..9906fedc7e 100644 --- a/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php +++ b/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php @@ -80,6 +80,7 @@ class CrawlerTest extends \PHPUnit_Framework_TestCase /** * @covers Symfony\Component\DomCrawler\Crawler::addHtmlContent + * @requires extension mbstring */ public function testAddHtmlContentCharset() { @@ -114,6 +115,7 @@ class CrawlerTest extends \PHPUnit_Framework_TestCase /** * @covers Symfony\Component\DomCrawler\Crawler::addHtmlContent + * @requires extension mbstring */ public function testAddHtmlContentCharsetGbk() { @@ -234,7 +236,7 @@ EOF $this->assertEquals('中文', $crawler->filterXPath('//span')->text(), '->addContent() guess wrong charset'); $crawler = new Crawler(); - $crawler->addContent(mb_convert_encoding('日本語', 'SJIS', 'UTF-8')); + $crawler->addContent(iconv('UTF-8', 'SJIS', '日本語')); $this->assertEquals('日本語', $crawler->filterXPath('//body')->text(), '->addContent() can recognize "Shift_JIS" in html5 meta charset tag'); } diff --git a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php index 40b05b88bb..80ecb417d6 100644 --- a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php +++ b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php @@ -34,16 +34,13 @@ class FilesystemTest extends \PHPUnit_Framework_TestCase public static function setUpBeforeClass() { - if ('\\' === DIRECTORY_SEPARATOR) { - self::$symlinkOnWindows = true; - $originDir = tempnam(sys_get_temp_dir(), 'sl'); - $targetDir = tempnam(sys_get_temp_dir(), 'sl'); - if (true !== @symlink($originDir, $targetDir)) { - $report = error_get_last(); - if (is_array($report) && false !== strpos($report['message'], 'error code(1314)')) { - self::$symlinkOnWindows = false; - } + if ('\\' === DIRECTORY_SEPARATOR && null === self::$symlinkOnWindows) { + $target = tempnam(sys_get_temp_dir(), 'sl'); + $link = sys_get_temp_dir().'/sl'.microtime(true).mt_rand(); + if (self::$symlinkOnWindows = @symlink($target, $link)) { + unlink($link); } + unlink($target); } } @@ -58,27 +55,10 @@ class FilesystemTest extends \PHPUnit_Framework_TestCase protected function tearDown() { - $this->clean($this->workspace); + $this->filesystem->remove($this->workspace); umask($this->umask); } - /** - * @param string $file - */ - private function clean($file) - { - if (is_dir($file) && !is_link($file)) { - $dir = new \FilesystemIterator($file); - foreach ($dir as $childFile) { - $this->clean($childFile); - } - - rmdir($file); - } else { - unlink($file); - } - } - public function testCopyCreatesNewFile() { $sourceFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_source_file'; @@ -1035,7 +1015,7 @@ class FilesystemTest extends \PHPUnit_Framework_TestCase $this->markTestSkipped('symlink is not supported'); } - if ('\\' === DIRECTORY_SEPARATOR && false === self::$symlinkOnWindows) { + if (false === self::$symlinkOnWindows) { $this->markTestSkipped('symlink requires "Create symbolic links" privilege on Windows'); } } diff --git a/src/Symfony/Component/Finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php index e1d82c559c..61242eab38 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php @@ -60,9 +60,6 @@ class RecursiveDirectoryIteratorTest extends IteratorTestCase $i->seek(1); $actual[] = $i->getPathname(); - $i->seek(2); - $actual[] = $i->getPathname(); - $this->assertEquals($contains, $actual); } @@ -73,7 +70,6 @@ class RecursiveDirectoryIteratorTest extends IteratorTestCase // ftp $contains = array( 'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'README', - 'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'index.html', 'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'pub', ); $data[] = array('ftp://ftp.mozilla.org/', false, $contains); diff --git a/src/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php index effaf76868..ed24f3ea0d 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php @@ -33,7 +33,11 @@ class SortableIteratorTest extends RealIteratorTestCase if (!is_callable($mode)) { switch ($mode) { case SortableIterator::SORT_BY_ACCESSED_TIME : - file_get_contents(self::toAbsolute('.git')); + if ('\\' === DIRECTORY_SEPARATOR) { + touch(self::toAbsolute('.git')); + } else { + file_get_contents(self::toAbsolute('.git')); + } sleep(1); file_get_contents(self::toAbsolute('.bar')); break; @@ -56,7 +60,11 @@ class SortableIteratorTest extends RealIteratorTestCase if ($mode === SortableIterator::SORT_BY_ACCESSED_TIME || $mode === SortableIterator::SORT_BY_CHANGED_TIME - || $mode === SortableIterator::SORT_BY_MODIFIED_TIME) { + || $mode === SortableIterator::SORT_BY_MODIFIED_TIME + ) { + if ('\\' === DIRECTORY_SEPARATOR && SortableIterator::SORT_BY_MODIFIED_TIME !== $mode) { + $this->markTestSkipped('Sorting by atime or ctime is not supported on Windows'); + } $this->assertOrderedIteratorForGroups($expected, $iterator); } else { $this->assertOrderedIterator($expected, $iterator); diff --git a/src/Symfony/Component/Finder/Tests/Shell/CommandTest.php b/src/Symfony/Component/Finder/Tests/Shell/CommandTest.php index f967cdaa54..e9145127ea 100644 --- a/src/Symfony/Component/Finder/Tests/Shell/CommandTest.php +++ b/src/Symfony/Component/Finder/Tests/Shell/CommandTest.php @@ -86,7 +86,7 @@ class CommandTest extends \PHPUnit_Framework_TestCase $cmd = Command::create()->add('--force'); $cmd->arg('--run'); - $this->assertSame('--force \'--run\'', $cmd->join()); + $this->assertSame('--force '.escapeshellarg('--run'), $cmd->join()); } public function testCmd() diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php index 3e837ccef0..4d5935c048 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -260,7 +260,7 @@ class DateType extends AbstractType $pattern = $formatter->getPattern(); $timezone = $formatter->getTimezoneId(); - if ($setTimeZone = method_exists($formatter, 'setTimeZone')) { + if ($setTimeZone = PHP_VERSION_ID >= 50500 || method_exists($formatter, 'setTimeZone')) { $formatter->setTimeZone('UTC'); } else { $formatter->setTimeZoneId('UTC'); diff --git a/src/Symfony/Component/HttpFoundation/JsonResponse.php b/src/Symfony/Component/HttpFoundation/JsonResponse.php index 8267b0c4f8..13611425d4 100644 --- a/src/Symfony/Component/HttpFoundation/JsonResponse.php +++ b/src/Symfony/Component/HttpFoundation/JsonResponse.php @@ -27,6 +27,10 @@ class JsonResponse extends Response protected $data; protected $callback; + // Encode <, >, ', &, and " for RFC4627-compliant JSON, which may also be embedded into HTML. + // 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT + private $encodingOptions = 15; + /** * Constructor. * @@ -41,6 +45,7 @@ class JsonResponse extends Response if (null === $data) { $data = new \ArrayObject(); } + $this->setData($data); } @@ -55,11 +60,11 @@ class JsonResponse extends Response /** * Sets the JSONP callback. * - * @param string $callback + * @param string|null $callback The JSONP callback or null to use none * * @return JsonResponse * - * @throws \InvalidArgumentException + * @throws \InvalidArgumentException When the callback name is not valid */ public function setCallback($callback = null) { @@ -80,7 +85,7 @@ class JsonResponse extends Response } /** - * Sets the data to be sent as json. + * Sets the data to be sent as JSON. * * @param mixed $data * @@ -90,18 +95,63 @@ class JsonResponse extends Response */ public function setData($data = array()) { - // Encode <, >, ', &, and " for RFC4627-compliant JSON, which may also be embedded into HTML. - $this->data = json_encode($data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT); + if (defined('HHVM_VERSION')) { + // HHVM does not trigger any warnings and let exceptions + // thrown from a JsonSerializable object pass through. + // If only PHP did the same... + $data = json_encode($data, $this->encodingOptions); + } else { + try { + if (PHP_VERSION_ID < 50400) { + // PHP 5.3 triggers annoying warnings for some + // types that can't be serialized as JSON (INF, resources, etc.) + // but doesn't provide the JsonSerializable interface. + set_error_handler('var_dump', 0); + $data = @json_encode($data, $this->encodingOptions); + } else { + // PHP 5.4 and up wrap exceptions thrown by JsonSerializable + // objects in a new exception that needs to be removed. + // Fortunately, PHP 5.5 and up do not trigger any warning anymore. + if (PHP_VERSION_ID < 50500) { + // Clear json_last_error() + json_encode(null); + $errorHandler = set_error_handler('var_dump'); + restore_error_handler(); + set_error_handler(function () use ($errorHandler) { + if (JSON_ERROR_NONE === json_last_error()) { + return $errorHandler && false !== call_user_func_array($errorHandler, func_get_args()); + } + }); + } + + $data = json_encode($data, $this->encodingOptions); + } + + if (PHP_VERSION_ID < 50500) { + restore_error_handler(); + } + } catch (\Exception $e) { + if (PHP_VERSION_ID < 50500) { + restore_error_handler(); + } + if (PHP_VERSION_ID >= 50400 && 'Exception' === get_class($e) && 0 === strpos($e->getMessage(), 'Failed calling ')) { + throw $e->getPrevious() ?: $e; + } + throw $e; + } + } if (JSON_ERROR_NONE !== json_last_error()) { throw new \InvalidArgumentException($this->transformJsonError()); } + $this->data = $data; + return $this->update(); } /** - * Updates the content and headers according to the json data and callback. + * Updates the content and headers according to the JSON data and callback. * * @return JsonResponse */ diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index f591ca90be..4119c3552f 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -1740,9 +1740,9 @@ class Request return $prefix; } - if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(dirname($baseUrl), '/').'/')) { + if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(dirname($baseUrl), '/'.DIRECTORY_SEPARATOR).'/')) { // directory portion of $baseUrl matches - return rtrim($prefix, '/'); + return rtrim($prefix, '/'.DIRECTORY_SEPARATOR); } $truncatedRequestUri = $requestUri; @@ -1763,7 +1763,7 @@ class Request $baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl)); } - return rtrim($baseUrl, '/'); + return rtrim($baseUrl, '/'.DIRECTORY_SEPARATOR); } /** diff --git a/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php index b239ef30a4..e4f05fadcf 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php @@ -54,7 +54,7 @@ class BinaryFileResponseTest extends ResponseTestCase */ public function testRequests($requestRange, $offset, $length, $responseRange) { - $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif')->setAutoEtag(); + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'))->setAutoEtag(); // do a request to get the ETag $request = Request::create('/'); @@ -96,7 +96,7 @@ class BinaryFileResponseTest extends ResponseTestCase */ public function testFullFileRequests($requestRange) { - $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif')->setAutoEtag(); + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'))->setAutoEtag(); // prepare a request for a range of the testing file $request = Request::create('/'); @@ -131,7 +131,7 @@ class BinaryFileResponseTest extends ResponseTestCase */ public function testInvalidRequests($requestRange) { - $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif')->setAutoEtag(); + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'))->setAutoEtag(); // prepare a request for a range of the testing file $request = Request::create('/'); @@ -159,7 +159,7 @@ class BinaryFileResponseTest extends ResponseTestCase $request->headers->set('X-Sendfile-Type', 'X-Sendfile'); BinaryFileResponse::trustXSendfileTypeHeader(); - $response = BinaryFileResponse::create(__DIR__.'/../README.md'); + $response = BinaryFileResponse::create(__DIR__.'/../README.md', 200, array('Content-Type' => 'application/octet-stream')); $response->prepare($request); $this->expectOutputString(''); @@ -180,7 +180,7 @@ class BinaryFileResponseTest extends ResponseTestCase $file = new FakeFile($realpath, __DIR__.'/File/Fixtures/test'); BinaryFileResponse::trustXSendfileTypeHeader(); - $response = new BinaryFileResponse($file); + $response = new BinaryFileResponse($file, 200, array('Content-Type' => 'application/octet-stream')); $reflection = new \ReflectionObject($response); $property = $reflection->getProperty('file'); $property->setAccessible(true); @@ -203,7 +203,7 @@ class BinaryFileResponseTest extends ResponseTestCase public function testAcceptRangeOnUnsafeMethods() { $request = Request::create('/', 'POST'); - $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif'); + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream')); $response->prepare($request); $this->assertEquals('none', $response->headers->get('Accept-Ranges')); @@ -212,7 +212,7 @@ class BinaryFileResponseTest extends ResponseTestCase public function testAcceptRangeNotOverriden() { $request = Request::create('/', 'POST'); - $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif'); + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream')); $response->headers->set('Accept-Ranges', 'foo'); $response->prepare($request); @@ -229,6 +229,6 @@ class BinaryFileResponseTest extends ResponseTestCase protected function provideResponse() { - return new BinaryFileResponse(__DIR__.'/../README.md'); + return new BinaryFileResponse(__DIR__.'/../README.md', 200, array('Content-Type' => 'application/octet-stream')); } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/File/FileTest.php b/src/Symfony/Component/HttpFoundation/Tests/File/FileTest.php index 08b7cccb52..90a143367a 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/File/FileTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/File/FileTest.php @@ -45,6 +45,9 @@ class FileTest extends \PHPUnit_Framework_TestCase $this->assertEquals('gif', $file->guessExtension()); } + /** + * @requires extension fileinfo + */ public function testGuessExtensionWithReset() { $file = new File(__DIR__.'/Fixtures/other-file.example'); diff --git a/src/Symfony/Component/HttpFoundation/Tests/File/MimeType/MimeTypeTest.php b/src/Symfony/Component/HttpFoundation/Tests/File/MimeType/MimeTypeTest.php index b2a573e27a..1d5648eada 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/File/MimeType/MimeTypeTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/File/MimeType/MimeTypeTest.php @@ -14,17 +14,16 @@ namespace Symfony\Component\HttpFoundation\Tests\File\MimeType; use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser; use Symfony\Component\HttpFoundation\File\MimeType\FileBinaryMimeTypeGuesser; +/** + * @requires extension fileinfo + */ class MimeTypeTest extends \PHPUnit_Framework_TestCase { protected $path; public function testGuessImageWithoutExtension() { - if (extension_loaded('fileinfo')) { - $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test')); - } else { - $this->assertNull(MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test')); - } + $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test')); } public function testGuessImageWithDirectory() @@ -38,29 +37,17 @@ class MimeTypeTest extends \PHPUnit_Framework_TestCase { $guesser = MimeTypeGuesser::getInstance(); $guesser->register(new FileBinaryMimeTypeGuesser()); - if (extension_loaded('fileinfo')) { - $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test')); - } else { - $this->assertNull(MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test')); - } + $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test')); } public function testGuessImageWithKnownExtension() { - if (extension_loaded('fileinfo')) { - $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test.gif')); - } else { - $this->assertNull(MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test.gif')); - } + $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test.gif')); } public function testGuessFileWithUnknownExtension() { - if (extension_loaded('fileinfo')) { - $this->assertEquals('application/octet-stream', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/.unknownextension')); - } else { - $this->assertNull(MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/.unknownextension')); - } + $this->assertEquals('application/octet-stream', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/.unknownextension')); } public function testGuessWithIncorrectPath() @@ -75,7 +62,7 @@ class MimeTypeTest extends \PHPUnit_Framework_TestCase $this->markTestSkipped('Can not verify chmod operations on Windows'); } - if ('root' === get_current_user()) { + if (!getenv('USER') || 'root' === getenv('USER')) { $this->markTestSkipped('This test will fail if run under superuser'); } @@ -83,7 +70,7 @@ class MimeTypeTest extends \PHPUnit_Framework_TestCase touch($path); @chmod($path, 0333); - if (get_current_user() != 'root' && substr(sprintf('%o', fileperms($path)), -4) == '0333') { + if (substr(sprintf('%o', fileperms($path)), -4) == '0333') { $this->setExpectedException('Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException'); MimeTypeGuesser::getInstance()->guess($path); } else { diff --git a/src/Symfony/Component/Intl/DateFormatter/IntlDateFormatter.php b/src/Symfony/Component/Intl/DateFormatter/IntlDateFormatter.php index 3e0ac5de17..7de6db2d13 100644 --- a/src/Symfony/Component/Intl/DateFormatter/IntlDateFormatter.php +++ b/src/Symfony/Component/Intl/DateFormatter/IntlDateFormatter.php @@ -209,12 +209,14 @@ class IntlDateFormatter // behave like the intl extension $argumentError = null; - if (PHP_VERSION_ID < 50304 && !is_int($timestamp)) { - $argumentError = 'datefmt_format: takes either an array or an integer timestamp value '; - } elseif (PHP_VERSION_ID >= 50304 && !is_int($timestamp) && !$timestamp instanceof \DateTime) { - $argumentError = 'datefmt_format: takes either an array or an integer timestamp value or a DateTime object'; - if (PHP_VERSION_ID >= 50500 && !is_int($timestamp)) { - $argumentError = sprintf('datefmt_format: string \'%s\' is not numeric, which would be required for it to be a valid date', $timestamp); + if (!is_int($timestamp)) { + if (PHP_VERSION_ID < 50304) { + $argumentError = 'datefmt_format: takes either an array or an integer timestamp value '; + } elseif (!$timestamp instanceof \DateTime) { + $argumentError = 'datefmt_format: takes either an array or an integer timestamp value or a DateTime object'; + if (PHP_VERSION_ID >= 50500 || (extension_loaded('intl') && method_exists('IntlDateFormatter', 'setTimeZone'))) { + $argumentError = sprintf('datefmt_format: string \'%s\' is not numeric, which would be required for it to be a valid date', $timestamp); + } } } @@ -376,7 +378,7 @@ class IntlDateFormatter } // In PHP 5.5 default timezone depends on `date_default_timezone_get()` method - if (PHP_VERSION_ID >= 50500) { + if (PHP_VERSION_ID >= 50500 || (extension_loaded('intl') && method_exists('IntlDateFormatter', 'setTimeZone'))) { return date_default_timezone_get(); } } @@ -541,7 +543,7 @@ class IntlDateFormatter { if (null === $timeZoneId) { // In PHP 5.5 if $timeZoneId is null it fallbacks to `date_default_timezone_get()` method - if (PHP_VERSION_ID >= 50500) { + if (PHP_VERSION_ID >= 50500 || (extension_loaded('intl') && method_exists('IntlDateFormatter', 'setTimeZone'))) { $timeZoneId = date_default_timezone_get(); } else { // TODO: changes were made to ext/intl in PHP 5.4.4 release that need to be investigated since it will @@ -568,8 +570,16 @@ class IntlDateFormatter try { $this->dateTimeZone = new \DateTimeZone($timeZoneId); + if ('GMT' !== $timeZoneId && $this->dateTimeZone->getName() !== $timeZoneId) { + $timeZoneId = $timeZone = $this->getTimeZoneId(); + } } catch (\Exception $e) { - $this->dateTimeZone = new \DateTimeZone('UTC'); + if (PHP_VERSION_ID >= 50500 || (extension_loaded('intl') && method_exists('IntlDateFormatter', 'setTimeZone'))) { + $timeZoneId = $timeZone = $this->getTimeZoneId(); + } else { + $timeZoneId = 'UTC'; + } + $this->dateTimeZone = new \DateTimeZone($timeZoneId); } $this->timeZoneId = $timeZone; @@ -635,7 +645,7 @@ class IntlDateFormatter if (self::NONE !== $this->timetype) { $patternParts[] = $this->defaultTimeFormats[$this->timetype]; } - $pattern = implode(' ', $patternParts); + $pattern = implode(', ', $patternParts); return $pattern; } diff --git a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php index d906cdddd1..542cce1a3c 100644 --- a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php +++ b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php @@ -520,22 +520,30 @@ class NumberFormatter return false; } - preg_match('/^([^0-9\-\.]{0,})(.*)/', $value, $matches); + $groupSep = $this->getAttribute(self::GROUPING_USED) ? ',' : ''; // Any string before the numeric value causes error in the parsing - if (isset($matches[1]) && !empty($matches[1])) { + if (preg_match("/^-?(?:\.\d++|([\d{$groupSep}]++)(?:\.\d++)?)/", $value, $matches)) { + $value = $matches[0]; + $position = strlen($value); + if ($error = $groupSep && isset($matches[1]) && !preg_match('/^\d{1,3}+(?:(?:,\d{3})++|\d*+)$/', $matches[1])) { + $position -= strlen(preg_replace('/^\d{1,3}+(?:(?:,\d++)++|\d*+)/', '', $matches[1])); + } + } else { + $error = 1; + $position = 0; + } + + if ($error) { IntlGlobals::setError(IntlGlobals::U_PARSE_ERROR, 'Number parsing failed'); $this->errorCode = IntlGlobals::getErrorCode(); $this->errorMessage = IntlGlobals::getErrorMessage(); - $position = 0; return false; } - preg_match('/^[0-9\-\.\,]*/', $value, $matches); - $value = preg_replace('/[^0-9\.\-]/', '', $matches[0]); + $value = str_replace(',', '', $value); $value = $this->convertValueDataType($value, $type); - $position = strlen($matches[0]); // behave like the intl extension $this->resetError(); diff --git a/src/Symfony/Component/Intl/Tests/DateFormatter/AbstractIntlDateFormatterTest.php b/src/Symfony/Component/Intl/Tests/DateFormatter/AbstractIntlDateFormatterTest.php index c17dca13fd..259f5e5d9d 100644 --- a/src/Symfony/Component/Intl/Tests/DateFormatter/AbstractIntlDateFormatterTest.php +++ b/src/Symfony/Component/Intl/Tests/DateFormatter/AbstractIntlDateFormatterTest.php @@ -38,11 +38,16 @@ abstract class AbstractIntlDateFormatterTest extends \PHPUnit_Framework_TestCase $formatter = $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT); // In PHP 5.5 default timezone depends on `date_default_timezone_get()` method - if (PHP_VERSION_ID >= 50500) { + if (PHP_VERSION_ID >= 50500 || (extension_loaded('intl') && method_exists('IntlDateFormatter', 'setTimeZone'))) { $this->assertEquals(date_default_timezone_get(), $formatter->getTimeZoneId()); } else { $this->assertNull($formatter->getTimeZoneId()); } + + $this->assertEquals( + $this->getDateTime(0, $formatter->getTimeZoneId())->format('M j, Y, g:i A'), + $formatter->format(0) + ); } /** @@ -270,7 +275,7 @@ abstract class AbstractIntlDateFormatterTest extends \PHPUnit_Framework_TestCase public function formatErrorProvider() { // With PHP 5.5 IntlDateFormatter accepts empty values ('0') - if (PHP_VERSION_ID >= 50500) { + if (PHP_VERSION_ID >= 50500 || (extension_loaded('intl') && method_exists('IntlDateFormatter', 'setTimeZone'))) { return array( array('y-M-d', 'foobar', 'datefmt_format: string \'foobar\' is not numeric, which would be required for it to be a valid date: U_ILLEGAL_ARGUMENT_ERROR'), ); @@ -327,7 +332,7 @@ abstract class AbstractIntlDateFormatterTest extends \PHPUnit_Framework_TestCase ); // As of PHP 5.5, intl ext no longer fallbacks invalid time zones to UTC - if (PHP_VERSION_ID < 50500) { + if (PHP_VERSION_ID < 50500 && !(extension_loaded('intl') && method_exists('IntlDateFormatter', 'setTimeZone'))) { // When time zone not exists, uses UTC by default $data[] = array(0, 'Foo/Bar', '1970-01-01 00:00:00'); $data[] = array(0, 'UTC+04:30', '1970-01-01 00:00:00'); @@ -341,7 +346,7 @@ abstract class AbstractIntlDateFormatterTest extends \PHPUnit_Framework_TestCase { $formatter = $this->getDefaultDateFormatter('zzzz'); - if (PHP_VERSION_ID >= 50500) { + if (PHP_VERSION_ID >= 50500 || (extension_loaded('intl') && method_exists('IntlDateFormatter', 'setTimeZone'))) { $formatter->setTimeZone('GMT+03:00'); } else { $formatter->setTimeZoneId('GMT+03:00'); @@ -354,7 +359,7 @@ abstract class AbstractIntlDateFormatterTest extends \PHPUnit_Framework_TestCase { $formatter = $this->getDefaultDateFormatter('zzzz'); - if (PHP_VERSION_ID >= 50500) { + if (PHP_VERSION_ID >= 50500 || (extension_loaded('intl') && method_exists('IntlDateFormatter', 'setTimeZone'))) { $formatter->setTimeZone('GMT+00:30'); } else { $formatter->setTimeZoneId('GMT+00:30'); @@ -367,7 +372,7 @@ abstract class AbstractIntlDateFormatterTest extends \PHPUnit_Framework_TestCase { $formatter = $this->getDefaultDateFormatter('zzzz'); - if (PHP_VERSION_ID >= 50500) { + if (PHP_VERSION_ID >= 50500 || (extension_loaded('intl') && method_exists('IntlDateFormatter', 'setTimeZone'))) { $formatter->setTimeZone('Pacific/Fiji'); } else { $formatter->setTimeZoneId('Pacific/Fiji'); @@ -404,7 +409,7 @@ abstract class AbstractIntlDateFormatterTest extends \PHPUnit_Framework_TestCase public function testFormatWithIntlTimeZone() { - if (PHP_VERSION_ID < 50500) { + if (PHP_VERSION_ID < 50500 && !(extension_loaded('intl') && method_exists('IntlDateFormatter', 'setTimeZone'))) { $this->markTestSkipped('Only in PHP 5.5+ IntlDateFormatter allows to use DateTimeZone objects.'); } @@ -419,7 +424,7 @@ abstract class AbstractIntlDateFormatterTest extends \PHPUnit_Framework_TestCase public function testFormatWithTimezoneFromEnvironmentVariable() { - if (PHP_VERSION_ID >= 50500) { + if (PHP_VERSION_ID >= 50500 || (extension_loaded('intl') && method_exists('IntlDateFormatter', 'setTimeZone'))) { $this->markTestSkipped('IntlDateFormatter in PHP 5.5 no longer depends on TZ environment.'); } @@ -442,7 +447,7 @@ abstract class AbstractIntlDateFormatterTest extends \PHPUnit_Framework_TestCase public function testFormatWithTimezoneFromPhp() { - if (PHP_VERSION_ID < 50500) { + if (PHP_VERSION_ID < 50500 && !(extension_loaded('intl') && method_exists('IntlDateFormatter', 'setTimeZone'))) { $this->markTestSkipped('Only in PHP 5.5 IntlDateFormatter depends on default timezone (`date_default_timezone_get()`).'); } @@ -873,7 +878,7 @@ abstract class AbstractIntlDateFormatterTest extends \PHPUnit_Framework_TestCase { $formatter = $this->getDefaultDateFormatter(); - if (PHP_VERSION_ID >= 50500) { + if (PHP_VERSION_ID >= 50500 || (extension_loaded('intl') && method_exists('IntlDateFormatter', 'setTimeZone'))) { $formatter->setTimeZone($timeZoneId); } else { $formatter->setTimeZoneId($timeZoneId); @@ -884,15 +889,17 @@ abstract class AbstractIntlDateFormatterTest extends \PHPUnit_Framework_TestCase public function setTimeZoneIdProvider() { + $isPhp55 = PHP_VERSION_ID >= 50500 || (extension_loaded('intl') && method_exists('IntlDateFormatter', 'setTimeZone')); + return array( array('UTC', 'UTC'), array('GMT', 'GMT'), array('GMT-03:00', 'GMT-03:00'), array('Europe/Zurich', 'Europe/Zurich'), - array('GMT-0300', 'GMT-0300'), - array('Foo/Bar', 'Foo/Bar'), - array('GMT+00:AA', 'GMT+00:AA'), - array('GMT+00AA', 'GMT+00AA'), + array(null, $isPhp55 ? date_default_timezone_get() : null), + array('Foo/Bar', $isPhp55 ? 'UTC' : 'Foo/Bar'), + array('GMT+00:AA', $isPhp55 ? 'UTC' : 'GMT+00:AA'), + array('GMT+00AA', $isPhp55 ? 'UTC' : 'GMT+00AA'), ); } @@ -905,7 +912,9 @@ abstract class AbstractIntlDateFormatterTest extends \PHPUnit_Framework_TestCase { $dateTime = new \DateTime(); $dateTime->setTimestamp(null === $timestamp ? time() : $timestamp); - $dateTime->setTimezone(new \DateTimeZone($timeZone)); + if (null !== $timeZone) { + $dateTime->setTimezone(new \DateTimeZone($timeZone)); + } return $dateTime; } diff --git a/src/Symfony/Component/Intl/Tests/DateFormatter/Verification/IntlDateFormatterTest.php b/src/Symfony/Component/Intl/Tests/DateFormatter/Verification/IntlDateFormatterTest.php index bf4cdae26a..dcf38473f2 100644 --- a/src/Symfony/Component/Intl/Tests/DateFormatter/Verification/IntlDateFormatterTest.php +++ b/src/Symfony/Component/Intl/Tests/DateFormatter/Verification/IntlDateFormatterTest.php @@ -45,7 +45,11 @@ class IntlDateFormatterTest extends AbstractIntlDateFormatterTest protected function getDateFormatter($locale, $datetype, $timetype, $timezone = null, $calendar = IntlDateFormatter::GREGORIAN, $pattern = null) { - return new \IntlDateFormatter($locale, $datetype, $timetype, $timezone, $calendar, $pattern); + if (!$formatter = new \IntlDateFormatter($locale, $datetype, $timetype, $timezone, $calendar, $pattern)) { + throw new \InvalidArgumentException(intl_get_error_message()); + } + + return $formatter; } protected function getIntlErrorMessage() diff --git a/src/Symfony/Component/Intl/Tests/NumberFormatter/AbstractNumberFormatterTest.php b/src/Symfony/Component/Intl/Tests/NumberFormatter/AbstractNumberFormatterTest.php index 5585255ebf..569ad776cf 100644 --- a/src/Symfony/Component/Intl/Tests/NumberFormatter/AbstractNumberFormatterTest.php +++ b/src/Symfony/Component/Intl/Tests/NumberFormatter/AbstractNumberFormatterTest.php @@ -506,10 +506,11 @@ abstract class AbstractNumberFormatterTest extends \PHPUnit_Framework_TestCase /** * @dataProvider parseProvider */ - public function testParse($value, $expected, $message, $expectedPosition) + public function testParse($value, $expected, $message, $expectedPosition, $groupingUsed = true) { $position = 0; $formatter = $this->getNumberFormatter('en', NumberFormatter::DECIMAL); + $formatter->setAttribute(NumberFormatter::GROUPING_USED, $groupingUsed); $parsedValue = $formatter->parse($value, NumberFormatter::TYPE_DOUBLE, $position); $this->assertSame($expected, $parsedValue, $message); $this->assertSame($expectedPosition, $position, $message); @@ -535,6 +536,11 @@ abstract class AbstractNumberFormatterTest extends \PHPUnit_Framework_TestCase return array( array('prefix1', false, '->parse() does not parse a number with a string prefix.', 0), array('1.4suffix', (float) 1.4, '->parse() parses a number with a string suffix.', 3), + array('-.4suffix', (float) -0.4, '->parse() parses a negative dot float with suffix.', 3), + array('-123,4', false, '->parse() does not parse when invalid grouping used.', 6), + array('-1234,567', false, '->parse() does not parse when invalid grouping used.', 5), + array('-123,,456', false, '->parse() does not parse when invalid grouping used.', 4), + array('-123,,456', -123.0, '->parse() parses when grouping is disabled.', 4, false), ); } diff --git a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php index 597406366a..f28d5d8e84 100644 --- a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php +++ b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php @@ -543,8 +543,10 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase { $process = $this->getProcess(self::$phpBin.' -r "sleep(1);"'); $process->start(); + + $this->assertFalse($process->isSuccessful()); + while ($process->isRunning()) { - $this->assertFalse($process->isSuccessful()); usleep(300000); } diff --git a/src/Symfony/Component/Validator/Constraints/LengthValidator.php b/src/Symfony/Component/Validator/Constraints/LengthValidator.php index a184883b92..33fa9ceb2e 100644 --- a/src/Symfony/Component/Validator/Constraints/LengthValidator.php +++ b/src/Symfony/Component/Validator/Constraints/LengthValidator.php @@ -40,23 +40,25 @@ class LengthValidator extends ConstraintValidator $charset = 'UTF-8'; } - if (function_exists('iconv_strlen')) { - $length = @iconv_strlen($stringValue, $constraint->charset); - $invalidCharset = false === $length; + if ('UTF-8' === $charset) { + if (!preg_match('//u', $stringValue)) { + $invalidCharset = true; + } elseif (function_exists('utf8_decode')) { + $length = strlen(utf8_decode($stringValue)); + } else { + preg_replace('/./u', '', $stringValue, -1, $length); + } } elseif (function_exists('mb_strlen')) { - if (mb_check_encoding($stringValue, $constraint->charset)) { + if (@mb_check_encoding($stringValue, $constraint->charset)) { $length = mb_strlen($stringValue, $constraint->charset); } else { $invalidCharset = true; } - } elseif ('UTF-8' !== $charset) { - $length = strlen($stringValue); - } elseif (!preg_match('//u', $stringValue)) { - $invalidCharset = true; - } elseif (function_exists('utf8_decode')) { - $length = strlen(utf8_decode($stringValue)); + } elseif (function_exists('iconv_strlen')) { + $length = @iconv_strlen($stringValue, $constraint->charset); + $invalidCharset = false === $length; } else { - preg_replace('/./u', '', $stringValue, -1, $length); + $length = strlen($stringValue); } if ($invalidCharset) { diff --git a/src/Symfony/Component/Validator/Tests/Constraints/ImageValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/ImageValidatorTest.php index bb7954ed72..7c447da589 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/ImageValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/ImageValidatorTest.php @@ -14,6 +14,9 @@ namespace Symfony\Component\Validator\Tests\Constraints; use Symfony\Component\Validator\Constraints\Image; use Symfony\Component\Validator\Constraints\ImageValidator; +/** + * @requires extension fileinfo + */ class ImageValidatorTest extends AbstractConstraintValidatorTest { protected $context;