Adding zendframework/zend-config + updating vendors see BT#8703

1.9.x
Julio Montoya 11 years ago
parent e823a39a5c
commit fd4f13f5e0
  1. 3
      composer.json
  2. 172
      composer.lock
  3. 2
      vendor/autoload.php
  4. 5
      vendor/composer/ClassLoader.php
  5. 2
      vendor/composer/autoload_namespaces.php
  6. 8
      vendor/composer/autoload_real.php
  7. 418
      vendor/composer/installed.json
  8. 17
      vendor/sabre/vobject/ChangeLog.md
  9. 10
      vendor/sabre/vobject/lib/Component.php
  10. 23
      vendor/sabre/vobject/lib/Component/VCalendar.php
  11. 16
      vendor/sabre/vobject/lib/Component/VTimeZone.php
  12. 43
      vendor/sabre/vobject/lib/DateTimeParser.php
  13. 41
      vendor/sabre/vobject/lib/FreeBusyGenerator.php
  14. 4
      vendor/sabre/vobject/lib/ITip/Broker.php
  15. 36
      vendor/sabre/vobject/lib/Property/ICalendar/DateTime.php
  16. 52
      vendor/sabre/vobject/lib/Recur/EventIterator.php
  17. 6
      vendor/sabre/vobject/lib/Recur/RRuleIterator.php
  18. 9
      vendor/sabre/vobject/lib/StringUtil.php
  19. 137
      vendor/sabre/vobject/lib/VCardConverter.php
  20. 2
      vendor/sabre/vobject/lib/Version.php
  21. 56
      vendor/sabre/vobject/tests/VObject/Component/VCalendarTest.php
  22. 25
      vendor/sabre/vobject/tests/VObject/Component/VTimeZoneTest.php
  23. 245
      vendor/sabre/vobject/tests/VObject/FreeBusyGeneratorTest.php
  24. 145
      vendor/sabre/vobject/tests/VObject/ITip/BrokerAttendeeReplyTest.php
  25. 21
      vendor/sabre/vobject/tests/VObject/Parser/MimeDirTest.php
  26. 39
      vendor/sabre/vobject/tests/VObject/Property/ICalendar/DateTimeTest.php
  27. 1
      vendor/sabre/vobject/tests/VObject/Recur/EventIterator/IncorrectExpandTest.php
  28. 1
      vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MissingOverriddenTest.php
  29. 50
      vendor/sabre/vobject/tests/VObject/TestCase.php
  30. 199
      vendor/sabre/vobject/tests/VObject/VCardConverterTest.php
  31. 53
      vendor/symfony/console/Symfony/Component/Console/Application.php
  32. 6
      vendor/symfony/console/Symfony/Component/Console/CHANGELOG.md
  33. 30
      vendor/symfony/console/Symfony/Component/Console/Command/Command.php
  34. 2
      vendor/symfony/console/Symfony/Component/Console/Command/HelpCommand.php
  35. 4
      vendor/symfony/console/Symfony/Component/Console/Command/ListCommand.php
  36. 6
      vendor/symfony/console/Symfony/Component/Console/ConsoleEvents.php
  37. 28
      vendor/symfony/console/Symfony/Component/Console/Descriptor/JsonDescriptor.php
  38. 2
      vendor/symfony/console/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php
  39. 34
      vendor/symfony/console/Symfony/Component/Console/Descriptor/TextDescriptor.php
  40. 43
      vendor/symfony/console/Symfony/Component/Console/Event/ConsoleCommandEvent.php
  41. 2
      vendor/symfony/console/Symfony/Component/Console/Event/ConsoleExceptionEvent.php
  42. 4
      vendor/symfony/console/Symfony/Component/Console/Event/ConsoleTerminateEvent.php
  43. 8
      vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatter.php
  44. 4
      vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterInterface.php
  45. 42
      vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyle.php
  46. 2
      vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleStack.php
  47. 127
      vendor/symfony/console/Symfony/Component/Console/Helper/DebugFormatterHelper.php
  48. 4
      vendor/symfony/console/Symfony/Component/Console/Helper/DescriptorHelper.php
  49. 22
      vendor/symfony/console/Symfony/Component/Console/Helper/DialogHelper.php
  50. 2
      vendor/symfony/console/Symfony/Component/Console/Helper/Helper.php
  51. 2
      vendor/symfony/console/Symfony/Component/Console/Helper/HelperSet.php
  52. 142
      vendor/symfony/console/Symfony/Component/Console/Helper/ProcessHelper.php
  53. 194
      vendor/symfony/console/Symfony/Component/Console/Helper/ProgressBar.php
  54. 50
      vendor/symfony/console/Symfony/Component/Console/Helper/ProgressHelper.php
  55. 2
      vendor/symfony/console/Symfony/Component/Console/Helper/TableHelper.php
  56. 2
      vendor/symfony/console/Symfony/Component/Console/Input/ArgvInput.php
  57. 2
      vendor/symfony/console/Symfony/Component/Console/Input/ArrayInput.php
  58. 14
      vendor/symfony/console/Symfony/Component/Console/Input/Input.php
  59. 16
      vendor/symfony/console/Symfony/Component/Console/Input/InputArgument.php
  60. 27
      vendor/symfony/console/Symfony/Component/Console/Input/InputDefinition.php
  61. 14
      vendor/symfony/console/Symfony/Component/Console/Input/InputInterface.php
  62. 19
      vendor/symfony/console/Symfony/Component/Console/Input/InputOption.php
  63. 4
      vendor/symfony/console/Symfony/Component/Console/Output/Output.php
  64. 22
      vendor/symfony/console/Symfony/Component/Console/Output/OutputInterface.php
  65. 2
      vendor/symfony/console/Symfony/Component/Console/Output/StreamOutput.php
  66. 2
      vendor/symfony/console/Symfony/Component/Console/Shell.php
  67. 4
      vendor/symfony/console/Symfony/Component/Console/Tester/ApplicationTester.php
  68. 4
      vendor/symfony/console/Symfony/Component/Console/Tester/CommandTester.php
  69. 55
      vendor/symfony/console/Symfony/Component/Console/Tests/ApplicationTest.php
  70. 6
      vendor/symfony/console/Symfony/Component/Console/Tests/Command/CommandTest.php
  71. 20
      vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_1.txt
  72. 28
      vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_2.txt
  73. 24
      vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_astext1.txt
  74. 18
      vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_astext2.txt
  75. 14
      vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_gethelp.txt
  76. 20
      vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_run1.txt
  77. 2
      vendor/symfony/console/Symfony/Component/Console/Tests/Helper/HelperSetTest.php
  78. 118
      vendor/symfony/console/Symfony/Component/Console/Tests/Helper/ProcessHelperTest.php
  79. 180
      vendor/symfony/console/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php
  80. 4
      vendor/symfony/console/Symfony/Component/Console/composer.json
  81. 7
      vendor/symfony/filesystem/Symfony/Component/Filesystem/CHANGELOG.md
  82. 71
      vendor/symfony/filesystem/Symfony/Component/Filesystem/Filesystem.php
  83. 111
      vendor/symfony/filesystem/Symfony/Component/Filesystem/LockHandler.php
  84. 2
      vendor/symfony/filesystem/Symfony/Component/Filesystem/README.md
  85. 63
      vendor/symfony/filesystem/Symfony/Component/Filesystem/Tests/FilesystemTest.php
  86. 85
      vendor/symfony/filesystem/Symfony/Component/Filesystem/Tests/LockHandlerTest.php
  87. 2
      vendor/symfony/filesystem/Symfony/Component/Filesystem/composer.json
  88. 10
      vendor/symfony/process/Symfony/Component/Process/PhpProcess.php
  89. 74
      vendor/symfony/process/Symfony/Component/Process/Pipes/AbstractPipes.php
  90. 60
      vendor/symfony/process/Symfony/Component/Process/Pipes/PipesInterface.php
  91. 214
      vendor/symfony/process/Symfony/Component/Process/Pipes/UnixPipes.php
  92. 254
      vendor/symfony/process/Symfony/Component/Process/Pipes/WindowsPipes.php
  93. 87
      vendor/symfony/process/Symfony/Component/Process/Process.php
  94. 382
      vendor/symfony/process/Symfony/Component/Process/ProcessPipes.php
  95. 5
      vendor/symfony/process/Symfony/Component/Process/ProcessUtils.php
  96. 45
      vendor/symfony/process/Symfony/Component/Process/Tests/AbstractProcessTest.php
  97. 2
      vendor/symfony/process/Symfony/Component/Process/Tests/ProcessBuilderTest.php
  98. 2
      vendor/symfony/process/Symfony/Component/Process/composer.json
  99. 14
      vendor/symfony/yaml/Symfony/Component/Yaml/Dumper.php
  100. 6
      vendor/symfony/yaml/Symfony/Component/Yaml/Escaper.php
  101. Some files were not shown because too many files have changed in this diff Show More

@ -5,6 +5,7 @@
"toin0u/digitalocean": "~1.4",
"twig/twig": "1.*",
"michelf/php-markdown": "1.4.1",
"emojione/emojione": "1.3.0"
"emojione/emojione": "1.3.0",
"zendframework/zend-config": "2.3.3"
}
}

172
composer.lock generated

@ -4,7 +4,7 @@
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "3de2d83995130ea086a473db7a35a0a9",
"hash": "1abe77cf0b8752805b4ac0e3cde352b0",
"packages": [
{
"name": "alchemy/binary-driver",
@ -482,16 +482,16 @@
},
{
"name": "sabre/vobject",
"version": "3.3.3",
"version": "3.3.4",
"source": {
"type": "git",
"url": "https://github.com/fruux/sabre-vobject.git",
"reference": "de508f160e811c09b5e651909eb20b9e058a043c"
"reference": "e7cbc59a7a77325dfa32924865e1802c9216a3e0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fruux/sabre-vobject/zipball/de508f160e811c09b5e651909eb20b9e058a043c",
"reference": "de508f160e811c09b5e651909eb20b9e058a043c",
"url": "https://api.github.com/repos/fruux/sabre-vobject/zipball/e7cbc59a7a77325dfa32924865e1802c9216a3e0",
"reference": "e7cbc59a7a77325dfa32924865e1802c9216a3e0",
"shasum": ""
},
"require": {
@ -544,21 +544,21 @@
"jCard",
"vCard"
],
"time": "2014-10-09 15:59:25"
"time": "2014-11-19 22:15:24"
},
{
"name": "symfony/console",
"version": "v2.5.6",
"version": "v2.6.1",
"target-dir": "Symfony/Component/Console",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
"reference": "6f177fca24200a5b97aef5ce7a5c98124a0f0db0"
"reference": "ef825fd9f809d275926547c9e57cbf14968793e8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Console/zipball/6f177fca24200a5b97aef5ce7a5c98124a0f0db0",
"reference": "6f177fca24200a5b97aef5ce7a5c98124a0f0db0",
"url": "https://api.github.com/repos/symfony/Console/zipball/ef825fd9f809d275926547c9e57cbf14968793e8",
"reference": "ef825fd9f809d275926547c9e57cbf14968793e8",
"shasum": ""
},
"require": {
@ -566,16 +566,18 @@
},
"require-dev": {
"psr/log": "~1.0",
"symfony/event-dispatcher": "~2.1"
"symfony/event-dispatcher": "~2.1",
"symfony/process": "~2.1"
},
"suggest": {
"psr/log": "For using the console logger",
"symfony/event-dispatcher": ""
"symfony/event-dispatcher": "",
"symfony/process": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.5-dev"
"dev-master": "2.6-dev"
}
},
"autoload": {
@ -599,21 +601,21 @@
],
"description": "Symfony Console Component",
"homepage": "http://symfony.com",
"time": "2014-10-05 13:57:04"
"time": "2014-12-02 20:19:20"
},
{
"name": "symfony/filesystem",
"version": "v2.5.6",
"version": "v2.6.1",
"target-dir": "Symfony/Component/Filesystem",
"source": {
"type": "git",
"url": "https://github.com/symfony/Filesystem.git",
"reference": "4e62fab0060a826561c78b665925b37c870c45f5"
"reference": "ff6efc95256cb33031933729e68b01d720b5436b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Filesystem/zipball/4e62fab0060a826561c78b665925b37c870c45f5",
"reference": "4e62fab0060a826561c78b665925b37c870c45f5",
"url": "https://api.github.com/repos/symfony/Filesystem/zipball/ff6efc95256cb33031933729e68b01d720b5436b",
"reference": "ff6efc95256cb33031933729e68b01d720b5436b",
"shasum": ""
},
"require": {
@ -622,7 +624,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.5-dev"
"dev-master": "2.6-dev"
}
},
"autoload": {
@ -646,21 +648,21 @@
],
"description": "Symfony Filesystem Component",
"homepage": "http://symfony.com",
"time": "2014-09-22 09:14:18"
"time": "2014-12-02 20:19:20"
},
{
"name": "symfony/process",
"version": "v2.5.6",
"version": "v2.6.1",
"target-dir": "Symfony/Component/Process",
"source": {
"type": "git",
"url": "https://github.com/symfony/Process.git",
"reference": "9bbacbb3a7a27b17c0d51e2f126f59e0e588ad3a"
"reference": "bf0c9bd625f13b0b0bbe39919225cf145dfb935a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Process/zipball/9bbacbb3a7a27b17c0d51e2f126f59e0e588ad3a",
"reference": "9bbacbb3a7a27b17c0d51e2f126f59e0e588ad3a",
"url": "https://api.github.com/repos/symfony/Process/zipball/bf0c9bd625f13b0b0bbe39919225cf145dfb935a",
"reference": "bf0c9bd625f13b0b0bbe39919225cf145dfb935a",
"shasum": ""
},
"require": {
@ -669,7 +671,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.5-dev"
"dev-master": "2.6-dev"
}
},
"autoload": {
@ -693,21 +695,21 @@
],
"description": "Symfony Process Component",
"homepage": "http://symfony.com",
"time": "2014-10-01 05:50:18"
"time": "2014-12-02 20:19:20"
},
{
"name": "symfony/yaml",
"version": "v2.5.6",
"version": "v2.6.1",
"target-dir": "Symfony/Component/Yaml",
"source": {
"type": "git",
"url": "https://github.com/symfony/Yaml.git",
"reference": "2d9f527449cabfa8543dd7fa3a466d6ae83d6726"
"reference": "3346fc090a3eb6b53d408db2903b241af51dcb20"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/2d9f527449cabfa8543dd7fa3a466d6ae83d6726",
"reference": "2d9f527449cabfa8543dd7fa3a466d6ae83d6726",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/3346fc090a3eb6b53d408db2903b241af51dcb20",
"reference": "3346fc090a3eb6b53d408db2903b241af51dcb20",
"shasum": ""
},
"require": {
@ -716,7 +718,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.5-dev"
"dev-master": "2.6-dev"
}
},
"autoload": {
@ -740,7 +742,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "http://symfony.com",
"time": "2014-10-01 05:50:18"
"time": "2014-12-02 20:19:20"
},
{
"name": "toin0u/digitalocean",
@ -918,6 +920,112 @@
"templating"
],
"time": "2014-10-17 12:53:44"
},
{
"name": "zendframework/zend-config",
"version": "2.3.3",
"target-dir": "Zend/Config",
"source": {
"type": "git",
"url": "https://github.com/zendframework/Component_ZendConfig.git",
"reference": "a9ad512e1482461a5b500ee3fcf2d06ec9c7c7e8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zendframework/Component_ZendConfig/zipball/a9ad512e1482461a5b500ee3fcf2d06ec9c7c7e8",
"reference": "a9ad512e1482461a5b500ee3fcf2d06ec9c7c7e8",
"shasum": ""
},
"require": {
"php": ">=5.3.23",
"zendframework/zend-stdlib": "self.version"
},
"require-dev": {
"zendframework/zend-filter": "self.version",
"zendframework/zend-i18n": "self.version",
"zendframework/zend-json": "self.version",
"zendframework/zend-servicemanager": "self.version"
},
"suggest": {
"zendframework/zend-filter": "Zend\\Filter component",
"zendframework/zend-i18n": "Zend\\I18n component",
"zendframework/zend-json": "Zend\\Json to use the Json reader or writer classes",
"zendframework/zend-servicemanager": "Zend\\ServiceManager for use with the Config Factory to retrieve reader and writer instances"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.3-dev",
"dev-develop": "2.4-dev"
}
},
"autoload": {
"psr-0": {
"Zend\\Config\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"description": "provides a nested object property based user interface for accessing this configuration data within application code",
"homepage": "https://github.com/zendframework/zf2",
"keywords": [
"config",
"zf2"
],
"time": "2014-09-16 22:58:11"
},
{
"name": "zendframework/zend-stdlib",
"version": "2.3.3",
"target-dir": "Zend/Stdlib",
"source": {
"type": "git",
"url": "https://github.com/zendframework/Component_ZendStdlib.git",
"reference": "fa33e6647f830d0d2a1cb451efcdfe1bb9a66c33"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zendframework/Component_ZendStdlib/zipball/fa33e6647f830d0d2a1cb451efcdfe1bb9a66c33",
"reference": "fa33e6647f830d0d2a1cb451efcdfe1bb9a66c33",
"shasum": ""
},
"require": {
"php": ">=5.3.23"
},
"require-dev": {
"zendframework/zend-eventmanager": "self.version",
"zendframework/zend-serializer": "self.version",
"zendframework/zend-servicemanager": "self.version"
},
"suggest": {
"zendframework/zend-eventmanager": "To support aggregate hydrator usage",
"zendframework/zend-serializer": "Zend\\Serializer component",
"zendframework/zend-servicemanager": "To support hydrator plugin manager usage"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.3-dev",
"dev-develop": "2.4-dev"
}
},
"autoload": {
"psr-0": {
"Zend\\Stdlib\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"homepage": "https://github.com/zendframework/zf2",
"keywords": [
"stdlib",
"zf2"
],
"time": "2014-09-16 22:58:11"
}
],
"packages-dev": [],

@ -4,4 +4,4 @@
require_once __DIR__ . '/composer' . '/autoload_real.php';
return ComposerAutoloaderInitf179af6e0a538ee26448f8cc5e031097::getLoader();
return ComposerAutoloaderInit6960ef7dac86925470234c7bd58c9074::getLoader();

@ -56,7 +56,10 @@ class ClassLoader
public function getPrefixes()
{
return call_user_func_array('array_merge', $this->prefixesPsr0);
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
}
return array();
}
public function getPrefixesPsr4()

@ -6,6 +6,8 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Zend\\Stdlib\\' => array($vendorDir . '/zendframework/zend-stdlib'),
'Zend\\Config\\' => array($vendorDir . '/zendframework/zend-config'),
'Twig_' => array($vendorDir . '/twig/twig/lib'),
'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'),
'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),

@ -2,7 +2,7 @@
// autoload_real.php @generated by Composer
class ComposerAutoloaderInitf179af6e0a538ee26448f8cc5e031097
class ComposerAutoloaderInit6960ef7dac86925470234c7bd58c9074
{
private static $loader;
@ -19,9 +19,9 @@ class ComposerAutoloaderInitf179af6e0a538ee26448f8cc5e031097
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInitf179af6e0a538ee26448f8cc5e031097', 'loadClassLoader'), true, true);
spl_autoload_register(array('ComposerAutoloaderInit6960ef7dac86925470234c7bd58c9074', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInitf179af6e0a538ee26448f8cc5e031097', 'loadClassLoader'));
spl_autoload_unregister(array('ComposerAutoloaderInit6960ef7dac86925470234c7bd58c9074', 'loadClassLoader'));
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
@ -44,7 +44,7 @@ class ComposerAutoloaderInitf179af6e0a538ee26448f8cc5e031097
}
}
function composerRequiref179af6e0a538ee26448f8cc5e031097($file)
function composerRequire6960ef7dac86925470234c7bd58c9074($file)
{
require $file;
}

@ -523,43 +523,133 @@
]
},
{
"name": "sabre/vobject",
"version": "3.3.3",
"version_normalized": "3.3.3.0",
"name": "twig/twig",
"version": "v1.16.2",
"version_normalized": "1.16.2.0",
"source": {
"type": "git",
"url": "https://github.com/fruux/sabre-vobject.git",
"reference": "de508f160e811c09b5e651909eb20b9e058a043c"
"url": "https://github.com/fabpot/Twig.git",
"reference": "42f758d9fe2146d1f0470604fc05ee43580873fc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fruux/sabre-vobject/zipball/de508f160e811c09b5e651909eb20b9e058a043c",
"reference": "de508f160e811c09b5e651909eb20b9e058a043c",
"url": "https://api.github.com/repos/fabpot/Twig/zipball/42f758d9fe2146d1f0470604fc05ee43580873fc",
"reference": "42f758d9fe2146d1f0470604fc05ee43580873fc",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": ">=5.3.1"
"php": ">=5.2.4"
},
"require-dev": {
"phpunit/phpunit": "*",
"squizlabs/php_codesniffer": "*"
"time": "2014-10-17 12:53:44",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.16-dev"
}
},
"time": "2014-10-09 15:59:25",
"bin": [
"bin/vobject",
"bin/generate_vcards"
"installation-source": "dist",
"autoload": {
"psr-0": {
"Twig_": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": "http://fabien.potencier.org",
"role": "Lead Developer"
},
{
"name": "Armin Ronacher",
"email": "armin.ronacher@active-4.com",
"role": "Project Founder"
},
{
"name": "Twig Team",
"homepage": "https://github.com/fabpot/Twig/graphs/contributors",
"role": "Contributors"
}
],
"description": "Twig, the flexible, fast, and secure template language for PHP",
"homepage": "http://twig.sensiolabs.org",
"keywords": [
"templating"
]
},
{
"name": "emojione/emojione",
"version": "v1.3.0",
"version_normalized": "1.3.0.0",
"source": {
"type": "git",
"url": "https://github.com/Ranks/emojione.git",
"reference": "2a34acf3771a219045e133063aa759035d4f842c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Ranks/emojione/zipball/2a34acf3771a219045e133063aa759035d4f842c",
"reference": "2a34acf3771a219045e133063aa759035d4f842c",
"shasum": ""
},
"require": {
"php": ">=5.3"
},
"time": "2014-10-31 19:31:57",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Emojione\\": "lib/php/src"
}
},
"notification-url": "https://packagist.org/downloads/",
"description": "Emoji One is a complete set of emojis designed for the web. It includes libraries to easily convert unicode characters to shortnames (:smile:) and shortnames to our custom emoji images. PNG and SVG formats provided for the emoji images.",
"homepage": "http://www.emojione.com",
"keywords": [
"Emoji One",
"emoji",
"emojione",
"emojis",
"emoticons",
"smileys",
"smilies",
"unicode"
]
},
{
"name": "michelf/php-markdown",
"version": "1.4.1",
"version_normalized": "1.4.1.0",
"source": {
"type": "git",
"url": "https://github.com/michelf/php-markdown.git",
"reference": "de9a19c7bf352d41cc99ed86c3c0ef17e87394b6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/michelf/php-markdown/zipball/de9a19c7bf352d41cc99ed86c3c0ef17e87394b6",
"reference": "de9a19c7bf352d41cc99ed86c3c0ef17e87394b6",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"time": "2014-05-05 02:43:50",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.2.x-dev"
"dev-lib": "1.4.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Sabre\\VObject\\": "lib/"
"psr-0": {
"Michelf": ""
}
},
"notification-url": "https://packagist.org/downloads/",
@ -568,57 +658,113 @@
],
"authors": [
{
"name": "Evert Pot",
"email": "me@evertpot.com",
"homepage": "http://evertpot.com/",
"name": "Michel Fortin",
"email": "michel.fortin@michelf.ca",
"homepage": "http://michelf.ca/",
"role": "Developer"
},
{
"name": "Dominik Tobschall",
"email": "dominik@fruux.com",
"homepage": "http://tobschall.de/",
"role": "Developer"
"name": "John Gruber",
"homepage": "http://daringfireball.net/"
}
],
"description": "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects",
"homepage": "http://sabre.io/vobject/",
"description": "PHP Markdown",
"homepage": "http://michelf.ca/projects/php-markdown/",
"keywords": [
"VObject",
"iCalendar",
"jCal",
"jCard",
"vCard"
"markdown"
]
},
{
"name": "twig/twig",
"version": "v1.16.2",
"version_normalized": "1.16.2.0",
"name": "zendframework/zend-stdlib",
"version": "2.3.3",
"version_normalized": "2.3.3.0",
"target-dir": "Zend/Stdlib",
"source": {
"type": "git",
"url": "https://github.com/fabpot/Twig.git",
"reference": "42f758d9fe2146d1f0470604fc05ee43580873fc"
"url": "https://github.com/zendframework/Component_ZendStdlib.git",
"reference": "fa33e6647f830d0d2a1cb451efcdfe1bb9a66c33"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fabpot/Twig/zipball/42f758d9fe2146d1f0470604fc05ee43580873fc",
"reference": "42f758d9fe2146d1f0470604fc05ee43580873fc",
"url": "https://api.github.com/repos/zendframework/Component_ZendStdlib/zipball/fa33e6647f830d0d2a1cb451efcdfe1bb9a66c33",
"reference": "fa33e6647f830d0d2a1cb451efcdfe1bb9a66c33",
"shasum": ""
},
"require": {
"php": ">=5.2.4"
"php": ">=5.3.23"
},
"time": "2014-10-17 12:53:44",
"require-dev": {
"zendframework/zend-eventmanager": "self.version",
"zendframework/zend-serializer": "self.version",
"zendframework/zend-servicemanager": "self.version"
},
"suggest": {
"zendframework/zend-eventmanager": "To support aggregate hydrator usage",
"zendframework/zend-serializer": "Zend\\Serializer component",
"zendframework/zend-servicemanager": "To support hydrator plugin manager usage"
},
"time": "2014-09-16 22:58:11",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.16-dev"
"dev-master": "2.3-dev",
"dev-develop": "2.4-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-0": {
"Twig_": "lib/"
"Zend\\Stdlib\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"homepage": "https://github.com/zendframework/zf2",
"keywords": [
"stdlib",
"zf2"
]
},
{
"name": "sabre/vobject",
"version": "3.3.4",
"version_normalized": "3.3.4.0",
"source": {
"type": "git",
"url": "https://github.com/fruux/sabre-vobject.git",
"reference": "e7cbc59a7a77325dfa32924865e1802c9216a3e0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fruux/sabre-vobject/zipball/e7cbc59a7a77325dfa32924865e1802c9216a3e0",
"reference": "e7cbc59a7a77325dfa32924865e1802c9216a3e0",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": ">=5.3.1"
},
"require-dev": {
"phpunit/phpunit": "*",
"squizlabs/php_codesniffer": "*"
},
"time": "2014-11-19 22:15:24",
"bin": [
"bin/vobject",
"bin/generate_vcards"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.2.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Sabre\\VObject\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
@ -627,52 +773,52 @@
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": "http://fabien.potencier.org",
"role": "Lead Developer"
},
{
"name": "Armin Ronacher",
"email": "armin.ronacher@active-4.com",
"role": "Project Founder"
"name": "Evert Pot",
"email": "me@evertpot.com",
"homepage": "http://evertpot.com/",
"role": "Developer"
},
{
"name": "Twig Team",
"homepage": "https://github.com/fabpot/Twig/graphs/contributors",
"role": "Contributors"
"name": "Dominik Tobschall",
"email": "dominik@fruux.com",
"homepage": "http://tobschall.de/",
"role": "Developer"
}
],
"description": "Twig, the flexible, fast, and secure template language for PHP",
"homepage": "http://twig.sensiolabs.org",
"description": "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects",
"homepage": "http://sabre.io/vobject/",
"keywords": [
"templating"
"VObject",
"iCalendar",
"jCal",
"jCard",
"vCard"
]
},
{
"name": "symfony/filesystem",
"version": "v2.5.6",
"version_normalized": "2.5.6.0",
"version": "v2.6.1",
"version_normalized": "2.6.1.0",
"target-dir": "Symfony/Component/Filesystem",
"source": {
"type": "git",
"url": "https://github.com/symfony/Filesystem.git",
"reference": "4e62fab0060a826561c78b665925b37c870c45f5"
"reference": "ff6efc95256cb33031933729e68b01d720b5436b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Filesystem/zipball/4e62fab0060a826561c78b665925b37c870c45f5",
"reference": "4e62fab0060a826561c78b665925b37c870c45f5",
"url": "https://api.github.com/repos/symfony/Filesystem/zipball/ff6efc95256cb33031933729e68b01d720b5436b",
"reference": "ff6efc95256cb33031933729e68b01d720b5436b",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"time": "2014-09-22 09:14:18",
"time": "2014-12-02 20:19:20",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.5-dev"
"dev-master": "2.6-dev"
}
},
"installation-source": "dist",
@ -700,18 +846,18 @@
},
{
"name": "symfony/console",
"version": "v2.5.6",
"version_normalized": "2.5.6.0",
"version": "v2.6.1",
"version_normalized": "2.6.1.0",
"target-dir": "Symfony/Component/Console",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
"reference": "6f177fca24200a5b97aef5ce7a5c98124a0f0db0"
"reference": "ef825fd9f809d275926547c9e57cbf14968793e8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Console/zipball/6f177fca24200a5b97aef5ce7a5c98124a0f0db0",
"reference": "6f177fca24200a5b97aef5ce7a5c98124a0f0db0",
"url": "https://api.github.com/repos/symfony/Console/zipball/ef825fd9f809d275926547c9e57cbf14968793e8",
"reference": "ef825fd9f809d275926547c9e57cbf14968793e8",
"shasum": ""
},
"require": {
@ -719,17 +865,19 @@
},
"require-dev": {
"psr/log": "~1.0",
"symfony/event-dispatcher": "~2.1"
"symfony/event-dispatcher": "~2.1",
"symfony/process": "~2.1"
},
"suggest": {
"psr/log": "For using the console logger",
"symfony/event-dispatcher": ""
"symfony/event-dispatcher": "",
"symfony/process": ""
},
"time": "2014-10-05 13:57:04",
"time": "2014-12-02 20:19:20",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.5-dev"
"dev-master": "2.6-dev"
}
},
"installation-source": "dist",
@ -757,28 +905,28 @@
},
{
"name": "symfony/yaml",
"version": "v2.5.6",
"version_normalized": "2.5.6.0",
"version": "v2.6.1",
"version_normalized": "2.6.1.0",
"target-dir": "Symfony/Component/Yaml",
"source": {
"type": "git",
"url": "https://github.com/symfony/Yaml.git",
"reference": "2d9f527449cabfa8543dd7fa3a466d6ae83d6726"
"reference": "3346fc090a3eb6b53d408db2903b241af51dcb20"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/2d9f527449cabfa8543dd7fa3a466d6ae83d6726",
"reference": "2d9f527449cabfa8543dd7fa3a466d6ae83d6726",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/3346fc090a3eb6b53d408db2903b241af51dcb20",
"reference": "3346fc090a3eb6b53d408db2903b241af51dcb20",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"time": "2014-10-01 05:50:18",
"time": "2014-12-02 20:19:20",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.5-dev"
"dev-master": "2.6-dev"
}
},
"installation-source": "dist",
@ -806,28 +954,28 @@
},
{
"name": "symfony/process",
"version": "v2.5.6",
"version_normalized": "2.5.6.0",
"version": "v2.6.1",
"version_normalized": "2.6.1.0",
"target-dir": "Symfony/Component/Process",
"source": {
"type": "git",
"url": "https://github.com/symfony/Process.git",
"reference": "9bbacbb3a7a27b17c0d51e2f126f59e0e588ad3a"
"reference": "bf0c9bd625f13b0b0bbe39919225cf145dfb935a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Process/zipball/9bbacbb3a7a27b17c0d51e2f126f59e0e588ad3a",
"reference": "9bbacbb3a7a27b17c0d51e2f126f59e0e588ad3a",
"url": "https://api.github.com/repos/symfony/Process/zipball/bf0c9bd625f13b0b0bbe39919225cf145dfb935a",
"reference": "bf0c9bd625f13b0b0bbe39919225cf145dfb935a",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"time": "2014-10-01 05:50:18",
"time": "2014-12-02 20:19:20",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.5-dev"
"dev-master": "2.6-dev"
}
},
"installation-source": "dist",
@ -854,96 +1002,60 @@
"homepage": "http://symfony.com"
},
{
"name": "emojione/emojione",
"version": "v1.3.0",
"version_normalized": "1.3.0.0",
"name": "zendframework/zend-config",
"version": "2.3.3",
"version_normalized": "2.3.3.0",
"target-dir": "Zend/Config",
"source": {
"type": "git",
"url": "https://github.com/Ranks/emojione.git",
"reference": "2a34acf3771a219045e133063aa759035d4f842c"
"url": "https://github.com/zendframework/Component_ZendConfig.git",
"reference": "a9ad512e1482461a5b500ee3fcf2d06ec9c7c7e8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Ranks/emojione/zipball/2a34acf3771a219045e133063aa759035d4f842c",
"reference": "2a34acf3771a219045e133063aa759035d4f842c",
"url": "https://api.github.com/repos/zendframework/Component_ZendConfig/zipball/a9ad512e1482461a5b500ee3fcf2d06ec9c7c7e8",
"reference": "a9ad512e1482461a5b500ee3fcf2d06ec9c7c7e8",
"shasum": ""
},
"require": {
"php": ">=5.3"
},
"time": "2014-10-31 19:31:57",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Emojione\\": "lib/php/src"
}
},
"notification-url": "https://packagist.org/downloads/",
"description": "Emoji One is a complete set of emojis designed for the web. It includes libraries to easily convert unicode characters to shortnames (:smile:) and shortnames to our custom emoji images. PNG and SVG formats provided for the emoji images.",
"homepage": "http://www.emojione.com",
"keywords": [
"Emoji One",
"emoji",
"emojione",
"emojis",
"emoticons",
"smileys",
"smilies",
"unicode"
]
},
{
"name": "michelf/php-markdown",
"version": "1.4.1",
"version_normalized": "1.4.1.0",
"source": {
"type": "git",
"url": "https://github.com/michelf/php-markdown.git",
"reference": "de9a19c7bf352d41cc99ed86c3c0ef17e87394b6"
"php": ">=5.3.23",
"zendframework/zend-stdlib": "self.version"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/michelf/php-markdown/zipball/de9a19c7bf352d41cc99ed86c3c0ef17e87394b6",
"reference": "de9a19c7bf352d41cc99ed86c3c0ef17e87394b6",
"shasum": ""
"require-dev": {
"zendframework/zend-filter": "self.version",
"zendframework/zend-i18n": "self.version",
"zendframework/zend-json": "self.version",
"zendframework/zend-servicemanager": "self.version"
},
"require": {
"php": ">=5.3.0"
"suggest": {
"zendframework/zend-filter": "Zend\\Filter component",
"zendframework/zend-i18n": "Zend\\I18n component",
"zendframework/zend-json": "Zend\\Json to use the Json reader or writer classes",
"zendframework/zend-servicemanager": "Zend\\ServiceManager for use with the Config Factory to retrieve reader and writer instances"
},
"time": "2014-05-05 02:43:50",
"time": "2014-09-16 22:58:11",
"type": "library",
"extra": {
"branch-alias": {
"dev-lib": "1.4.x-dev"
"dev-master": "2.3-dev",
"dev-develop": "2.4-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-0": {
"Michelf": ""
"Zend\\Config\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Michel Fortin",
"email": "michel.fortin@michelf.ca",
"homepage": "http://michelf.ca/",
"role": "Developer"
},
{
"name": "John Gruber",
"homepage": "http://daringfireball.net/"
}
],
"description": "PHP Markdown",
"homepage": "http://michelf.ca/projects/php-markdown/",
"description": "provides a nested object property based user interface for accessing this configuration data within application code",
"homepage": "https://github.com/zendframework/zf2",
"keywords": [
"markdown"
"config",
"zf2"
]
}
]

@ -1,6 +1,23 @@
ChangeLog
=========
3.3.4 (2014-11-19)
------------------
* #154: Converting `ANNIVERSARY` to `X-ANNIVERSARY` and `X-ABDATE` and
vice-versa when converting to/from vCard 4.
* #154: It's now possible to easily select all vCard properties belonging to
a single group with `$vcard->{'ITEM1.'}` syntax. (@armin-hackmann)
* #156: Simpler way to check if a string is UTF-8. (@Hywan)
* Unittest improvements.
* #159: The recurrence iterator, freebusy generator and iCalendar DATE and
DATE-TIME properties can now all accept a reference timezone when working
floating times or all-day events.
* #159: Master events will no longer get a `RECURRENCE-ID` when expanding.
* #159: `RECURRENCE-ID` for all-day events will now be correct when expanding.
* #163: Added a `getTimeZone()` method to `VTIMEZONE` components.
3.3.3 (2014-10-09)
------------------

@ -225,8 +225,14 @@ class Component extends Node {
foreach($this->children as $key=>$child) {
if (
strtoupper($child->name) === $name &&
(is_null($group) || ( $child instanceof Property && strtoupper($child->group) === $group))
(
strtoupper($child->name) === $name
&& (is_null($group) || ( $child instanceof Property && strtoupper($child->group) === $group))
)
||
(
$name === '' && $child instanceof Property && strtoupper($child->group) === $group
)
) {
$result[$key] = $child;

@ -2,10 +2,11 @@
namespace Sabre\VObject\Component;
use
Sabre\VObject,
Sabre\VObject\Component,
Sabre\VObject\Recur\EventIterator;
use DateTime;
use DateTimeZone;
use Sabre\VObject;
use Sabre\VObject\Component;
use Sabre\VObject\Recur\EventIterator;
/**
* The VCalendar component
@ -239,12 +240,18 @@ class VCalendar extends VObject\Document {
*
* @param DateTime $start
* @param DateTime $end
* @param DateTimeZone $timeZone reference timezone for floating dates and
* times.
* @return void
*/
public function expand(\DateTime $start, \DateTime $end) {
public function expand(DateTime $start, DateTime $end, DateTimeZone $timeZone = null) {
$newEvents = array();
if (!$timeZone) {
$timeZone = new DateTimeZone('UTC');
}
foreach($this->select('VEVENT') as $key=>$vevent) {
if (isset($vevent->{'RECURRENCE-ID'})) {
@ -266,7 +273,7 @@ class VCalendar extends VObject\Document {
throw new \LogicException('Event did not have a UID!');
}
$it = new EventIterator($this, $vevent->uid);
$it = new EventIterator($this, $vevent->uid, $timeZone);
$it->fastForward($start);
while($it->valid() && $it->getDTStart() < $end) {
@ -288,11 +295,11 @@ class VCalendar extends VObject\Document {
foreach($newEvent->children as $child) {
if ($child instanceof VObject\Property\ICalendar\DateTime && $child->hasTime()) {
$dt = $child->getDateTimes();
$dt = $child->getDateTimes($timeZone);
// We only need to update the first timezone, because
// setDateTimes will match all other timezones to the
// first.
$dt[0]->setTimeZone(new \DateTimeZone('UTC'));
$dt[0]->setTimeZone(new DateTimeZone('UTC'));
$child->setDateTimes($dt);
}

@ -16,6 +16,20 @@ use Sabre\VObject;
*/
class VTimeZone extends VObject\Component {
/**
* Returns the PHP DateTimeZone for this VTIMEZONE component.
*
* If we can't accurately determine the timezone, this method will return
* UTC.
*
* @return \DateTimeZone
*/
function getTimeZone() {
return VObject\TimeZoneUtil::getTimeZone((string)$this->TZID, $this->root);
}
/**
* A simple list of validation rules.
*
@ -30,7 +44,7 @@ class VTimeZone extends VObject\Component {
*
* @var array
*/
public function getValidationRules() {
function getValidationRules() {
return array(
'TZID' => 1,

@ -2,6 +2,12 @@
namespace Sabre\VObject;
use DateTime;
use DateTimeZone;
use DateInterval;
use InvalidArgumentException;
use LogicException;
/**
* DateTimeParser
*
@ -25,19 +31,19 @@ class DateTimeParser {
* @param DateTimeZone $tz
* @return DateTime
*/
static public function parseDateTime($dt, \DateTimeZone $tz = null) {
static public function parseDateTime($dt, DateTimeZone $tz = null) {
// Format is YYYYMMDD + "T" + hhmmss
$result = preg_match('/^([0-9]{4})([0-1][0-9])([0-3][0-9])T([0-2][0-9])([0-5][0-9])([0-5][0-9])([Z]?)$/',$dt,$matches);
if (!$result) {
throw new \LogicException('The supplied iCalendar datetime value is incorrect: ' . $dt);
throw new LogicException('The supplied iCalendar datetime value is incorrect: ' . $dt);
}
if ($matches[7]==='Z' || is_null($tz)) {
$tz = new \DateTimeZone('UTC');
$tz = new DateTimeZone('UTC');
}
$date = new \DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3] . ' ' . $matches[4] . ':' . $matches[5] .':' . $matches[6], $tz);
$date = new DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3] . ' ' . $matches[4] . ':' . $matches[5] .':' . $matches[6], $tz);
// Still resetting the timezone, to normalize everything to UTC
// $date->setTimeZone(new \DateTimeZone('UTC'));
@ -46,21 +52,26 @@ class DateTimeParser {
}
/**
* Parses an iCalendar (rfc5545) formatted date and returns a DateTime object
* Parses an iCalendar (rfc5545) formatted date and returns a DateTime object.
*
* @param string $date
* @param DateTimeZone $tz
* @return DateTime
*/
static public function parseDate($date) {
static public function parseDate($date, DateTimeZone $tz = null) {
// Format is YYYYMMDD
$result = preg_match('/^([0-9]{4})([0-1][0-9])([0-3][0-9])$/',$date,$matches);
if (!$result) {
throw new \LogicException('The supplied iCalendar date value is incorrect: ' . $date);
throw new LogicException('The supplied iCalendar date value is incorrect: ' . $date);
}
if (is_null($tz)) {
$tz = new DateTimeZone('UTC');
}
$date = new \DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3], new \DateTimeZone('UTC'));
$date = new DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3], $tz);
return $date;
}
@ -79,7 +90,7 @@ class DateTimeParser {
$result = preg_match('/^(?P<plusminus>\+|-)?P((?P<week>\d+)W)?((?P<day>\d+)D)?(T((?P<hour>\d+)H)?((?P<minute>\d+)M)?((?P<second>\d+)S)?)?$/', $duration, $matches);
if (!$result) {
throw new \LogicException('The supplied iCalendar duration value is incorrect: ' . $duration);
throw new LogicException('The supplied iCalendar duration value is incorrect: ' . $duration);
}
if (!$asString) {
@ -128,7 +139,7 @@ class DateTimeParser {
if ($duration==='P') {
$duration = 'PT0S';
}
$iv = new \DateInterval($duration);
$iv = new DateInterval($duration);
if ($invert) $iv->invert = true;
return $iv;
@ -164,17 +175,17 @@ class DateTimeParser {
* Parses either a Date or DateTime, or Duration value.
*
* @param string $date
* @param DateTimeZone|string $referenceTZ
* @param DateTimeZone|string $referenceTz
* @return DateTime|DateInterval
*/
static public function parse($date, $referenceTZ = null) {
static public function parse($date, $referenceTz = null) {
if ($date[0]==='P' || ($date[0]==='-' && $date[1]==='P')) {
return self::parseDuration($date);
} elseif (strlen($date)===8) {
return self::parseDate($date);
return self::parseDate($date, $referenceTz);
} else {
return self::parseDateTime($date, $referenceTZ);
return self::parseDateTime($date, $referenceTz);
}
}
@ -284,7 +295,7 @@ class DateTimeParser {
$/x';
if (!preg_match($regex, $date, $matches)) {
throw new \InvalidArgumentException('Invalid vCard date-time string: ' . $date);
throw new InvalidArgumentException('Invalid vCard date-time string: ' . $date);
}
}
@ -387,7 +398,7 @@ class DateTimeParser {
$/x';
if (!preg_match($regex, $date, $matches)) {
throw new \InvalidArgumentException('Invalid vCard time string: ' . $date);
throw new InvalidArgumentException('Invalid vCard time string: ' . $date);
}
}

@ -2,6 +2,7 @@
namespace Sabre\VObject;
use DateTimeZone;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Recur\EventIterator;
@ -49,6 +50,21 @@ class FreeBusyGenerator {
*/
protected $baseObject;
/**
* Reference timezone.
*
* When we are calculating busy times, and we come across so-called
* floating times (times without a timezone), we use the reference timezone
* instead.
*
* This is also used for all-day events.
*
* This defaults to UTC.
*
* @var DateTimeZone
*/
protected $timeZone;
/**
* Creates the generator.
*
@ -58,9 +74,10 @@ class FreeBusyGenerator {
* @param DateTime $start
* @param DateTime $end
* @param mixed $objects
* @param DateTimeZone $timeZone
* @return void
*/
public function __construct(\DateTime $start = null, \DateTime $end = null, $objects = null) {
public function __construct(\DateTime $start = null, \DateTime $end = null, $objects = null, DateTimeZone $timeZone = null) {
if ($start && $end) {
$this->setTimeRange($start, $end);
@ -69,6 +86,10 @@ class FreeBusyGenerator {
if ($objects) {
$this->setObjects($objects);
}
if (is_null($timeZone)) {
$timeZone = new DateTimeZone('UTC');
}
$this->setTimeZone($timeZone);
}
@ -136,6 +157,18 @@ class FreeBusyGenerator {
}
/**
* Sets the reference timezone for floating times.
*
* @param DateTimeZone $timeZone
* @return void
*/
public function setTimeZone(DateTimeZone $timeZone) {
$this->timeZone = $timeZone;
}
/**
* Parses the input data and returns a correct VFREEBUSY object, wrapped in
* a VCALENDAR.
@ -172,7 +205,7 @@ class FreeBusyGenerator {
if ($component->RRULE) {
$iterator = new EventIterator($object, (string)$component->uid);
$iterator = new EventIterator($object, (string)$component->uid, $this->timeZone);
if ($this->start) {
$iterator->fastForward($this->start);
}
@ -196,13 +229,13 @@ class FreeBusyGenerator {
} else {
$startTime = $component->DTSTART->getDateTime();
$startTime = $component->DTSTART->getDateTime($this->timeZone);
if ($this->end && $startTime > $this->end) {
break;
}
$endTime = null;
if (isset($component->DTEND)) {
$endTime = $component->DTEND->getDateTime();
$endTime = $component->DTEND->getDateTime($this->timeZone);
} elseif (isset($component->DURATION)) {
$duration = DateTimeParser::parseDuration((string)$component->DURATION);
$endTime = clone $startTime;

@ -736,6 +736,10 @@ class Broker {
$event->add('SUMMARY', $summary);
}
} else {
// This branch of the code is reached, when a reply is
// generated for an instance of a recurring event, through the
// fact that the instance has disappeared by showing up in
// EXDATE
$dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
// Treat is as a DATE field
if (strlen($instance['id']) <= 8) {

@ -2,11 +2,11 @@
namespace Sabre\VObject\Property\ICalendar;
use
Sabre\VObject\Property,
Sabre\VObject\Parser\MimeDir,
Sabre\VObject\DateTimeParser,
Sabre\VObject\TimeZoneUtil;
use DateTimeZone;
use Sabre\VObject\Property;
use Sabre\VObject\Parser\MimeDir;
use Sabre\VObject\DateTimeParser;
use Sabre\VObject\TimeZoneUtil;
/**
* DateTime property
@ -117,11 +117,16 @@ class DateTime extends Property {
* first will be returned. To get an array with multiple values, call
* getDateTimes.
*
* If no timezone information is known, because it's either an all-day
* property or floating time, we will use the DateTimeZone argument to
* figure out the exact date.
*
* @param DateTimeZone $timeZone
* @return \DateTime
*/
public function getDateTime() {
public function getDateTime(DateTimeZone $timeZone = null) {
$dt = $this->getDateTimes();
$dt = $this->getDateTimes($timeZone);
if (!$dt) return null;
return $dt[0];
@ -131,20 +136,25 @@ class DateTime extends Property {
/**
* Returns multiple date-time values.
*
* If no timezone information is known, because it's either an all-day
* property or floating time, we will use the DateTimeZone argument to
* figure out the exact date.
*
* @param DateTimeZone $timeZone
* @return \DateTime[]
*/
public function getDateTimes() {
public function getDateTimes(DateTimeZone $timeZone = null) {
// Finding the timezone.
$tz = $this['TZID'];
// Does the property have a TZID?
$tzid = $this['TZID'];
if ($tz) {
$tz = TimeZoneUtil::getTimeZone((string)$tz, $this->root);
if ($tzid) {
$timeZone = TimeZoneUtil::getTimeZone((string)$tzid, $this->root);
}
$dts = array();
foreach($this->getParts() as $part) {
$dts[] = DateTimeParser::parse($part, $tz);
$dts[] = DateTimeParser::parse($part, $timeZone);
}
return $dts;

@ -4,6 +4,7 @@ namespace Sabre\VObject\Recur;
use InvalidArgumentException;
use DateTime;
use DateTimeZone;
use Sabre\VObject\Component;
use Sabre\VObject\Component\VEvent;
@ -55,6 +56,20 @@ use Sabre\VObject\Component\VEvent;
*/
class EventIterator implements \Iterator {
/**
* Reference timeZone for floating dates and times.
*
* @var DateTimeZone
*/
protected $timeZone;
/**
* True if we're iterating an all-day event.
*
* @var bool
*/
protected $allDay = false;
/**
* Creates the iterator
*
@ -63,8 +78,15 @@ class EventIterator implements \Iterator {
*
* @param Component $vcal
* @param string|null $uid
* @param DateTimeZone $timeZone Reference timezone for floating dates and
* times.
*/
public function __construct(Component $vcal, $uid = null) {
public function __construct(Component $vcal, $uid = null, DateTimeZone $timeZone = null) {
if (is_null($this->timeZone)) {
$timeZone = new DateTimeZone('UTC');
}
$this->timeZone = $timeZone;
$rrule = null;
if ($vcal instanceof VEvent) {
@ -95,7 +117,9 @@ class EventIterator implements \Iterator {
} else {
$this->exceptions[$vevent->{'RECURRENCE-ID'}->getDateTime()->getTimeStamp()] = true;
$this->exceptions[
$vevent->{'RECURRENCE-ID'}->getDateTime($this->timeZone)->getTimeStamp()
] = true;
$this->overriddenEvents[] = $vevent;
}
@ -126,13 +150,14 @@ class EventIterator implements \Iterator {
'COUNT' => 1,
);
}
$this->startDate = $this->masterEvent->DTSTART->getDateTime();
$this->startDate = $this->masterEvent->DTSTART->getDateTime($this->timeZone);
$this->allDay = !$this->masterEvent->DTSTART->hasTime();
if (isset($this->masterEvent->EXDATE)) {
foreach($this->masterEvent->EXDATE as $exDate) {
foreach($exDate->getDateTimes() as $dt) {
foreach($exDate->getDateTimes($this->timeZone) as $dt) {
$this->exceptions[$dt->getTimeStamp()] = true;
}
@ -142,14 +167,14 @@ class EventIterator implements \Iterator {
if (isset($this->masterEvent->DTEND)) {
$this->eventDuration =
$this->masterEvent->DTEND->getDateTime()->getTimeStamp() -
$this->masterEvent->DTEND->getDateTime($this->timeZone)->getTimeStamp() -
$this->startDate->getTimeStamp();
} elseif (isset($this->masterEvent->DURATION)) {
$duration = $this->masterEvent->DURATION->getDateInterval();
$end = clone $this->startDate;
$end->add($duration);
$this->eventDuration = $end->getTimeStamp() - $this->startDate->getTimeStamp();
} elseif ($this->masterEvent->DTSTART->getValueType() === 'DATE') {
} elseif ($this->allDay) {
$this->eventDuration = 3600 * 24;
} else {
$this->eventDuration = 0;
@ -259,8 +284,15 @@ class EventIterator implements \Iterator {
if (isset($event->DTEND)) {
$event->DTEND->setDateTime($this->getDtEnd());
}
if ($this->recurIterator->key() > 0) {
$event->add('RECURRENCE-ID', $event->DTSTART->getDateTime());
// Including a RECURRENCE-ID to the object, unless this is the first
// object.
//
// The inner recurIterator is always one step ahead, this is why we're
// checking for the key being higher than 1.
if ($this->recurIterator->key() > 1) {
$recurid = clone $event->DTSTART;
$recurid->name = 'RECURRENCE-ID';
$event->add($recurid);
}
return $event;
@ -301,7 +333,7 @@ class EventIterator implements \Iterator {
// re-creating overridden event index.
$index = array();
foreach($this->overriddenEvents as $key=>$event) {
$stamp = $event->DTSTART->getDateTime()->getTimeStamp();
$stamp = $event->DTSTART->getDateTime($this->timeZone)->getTimeStamp();
$index[$stamp] = $key;
}
krsort($index);
@ -357,7 +389,7 @@ class EventIterator implements \Iterator {
// Putting the rrule next date aside.
$this->nextDate = $nextDate;
$this->currentDate = $this->currentOverriddenEvent->DTSTART->getDateTime();
$this->currentDate = $this->currentOverriddenEvent->DTSTART->getDateTime($this->timeZone);
// Ensuring that this item will only be used once.
array_pop($this->overriddenEventsIndex);

@ -374,7 +374,11 @@ class RRuleIterator implements Iterator {
// Current hour of the day
$currentHour = $this->currentDate->format('G');
} while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours)) || ($this->byMonth && !in_array($currentMonth, $recurrenceMonths)));
} while (
($this->byDay && !in_array($currentDay, $recurrenceDays)) ||
($this->byHour && !in_array($currentHour, $recurrenceHours)) ||
($this->byMonth && !in_array($currentMonth, $recurrenceMonths))
);
}

@ -19,17 +19,12 @@ class StringUtil {
*/
static public function isUTF8($str) {
// First check.. mb_check_encoding
if (!mb_check_encoding($str, 'UTF-8')) {
return false;
}
// Control characters
if (preg_match('%(?:[\x00-\x08\x0B-\x0C\x0E\x0F])%', $str)) {
if (preg_match('%[\x00-\x08\x0B-\x0C\x0E\x0F]%', $str)) {
return false;
}
return true;
return (bool)preg_match('%%u', $str);
}

@ -76,7 +76,6 @@ class VCardConverter {
}
$parameters = $property->parameters();
$valueType = null;
if (isset($parameters['VALUE'])) {
$valueType = $parameters['VALUE']->getValue();
@ -85,14 +84,19 @@ class VCardConverter {
if (!$valueType) {
$valueType = $property->getValueType();
}
$newProperty = $output->createProperty(
$property->name,
$property->getParts(),
array(), // parameters will get added a bit later.
$valueType
);
$newProperty = null;
if ($targetVersion===Document::VCARD30) {
if ($property instanceof Property\Uri && in_array($property->name, array('PHOTO','LOGO','SOUND'))) {
$newProperty = $this->convertUriToBinary($output, $property, $parameters);
$newProperty = $this->convertUriToBinary($output, $newProperty, $parameters);
} elseif ($property instanceof Property\VCard\DateAndOrTime) {
@ -106,26 +110,38 @@ class VCardConverter {
$parts = DateTimeParser::parseVCardDateTime($property->getValue());
if (is_null($parts['year'])) {
$newValue = '1604-' . $parts['month'] . '-' . $parts['date'];
$newProperty = $output->createProperty(
$property->name,
$newValue,
array(
'X-APPLE-OMIT-YEAR' => '1604'
),
$valueType
);
$newProperty->setValue($newValue);
$newProperty['X-APPLE-OMIT-YEAR'] = '1604';
}
if ($newProperty->name == 'ANNIVERSARY') {
// Microsoft non-standard anniversary
$newProperty->name = 'X-ANNIVERSARY';
// We also need to add a new apple property for the same
// purpose. This apple property needs a 'label' in the same
// group, so we first need to find a groupname that doesn't
// exist yet.
$x = 1;
while($output->select('ITEM' . $x . '.')) {
$x++;
}
$output->add('ITEM' . $x . '.X-ABDATE', $newProperty->getValue(), array('VALUE' => 'DATE-AND-OR-TIME'));
$output->add('ITEM' . $x . '.X-ABLABEL', '_$!<Anniversary>!$_');
}
} elseif ($property->name === 'KIND') {
switch(strtolower($property->getValue())) {
case 'org' :
// OS X addressbook property.
// vCard 3.0 does not have an equivalent to KIND:ORG,
// but apple has an extension that means the same
// thing.
$newProperty = $output->createProperty('X-ABSHOWAS','COMPANY');
break;
case 'individual' :
// Individual is implied, so we can just skip it.
// Individual is implicit, so we skip it.
return;
case 'group' :
@ -146,7 +162,7 @@ class VCardConverter {
if ($property instanceof Property\Binary) {
$newProperty = $this->convertBinaryToUri($output, $property, $parameters);
$newProperty = $this->convertBinaryToUri($output, $newProperty, $parameters);
} elseif ($property instanceof Property\VCard\DateAndOrTime && isset($parameters['X-APPLE-OMIT-YEAR'])) {
@ -155,45 +171,66 @@ class VCardConverter {
$parts = DateTimeParser::parseVCardDateTime($property->getValue());
if ($parts['year']===$property['X-APPLE-OMIT-YEAR']->getValue()) {
$newValue = '--' . $parts['month'] . '-' . $parts['date'];
$newProperty = $output->createProperty(
$property->name,
$newValue,
array(),
$valueType
);
$newProperty->setValue($newValue);
}
// Regardless if the year matched or not, we do need to strip
// X-APPLE-OMIT-YEAR.
unset($parameters['X-APPLE-OMIT-YEAR']);
} else {
switch($property->name) {
case 'X-ABSHOWAS' :
if (strtoupper($property->getValue()) === 'COMPANY') {
$newProperty = $output->createProperty('KIND','org');
}
break;
case 'X-ADDRESSBOOKSERVER-KIND' :
if (strtoupper($property->getValue()) === 'GROUP') {
$newProperty = $output->createProperty('KIND','group');
}
switch($property->name) {
case 'X-ABSHOWAS' :
if (strtoupper($property->getValue()) === 'COMPANY') {
$newProperty = $output->createProperty('KIND','ORG');
}
break;
case 'X-ADDRESSBOOKSERVER-KIND' :
if (strtoupper($property->getValue()) === 'GROUP') {
$newProperty = $output->createProperty('KIND','GROUP');
}
break;
case 'X-ANNIVERSARY' :
$newProperty->name = 'ANNIVERSARY';
// If we already have an anniversary property with the same
// value, ignore.
foreach ($output->select('ANNIVERSARY') as $anniversary) {
if ($anniversary->getValue() === $newProperty->getValue()) {
return;
}
}
break;
case 'X-ABDATE' :
// Find out what the label was, if it exists.
if (!$property->group) {
break;
}
}
}
}
$label = $input->{$property->group . '.X-ABLABEL'};
// We only support converting anniversaries.
if ($label->getValue()!=='_$!<Anniversary>!$_') {
break;
}
if (is_null($newProperty)) {
// If we already have an anniversary property with the same
// value, ignore.
foreach ($output->select('ANNIVERSARY') as $anniversary) {
if ($anniversary->getValue() === $newProperty->getValue()) {
return;
}
}
$newProperty->name = 'ANNIVERSARY';
break;
// Apple's per-property label system.
case 'X-ABLABEL' :
if($newProperty->getValue() === '_$!<Anniversary>!$_') {
// We can safely remove these, as they are converted to
// ANNIVERSARY properties.
return;
}
break;
$newProperty = $output->createProperty(
$property->name,
$property->getParts(),
array(), // no parameters yet
$valueType
);
}
}
@ -231,10 +268,11 @@ class VCardConverter {
* the new property.
* @return Property\Uri
*/
protected function convertBinaryToUri(Component\VCard $output, Property\Binary $property, array &$parameters) {
protected function convertBinaryToUri(Component\VCard $output, Property\Binary $newProperty, array &$parameters) {
$value = $newProperty->getValue();
$newProperty = $output->createProperty(
$property->name,
$newProperty->name,
null, // no value
array(), // no parameters yet
'URI' // Forcing the BINARY type
@ -267,8 +305,7 @@ class VCardConverter {
}
$newProperty->setValue('data:' . $mimeType . ';base64,' . base64_encode($property->getValue()));
$newProperty->setValue('data:' . $mimeType . ';base64,' . base64_encode($value));
return $newProperty;
}
@ -286,17 +323,17 @@ class VCardConverter {
* the new property.
* @return Property\Binary|null
*/
protected function convertUriToBinary(Component\VCard $output, Property\Uri $property, array &$parameters) {
protected function convertUriToBinary(Component\VCard $output, Property\Uri $newProperty, array &$parameters) {
$value = $property->getValue();
$value = $newProperty->getValue();
// Only converting data: uris
if (substr($value, 0, 5)!=='data:') {
return;
return $newProperty;
}
$newProperty = $output->createProperty(
$property->name,
$newProperty->name,
null, // no value
array(), // no parameters yet
'BINARY'

@ -14,6 +14,6 @@ class Version {
/**
* Full version number
*/
const VERSION = '3.3.3';
const VERSION = '3.3.4';
}

@ -2,6 +2,7 @@
namespace Sabre\VObject\Component;
use DateTimeZone;
use Sabre\VObject;
class VCalendarTest extends \PHPUnit_Framework_TestCase {
@ -9,13 +10,16 @@ class VCalendarTest extends \PHPUnit_Framework_TestCase {
/**
* @dataProvider expandData
*/
public function testExpand($input, $output) {
public function testExpand($input, $output, $timeZone = 'UTC', $start = '2011-12-01', $end = '2011-12-31') {
$vcal = VObject\Reader::read($input);
$timeZone = new DateTimeZone($timeZone);
$vcal->expand(
new \DateTime('2011-12-01'),
new \DateTime('2011-12-31')
new \DateTime($start),
new \DateTime($end),
$timeZone
);
// This will normalize the output
@ -218,6 +222,52 @@ END:VCALENDAR
';
$tests[] = array($input, $output);
// Floating dates and times.
$input = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:bla1
DTSTART:20141112T195000
END:VEVENT
BEGIN:VEVENT
UID:bla2
DTSTART;VALUE=DATE:20141112
END:VEVENT
BEGIN:VEVENT
UID:bla3
DTSTART;VALUE=DATE:20141112
RRULE:FREQ=DAILY;COUNT=2
END:VEVENT
END:VCALENDAR
ICS;
$output = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:bla1
DTSTART:20141112T225000Z
END:VEVENT
BEGIN:VEVENT
UID:bla2
DTSTART;VALUE=DATE:20141112
END:VEVENT
BEGIN:VEVENT
UID:bla3
DTSTART;VALUE=DATE:20141112
END:VEVENT
BEGIN:VEVENT
UID:bla3
DTSTART;VALUE=DATE:20141113
RECURRENCE-ID;VALUE=DATE:20141113
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array($input, $output, 'America/Argentina/Buenos_Aires', '2014-01-01', '2015-01-01');
return $tests;
}

@ -7,7 +7,7 @@ use Sabre\VObject\Reader;
class VTimeZoneTest extends \PHPUnit_Framework_TestCase {
public function testValidate() {
function testValidate() {
$input = <<<HI
BEGIN:VCALENDAR
@ -31,4 +31,27 @@ HI;
}
function testGetTimeZone() {
$input = <<<HI
BEGIN:VCALENDAR
VERSION:2.0
PRODID:YoYo
BEGIN:VTIMEZONE
TZID:America/Toronto
END:VTIMEZONE
END:VCALENDAR
HI;
$obj = Reader::read($input);
$tz = new \DateTimeZone('America/Toronto');
$this->assertEquals(
$tz,
$obj->VTIMEZONE->getTimeZone()
);
}
}

@ -6,8 +6,9 @@ class FreeBusyGeneratorTest extends \PHPUnit_Framework_TestCase {
function getInput() {
// shows up
$blob1 = <<<ICS
$tests = array();
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar
@ -17,8 +18,13 @@ END:VEVENT
END:VCALENDAR
ICS;
// opaque, shows up
$blob2 = <<<ICS
$tests[] = array(
$blob,
"20110101T120000Z/20110101T130000Z"
);
// opaque, shows up
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar2
@ -29,8 +35,13 @@ END:VEVENT
END:VCALENDAR
ICS;
// transparent, hidden
$blob3 = <<<ICS
$tests[] = array(
$blob,
"20110101T130000Z/20110101T140000Z"
);
// transparent, hidden
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar3
@ -41,8 +52,13 @@ END:VEVENT
END:VCALENDAR
ICS;
// cancelled, hidden
$blob4 = <<<ICS
$tests[] = array(
$blob,
null,
);
// cancelled, hidden
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar4
@ -53,8 +69,13 @@ END:VEVENT
END:VCALENDAR
ICS;
// tentative, shows up
$blob5 = <<<ICS
$tests[] = array(
$blob,
null,
);
// tentative, shows up
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar5
@ -65,8 +86,13 @@ END:VEVENT
END:VCALENDAR
ICS;
// outside of time-range, hidden
$blob6 = <<<ICS
$tests[] = array(
$blob,
'20110101T180000Z/20110101T190000Z',
);
// outside of time-range, hidden
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar6
@ -76,8 +102,13 @@ END:VEVENT
END:VCALENDAR
ICS;
// outside of time-range, hidden
$blob7 = <<<ICS
$tests[] = array(
$blob,
null,
);
// outside of time-range, hidden
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar7
@ -87,8 +118,13 @@ END:VEVENT
END:VCALENDAR
ICS;
// using duration, shows up
$blob8 = <<<ICS
$tests[] = array(
$blob,
null,
);
// using duration, shows up
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar8
@ -98,9 +134,13 @@ END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
$blob,
'20110101T190000Z/20110101T200000Z',
);
// Day-long event, shows up
$blob9 = <<<ICS
// Day-long event, shows up
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar9
@ -109,9 +149,14 @@ END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
$blob,
'20110102T000000Z/20110103T000000Z',
);
// No duration, does not show up
$blob10 = <<<ICS
// No duration, does not show up
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar10
@ -120,8 +165,13 @@ END:VEVENT
END:VCALENDAR
ICS;
// encoded as object, shows up
$blob11 = <<<ICS
$tests[] = array(
$blob,
null,
);
// encoded as object, shows up
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar11
@ -131,8 +181,13 @@ END:VEVENT
END:VCALENDAR
ICS;
// Freebusy. Some parts show up
$blob12 = <<<ICS
$tests[] = array(
Reader::read($blob),
'20110101T210000Z/20110101T220000Z',
);
// Freebusy. Some parts show up
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VFREEBUSY
FREEBUSY:20110103T010000Z/20110103T020000Z
@ -144,8 +199,19 @@ END:VFREEBUSY
END:VCALENDAR
ICS;
// Yearly recurrence rule, shows up
$blob13 = <<<ICS
$tests[] = array(
Reader::read($blob),
array(
'20110103T010000Z/20110103T020000Z',
'20110103T030000Z/20110103T040000Z',
'20110103T040000Z/20110103T050000Z',
'20110103T050000Z/20110103T060000Z',
)
);
// Yearly recurrence rule, shows up
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar13
@ -156,8 +222,14 @@ END:VEVENT
END:VCALENDAR
ICS;
// Yearly recurrence rule + duration, shows up
$blob14 = <<<ICS
$tests[] = array(
Reader::read($blob),
'20110101T220000Z/20110101T230000Z',
);
// Yearly recurrence rule + duration, shows up
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar14
@ -168,54 +240,97 @@ END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
Reader::read($blob),
'20110101T230000Z/20110102T000000Z',
);
// Floating time, no timezone
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar
DTSTART:20110101T120000
DTEND:20110101T130000
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
$blob,
"20110101T120000Z/20110101T130000Z"
);
return array(
$blob1,
$blob2,
$blob3,
$blob4,
$blob5,
$blob6,
$blob7,
$blob8,
$blob9,
$blob10,
Reader::read($blob11),
$blob12,
$blob13,
$blob14,
// Floating time + reference timezone
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar
DTSTART:20110101T120000
DTEND:20110101T130000
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
$blob,
"20110101T170000Z/20110101T180000Z",
new \DateTimeZone('America/Toronto')
);
// All-day event
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar
DTSTART;VALUE=DATE:20110101
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
$blob,
"20110101T000000Z/20110102T000000Z"
);
// All-day event + reference timezone
$blob = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar
DTSTART;VALUE=DATE:20110101
END:VEVENT
END:VCALENDAR
ICS;
$tests[] = array(
$blob,
"20110101T050000Z/20110102T050000Z",
new \DateTimeZone('America/Toronto')
);
return $tests;
}
function testGenerator() {
/**
* @dataProvider getInput
*/
function testGenerator($input, $expected, $timeZone = null) {
$gen = new FreeBusyGenerator(
new \DateTime('20110101T110000Z', new \DateTimeZone('UTC')),
new \DateTime('20110103T110000Z', new \DateTimeZone('UTC')),
$this->getInput()
$input,
$timeZone
);
$result = $gen->getResult();
$expected = array(
'20110101T120000Z/20110101T130000Z',
'20110101T130000Z/20110101T140000Z',
'20110101T180000Z/20110101T190000Z',
'20110101T190000Z/20110101T200000Z',
'20110102T000000Z/20110103T000000Z',
'20110101T210000Z/20110101T220000Z',
'20110103T010000Z/20110103T020000Z',
'20110103T030000Z/20110103T040000Z',
'20110103T040000Z/20110103T050000Z',
'20110103T050000Z/20110103T060000Z',
$expected = (array)$expected;
'20110101T220000Z/20110101T230000Z',
'20110101T230000Z/20110102T000000Z',
);
$freebusy = $result->VFREEBUSY->select('FREEBUSY');
foreach($result->VFREEBUSY->FREEBUSY as $fb) {
foreach($freebusy as $fb) {
$this->assertContains((string)$fb, $expected, "$fb did not appear in our list of expected freebusy strings. This is concerning!");
@ -223,10 +338,10 @@ ICS;
unset($expected[$k]);
}
if (count($expected)>0) {
$this->fail('There were elements in the expected array that were not found in the output: ' . "\n" . print_r($expected,true) . "\n" . $result->serialize());
}
$this->assertTrue(
count($expected) === 0,
'There were elements in the expected array that were not found in the output: ' . "\n" . print_r($expected,true) . "\n" . $result->serialize()
);
}

@ -11,6 +11,7 @@ BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SUMMARY:B-day party
SEQUENCE:1
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
@ -25,6 +26,7 @@ BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SUMMARY:B-day party
SEQUENCE:1
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org
@ -54,6 +56,7 @@ BEGIN:VEVENT
UID:foobar
SEQUENCE:1
DTSTART:20140716T120000Z
SUMMARY:B-day party
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org
END:VEVENT
@ -79,6 +82,7 @@ SEQUENCE:1
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
DTSTART:20140724T120000Z
SUMMARY:Daily sprint
RRULE;FREQ=DAILY
END:VEVENT
END:VCALENDAR
@ -94,6 +98,7 @@ SEQUENCE:1
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;PARTSTAT=NEEDS-ACTION;CN=One:mailto:one@example.org
DTSTART:20140724T120000Z
SUMMARY:Daily sprint
END:VEVENT
BEGIN:VEVENT
UID:foobar
@ -159,6 +164,7 @@ BEGIN:VEVENT
UID:foobar
SEQUENCE:1
DTSTART:20140726T120000Z
SUMMARY:Daily sprint
RECURRENCE-ID:20140726T120000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org
@ -167,6 +173,7 @@ BEGIN:VEVENT
UID:foobar
SEQUENCE:1
DTSTART:20140724T120000Z
SUMMARY:Daily sprint
RECURRENCE-ID:20140724T120000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org
@ -175,6 +182,7 @@ BEGIN:VEVENT
UID:foobar
SEQUENCE:1
DTSTART:20140728T120000Z
SUMMARY:Daily sprint
RECURRENCE-ID:20140728T120000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;PARTSTAT=TENTATIVE;CN=One:mailto:one@example.org
@ -183,6 +191,7 @@ BEGIN:VEVENT
UID:foobar
SEQUENCE:1
DTSTART:20140729T120000Z
SUMMARY:Daily sprint
RECURRENCE-ID:20140729T120000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org
@ -191,6 +200,7 @@ BEGIN:VEVENT
UID:foobar
SEQUENCE:1
DTSTART:20140725T120000Z
SUMMARY:Daily sprint
RECURRENCE-ID:20140725T120000Z
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org
@ -618,6 +628,77 @@ ICS
}
/**
* @depends testCreateReplyByException
*/
function testCreateReplyByExceptionAllDay() {
$oldMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SUMMARY:Weekly meeting
UID:foobar
SEQUENCE:1
DTSTART;VALUE=DATE:20140811
RRULE:FREQ=WEEKLY
ORGANIZER:mailto:organizer@example.org
ATTENDEE:mailto:one@example.org
END:VEVENT
END:VCALENDAR
ICS;
$newMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SUMMARY:Weekly meeting
UID:foobar
SEQUENCE:1
DTSTART;VALUE=DATE:20140811
RRULE:FREQ=WEEKLY
ORGANIZER:mailto:organizer@example.org
ATTENDEE:mailto:one@example.org
EXDATE;VALUE=DATE:20140818
END:VEVENT
END:VCALENDAR
ICS;
$version = \Sabre\VObject\Version::VERSION;
$expected = array(
array(
'uid' => 'foobar',
'method' => 'REPLY',
'component' => 'VEVENT',
'sender' => 'mailto:one@example.org',
'senderName' => null,
'recipient' => 'mailto:organizer@example.org',
'recipientName' => null,
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:REPLY
BEGIN:VEVENT
UID:foobar
SEQUENCE:1
DTSTART;VALUE=DATE:20140818
SUMMARY:Weekly meeting
RECURRENCE-ID;VALUE=DATE:20140818
ORGANIZER:mailto:organizer@example.org
ATTENDEE;PARTSTAT=DECLINED:mailto:one@example.org
END:VEVENT
END:VCALENDAR
ICS
),
);
$result = $this->parse($oldMessage, $newMessage, $expected);
}
function testDeclined() {
$oldMessage = <<<ICS
@ -814,4 +895,68 @@ ICS;
}
function testAcceptedAllDay() {
$oldMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:1
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;CN=One:mailto:one@example.org
DTSTART;VALUE=DATE:20140716
END:VEVENT
END:VCALENDAR
ICS;
$newMessage = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:foobar
SEQUENCE:1
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org
DTSTART;VALUE=DATE:20140716
END:VEVENT
END:VCALENDAR
ICS;
$version = \Sabre\VObject\Version::VERSION;
$expected = array(
array(
'uid' => 'foobar',
'method' => 'REPLY',
'component' => 'VEVENT',
'sender' => 'mailto:one@example.org',
'senderName' => 'One',
'recipient' => 'mailto:strunk@example.org',
'recipientName' => 'Strunk',
'message' => <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject $version//EN
CALSCALE:GREGORIAN
METHOD:REPLY
BEGIN:VEVENT
UID:foobar
SEQUENCE:1
DTSTART;VALUE=DATE:20140716
ORGANIZER;CN=Strunk:mailto:strunk@example.org
ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org
END:VEVENT
END:VCALENDAR
ICS
),
);
$result = $this->parse($oldMessage, $newMessage, $expected);
}
}

@ -0,0 +1,21 @@
<?php
namespace Sabre\VObject\Parser;
/**
* Note that most MimeDir related tests can actually be found in the ReaderTest
* class one level up.
*/
class MimeDirTest extends \PHPUnit_Framework_TestCase {
/**
* @expectedException \Sabre\VObject\ParseException
*/
function testParseError() {
$mimeDir = new MimeDir();
$mimeDir->parse(fopen(__FILE__,'a'));
}
}

@ -2,9 +2,9 @@
namespace Sabre\VObject\Property\ICalendar;
use
Sabre\VObject\Component,
Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Component;
use Sabre\VObject\Component\VCalendar;
class DateTimeTest extends \PHPUnit_Framework_TestCase {
@ -121,7 +121,7 @@ class DateTimeTest extends \PHPUnit_Framework_TestCase {
$dt2->setTimeZone($tz);
$elem = $this->vcal->createProperty('DTSTART');
$elem->setValue(array($dt1,$dt2));
$elem->setValue(array($dt1, $dt2));
$this->assertEquals('19850704T013000,19850704T023000', (string)$elem);
$this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
@ -140,7 +140,7 @@ class DateTimeTest extends \PHPUnit_Framework_TestCase {
$dt2->setTimeZone($tz);
$elem = $this->vcal->createProperty('DTSTART');
$elem->setParts(array($dt1,$dt2));
$elem->setParts(array($dt1, $dt2));
$this->assertEquals('19850704T013000,19850704T023000', (string)$elem);
$this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
@ -155,7 +155,7 @@ class DateTimeTest extends \PHPUnit_Framework_TestCase {
$dt2 = '19850704T023000Z';
$elem = $this->vcal->createProperty('DTSTART');
$elem->setParts(array($dt1,$dt2));
$elem->setParts(array($dt1, $dt2));
$this->assertEquals('19850704T013000Z,19850704T023000Z', (string)$elem);
$this->assertNull($elem['VALUE']);
@ -197,8 +197,20 @@ class DateTimeTest extends \PHPUnit_Framework_TestCase {
}
function testGetDateTimeDateDATEReferenceTimeZone() {
$elem = $this->vcal->createProperty('DTSTART','19850704');
$tz = new \DateTimeZone('America/Toronto');
$dt = $elem->getDateTime($tz);
$dt->setTimeZone(new \DateTimeZone('UTC'));
$this->assertInstanceOf('DateTime', $dt);
$this->assertEquals('1985-07-04 04:00:00', $dt->format('Y-m-d H:i:s'));
}
function testGetDateTimeDateLOCAL() {
function testGetDateTimeDateFloating() {
$elem = $this->vcal->createProperty('DTSTART','19850704T013000');
$dt = $elem->getDateTime();
@ -208,6 +220,19 @@ class DateTimeTest extends \PHPUnit_Framework_TestCase {
}
function testGetDateTimeDateFloatingReferenceTimeZone() {
$elem = $this->vcal->createProperty('DTSTART','19850704T013000');
$tz = new \DateTimeZone('America/Toronto');
$dt = $elem->getDateTime($tz);
$dt->setTimeZone(new \DateTimeZone('UTC'));
$this->assertInstanceOf('DateTime', $dt);
$this->assertEquals('1985-07-04 05:30:00', $dt->format('Y-m-d H:i:s'));
}
function testGetDateTimeDateUTC() {
$elem = $this->vcal->createProperty('DTSTART','19850704T013000Z');

@ -45,7 +45,6 @@ BEGIN:VEVENT
UID:foo
DTSTART:20130711T050000Z
DTEND:20130711T053000Z
RECURRENCE-ID:20130711T050000Z
END:VEVENT
BEGIN:VEVENT
UID:foo

@ -45,7 +45,6 @@ UID:foo
DTSTART:20130727T120000Z
DURATION:PT1H
SUMMARY:A
RECURRENCE-ID:20130727T120000Z
END:VEVENT
BEGIN:VEVENT
RECURRENCE-ID:20130728T120000Z

@ -0,0 +1,50 @@
<?php
namespace Sabre\VObject;
class TestCase extends \PHPUnit_Framework_TestCase {
/**
* This method tests wether two vcards or icalendar objects are
* semantically identical.
*
* It supports objects being supplied as strings, streams or
* Sabre\VObject\Component instances.
*
* PRODID is removed from both objects as this is often variable.
*
* @param resource|string|Component $expected
* @param resource|string|Component $actual
* @param string $message
*/
function assertVObjEquals($expected, $actual, $message = '') {
$self = $this;
$getObj = function($input) use ($self) {
if (is_resource($input)) {
$input = stream_get_contents($input);
}
if (is_string($input)) {
$input = Reader::read($input);
}
if (!$input instanceof Component) {
$this->fail('Input must be a string, stream or VObject component');
}
unset($input->PRODID);
return $input;
};
$expected = $getObj($expected);
$actual = $getObj($actual);
$this->assertEquals(
$expected->serialize(),
$actual->serialize(),
$message
);
}
}

@ -2,12 +2,10 @@
namespace Sabre\VObject;
class VCardConverterTest extends \PHPUnit_Framework_TestCase {
class VCardConverterTest extends TestCase {
function testConvert30to40() {
$version = Version::VERSION;
$input = <<<IN
BEGIN:VCARD
VERSION:3.0
@ -22,13 +20,11 @@ PHOTO;X-PARAM=FOO;ENCODING=b;TYPE=PNG:Zm9v
PHOTO;VALUE=URI:http://example.org/foo.png
X-ABShowAs:COMPANY
END:VCARD
IN;
$output = <<<OUT
BEGIN:VCARD
VERSION:4.0
PRODID:-//Sabre//Sabre VObject {$version}//EN
FN:Steve
TEL;PREF=1;TYPE=HOME:+1 555 666 777
ITEM1.TEL:+1 444 555 666
@ -37,29 +33,25 @@ PHOTO;TYPE=HOME:data:image/jpeg;base64,Zm9v
PHOTO:data:image/gif;base64,Zm9v
PHOTO;X-PARAM=FOO:data:image/png;base64,Zm9v
PHOTO:http://example.org/foo.png
KIND:org
KIND:ORG
END:VCARD
OUT;
$vcard = \Sabre\VObject\Reader::read($input);
$vcard = $vcard->convert(\Sabre\VObject\Document::VCARD40);
$this->assertEquals(
$this->assertVObjEquals(
$output,
str_replace("\r", "", $vcard->serialize())
$vcard
);
}
function testConvert40to40() {
$version = Version::VERSION;
$input = <<<IN
BEGIN:VCARD
VERSION:4.0
PRODID:-//Sabre//Sabre VObject {$version}//EN
FN:Steve
TEL;PREF=1;TYPE=HOME:+1 555 666 777
PHOTO:data:image/jpeg;base64,Zm9v
@ -73,7 +65,6 @@ IN;
$output = <<<OUT
BEGIN:VCARD
VERSION:4.0
PRODID:-//Sabre//Sabre VObject {$version}//EN
FN:Steve
TEL;PREF=1;TYPE=HOME:+1 555 666 777
PHOTO:data:image/jpeg;base64,Zm9v
@ -87,17 +78,15 @@ OUT;
$vcard = \Sabre\VObject\Reader::read($input);
$vcard = $vcard->convert(\Sabre\VObject\Document::VCARD40);
$this->assertEquals(
$this->assertVObjEquals(
$output,
str_replace("\r", "", $vcard->serialize())
$vcard
);
}
function testConvert21to40() {
$version = Version::VERSION;
$input = <<<IN
BEGIN:VCARD
VERSION:2.1
@ -115,7 +104,6 @@ IN;
$output = <<<OUT
BEGIN:VCARD
VERSION:4.0
PRODID:-//Sabre//Sabre VObject {$version}//EN
N:Family;Johnson;;;
FN:Johnson Family
TEL;TYPE=HOME,VOICE:555-12345-345
@ -129,17 +117,15 @@ OUT;
$vcard = \Sabre\VObject\Reader::read($input);
$vcard = $vcard->convert(\Sabre\VObject\Document::VCARD40);
$this->assertEquals(
$this->assertVObjEquals(
$output,
str_replace("\r", "", $vcard->serialize())
$vcard
);
}
function testConvert30to30() {
$version = Version::VERSION;
$input = <<<IN
BEGIN:VCARD
VERSION:3.0
@ -171,17 +157,15 @@ OUT;
$vcard = \Sabre\VObject\Reader::read($input);
$vcard = $vcard->convert(\Sabre\VObject\Document::VCARD30);
$this->assertEquals(
$this->assertVObjEquals(
$output,
str_replace("\r", "", $vcard->serialize())
$vcard
);
}
function testConvert40to30() {
$version = Version::VERSION;
$input = <<<IN
BEGIN:VCARD
VERSION:4.0
@ -192,7 +176,7 @@ PHOTO:data:image/jpeg;base64,Zm9v
PHOTO:data:image/gif,foo
PHOTO;X-PARAM=FOO:data:image/png;base64,Zm9v
PHOTO:http://example.org/foo.png
KIND:org
KIND:ORG
END:VCARD
IN;
@ -200,7 +184,6 @@ IN;
$output = <<<OUT
BEGIN:VCARD
VERSION:3.0
PRODID:-//Sabre//Sabre VObject {$version}//EN
FN:Steve
TEL;TYPE=PREF,HOME:+1 555 666 777
PHOTO;ENCODING=b;TYPE=JPEG:Zm9v
@ -215,17 +198,15 @@ OUT;
$vcard = \Sabre\VObject\Reader::read($input);
$vcard = $vcard->convert(\Sabre\VObject\Document::VCARD30);
$this->assertEquals(
$this->assertVObjEquals(
$output,
str_replace("\r", "", $vcard->serialize())
$vcard
);
}
function testConvertGroupCard() {
$version = Version::VERSION;
$input = <<<IN
BEGIN:VCARD
VERSION:3.0
@ -238,8 +219,7 @@ IN;
$output = <<<OUT
BEGIN:VCARD
VERSION:4.0
PRODID:-//Sabre//Sabre VObject {$version}//EN
KIND:group
KIND:GROUP
END:VCARD
OUT;
@ -247,16 +227,15 @@ OUT;
$vcard = \Sabre\VObject\Reader::read($input);
$vcard = $vcard->convert(\Sabre\VObject\Document::VCARD40);
$this->assertEquals(
$this->assertVObjEquals(
$output,
str_replace("\r", "", $vcard->serialize())
$vcard
);
$input = $output;
$output = <<<OUT
BEGIN:VCARD
VERSION:3.0
PRODID:-//Sabre//Sabre VObject {$version}//EN
X-ADDRESSBOOKSERVER-KIND:GROUP
END:VCARD
@ -265,17 +244,15 @@ OUT;
$vcard = \Sabre\VObject\Reader::read($input);
$vcard = $vcard->convert(\Sabre\VObject\Document::VCARD30);
$this->assertEquals(
$this->assertVObjEquals(
$output,
str_replace("\r", "", $vcard->serialize())
$vcard
);
}
function testBDAYConversion() {
$version = Version::VERSION;
$input = <<<IN
BEGIN:VCARD
VERSION:3.0
@ -288,7 +265,6 @@ IN;
$output = <<<OUT
BEGIN:VCARD
VERSION:4.0
PRODID:-//Sabre//Sabre VObject {$version}//EN
BDAY:--04-16
END:VCARD
@ -297,16 +273,15 @@ OUT;
$vcard = \Sabre\VObject\Reader::read($input);
$vcard = $vcard->convert(\Sabre\VObject\Document::VCARD40);
$this->assertEquals(
$this->assertVObjEquals(
$output,
str_replace("\r", "", $vcard->serialize())
$vcard
);
$input = $output;
$output = <<<OUT
BEGIN:VCARD
VERSION:3.0
PRODID:-//Sabre//Sabre VObject {$version}//EN
BDAY;X-APPLE-OMIT-YEAR=1604:1604-04-16
END:VCARD
@ -315,9 +290,9 @@ OUT;
$vcard = \Sabre\VObject\Reader::read($input);
$vcard = $vcard->convert(\Sabre\VObject\Document::VCARD30);
$this->assertEquals(
$this->assertVObjEquals(
$output,
str_replace("\r", "", $vcard->serialize())
$vcard
);
}
@ -344,8 +319,8 @@ END:VCARD
IN;
$vcard = \Sabre\VObject\Reader::read($input);
$vcard->convert(\Sabre\VObject\Document::VCARD40);
$vcard = Reader::read($input);
$vcard->convert(Document::VCARD40);
}
@ -362,15 +337,13 @@ END:VCARD
IN;
$vcard = \Sabre\VObject\Reader::read($input);
$vcard->convert(\Sabre\VObject\Document::VCARD21);
$vcard = Reader::read($input);
$vcard->convert(Document::VCARD21);
}
function testConvertIndividualCard() {
$version = Version::VERSION;
$input = <<<IN
BEGIN:VCARD
VERSION:4.0
@ -383,34 +356,134 @@ IN;
$output = <<<OUT
BEGIN:VCARD
VERSION:3.0
PRODID:-//Sabre//Sabre VObject {$version}//EN
END:VCARD
OUT;
$vcard = \Sabre\VObject\Reader::read($input);
$vcard = $vcard->convert(\Sabre\VObject\Document::VCARD30);
$vcard = Reader::read($input);
$vcard = $vcard->convert(Document::VCARD30);
$this->assertEquals(
$this->assertVObjEquals(
$output,
str_replace("\r", "", $vcard->serialize())
$vcard
);
$input = $output;
$output = <<<OUT
BEGIN:VCARD
VERSION:4.0
PRODID:-//Sabre//Sabre VObject {$version}//EN
END:VCARD
OUT;
$vcard = \Sabre\VObject\Reader::read($input);
$vcard = $vcard->convert(\Sabre\VObject\Document::VCARD40);
$vcard = Reader::read($input);
$vcard = $vcard->convert(Document::VCARD40);
$this->assertVObjEquals(
$output,
$vcard
);
}
function testAnniversary() {
$input = <<<IN
BEGIN:VCARD
VERSION:4.0
ITEM1.ANNIVERSARY:20081210
END:VCARD
IN;
$output = <<<'OUT'
BEGIN:VCARD
VERSION:3.0
ITEM1.X-ABDATE;VALUE=DATE-AND-OR-TIME:20081210
ITEM1.X-ABLABEL:_$!<Anniversary>!$_
ITEM1.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20081210
END:VCARD
OUT;
$vcard = Reader::read($input);
$vcard = $vcard->convert(Document::VCARD30);
$this->assertVObjEquals(
$output,
$vcard
);
// Swapping input and output
list(
$input,
$output
) = array(
$output,
$input
);
$vcard = Reader::read($input);
$vcard = $vcard->convert(Document::VCARD40);
$this->assertVObjEquals(
$output,
$vcard
);
}
function testMultipleAnniversaries() {
$input = <<<IN
BEGIN:VCARD
VERSION:4.0
ITEM1.ANNIVERSARY:20081210
ITEM2.ANNIVERSARY:20091210
ITEM3.ANNIVERSARY:20101210
END:VCARD
IN;
$output = <<<'OUT'
BEGIN:VCARD
VERSION:3.0
ITEM1.X-ABDATE;VALUE=DATE-AND-OR-TIME:20081210
ITEM1.X-ABLABEL:_$!<Anniversary>!$_
ITEM1.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20081210
ITEM2.X-ABDATE;VALUE=DATE-AND-OR-TIME:20091210
ITEM2.X-ABLABEL:_$!<Anniversary>!$_
ITEM2.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20091210
ITEM3.X-ABDATE;VALUE=DATE-AND-OR-TIME:20101210
ITEM3.X-ABLABEL:_$!<Anniversary>!$_
ITEM3.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20101210
END:VCARD
OUT;
$vcard = Reader::read($input);
$vcard = $vcard->convert(Document::VCARD30);
$this->assertVObjEquals(
$output,
$vcard
);
// Swapping input and output
list(
$input,
$output
) = array(
$output,
$input
);
$vcard = Reader::read($input);
$vcard = $vcard->convert(Document::VCARD40);
$this->assertEquals(
$this->assertVObjEquals(
$output,
str_replace("\r", "", $vcard->serialize())
$vcard
);
}

@ -13,6 +13,8 @@ namespace Symfony\Component\Console;
use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Helper\DebugFormatterHelper;
use Symfony\Component\Console\Helper\ProcessHelper;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\ArgvInput;
@ -249,24 +251,7 @@ class Application
*/
public function getHelp()
{
$messages = array(
$this->getLongVersion(),
'',
'<comment>Usage:</comment>',
' [options] command [arguments]',
'',
'<comment>Options:</comment>',
);
foreach ($this->getDefinition()->getOptions() as $option) {
$messages[] = sprintf(' %-29s %s %s',
'<info>--'.$option->getName().'</info>',
$option->getShortcut() ? '<info>-'.$option->getShortcut().'</info>' : ' ',
$option->getDescription()
);
}
return implode(PHP_EOL, $messages);
return $this->getLongVersion();
}
/**
@ -723,9 +708,9 @@ class Application
$trace = $e->getTrace();
array_unshift($trace, array(
'function' => '',
'file' => $e->getFile() != null ? $e->getFile() : 'n/a',
'line' => $e->getLine() != null ? $e->getLine() : 'n/a',
'args' => array(),
'file' => $e->getFile() != null ? $e->getFile() : 'n/a',
'line' => $e->getLine() != null ? $e->getLine() : 'n/a',
'args' => array(),
));
for ($i = 0, $count = count($trace); $i < $count; $i++) {
@ -843,8 +828,8 @@ class Application
if (true === $input->hasParameterOption(array('--no-interaction', '-n'))) {
$input->setInteractive(false);
} elseif (function_exists('posix_isatty') && $this->getHelperSet()->has('dialog')) {
$inputStream = $this->getHelperSet()->get('dialog')->getInputStream();
} elseif (function_exists('posix_isatty') && $this->getHelperSet()->has('question')) {
$inputStream = $this->getHelperSet()->get('question')->getInputStream();
if (!@posix_isatty($inputStream)) {
$input->setInteractive(false);
}
@ -892,16 +877,20 @@ class Application
$event = new ConsoleCommandEvent($command, $input, $output);
$this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event);
try {
$exitCode = $command->run($input, $output);
} catch (\Exception $e) {
$event = new ConsoleTerminateEvent($command, $input, $output, $e->getCode());
$this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);
if ($event->commandShouldRun()) {
try {
$exitCode = $command->run($input, $output);
} catch (\Exception $e) {
$event = new ConsoleTerminateEvent($command, $input, $output, $e->getCode());
$this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);
$event = new ConsoleExceptionEvent($command, $input, $output, $e, $event->getExitCode());
$this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event);
$event = new ConsoleExceptionEvent($command, $input, $output, $e, $event->getExitCode());
$this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event);
throw $event->getException();
throw $event->getException();
}
} else {
$exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
}
$event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
@ -964,6 +953,8 @@ class Application
new DialogHelper(),
new ProgressHelper(),
new TableHelper(),
new DebugFormatterHelper(),
new ProcessHelper(),
new QuestionHelper(),
));
}

@ -1,6 +1,12 @@
CHANGELOG
=========
2.6.0
-----
* added a Process helper
* added a DebugFormatter helper
2.5.0
-----

@ -65,7 +65,7 @@ class Command
$this->configure();
if (!$this->name) {
throw new \LogicException('The command name cannot be empty.');
throw new \LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this)));
}
}
@ -159,7 +159,7 @@ class Command
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
*
* @return null|int null or 0 if everything went fine, or an error code
* @return null|int null or 0 if everything went fine, or an error code
*
* @throws \LogicException When this abstract method is not implemented
* @see setCode()
@ -202,7 +202,7 @@ class Command
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
*
* @return int The command exit code
* @return int The command exit code
*
* @throws \Exception
*
@ -287,7 +287,7 @@ class Command
*
* This method is not part of public API and should not be used directly.
*
* @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments
* @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments
*/
public function mergeApplicationDefinition($mergeArgs = true)
{
@ -361,10 +361,10 @@ class Command
/**
* Adds an argument.
*
* @param string $name The argument name
* @param int $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
* @param string $description A description text
* @param mixed $default The default value (for InputArgument::OPTIONAL mode only)
* @param string $name The argument name
* @param int $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
* @param string $description A description text
* @param mixed $default The default value (for InputArgument::OPTIONAL mode only)
*
* @return Command The current instance
*
@ -380,11 +380,11 @@ class Command
/**
* Adds an option.
*
* @param string $name The option name
* @param string $shortcut The shortcut (can be null)
* @param int $mode The option mode: One of the InputOption::VALUE_* constants
* @param string $description A description text
* @param mixed $default The default value (must be null for InputOption::VALUE_REQUIRED or InputOption::VALUE_NONE)
* @param string $name The option name
* @param string $shortcut The shortcut (can be null)
* @param int $mode The option mode: One of the InputOption::VALUE_* constants
* @param string $description A description text
* @param mixed $default The default value (must be null for InputOption::VALUE_REQUIRED or InputOption::VALUE_NONE)
*
* @return Command The current instance
*
@ -513,7 +513,7 @@ class Command
* Returns the processed help for the command replacing the %command.name% and
* %command.full_name% patterns with the real values dynamically.
*
* @return string The processed help for the command
* @return string The processed help for the command
*/
public function getProcessedHelp()
{
@ -618,7 +618,7 @@ class Command
/**
* Returns an XML representation of the command.
*
* @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 command
*

@ -83,7 +83,7 @@ EOF
$helper = new DescriptorHelper();
$helper->describe($output, $this->command, array(
'format' => $input->getOption('format'),
'raw' => $input->getOption('raw'),
'raw' => $input->getOption('raw'),
));
$this->command = null;

@ -74,8 +74,8 @@ EOF
$helper = new DescriptorHelper();
$helper->describe($output, $this->getApplication(), array(
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
'namespace' => $input->getArgument('namespace'),
));
}

@ -26,6 +26,8 @@ final class ConsoleEvents
* The event listener method receives a Symfony\Component\Console\Event\ConsoleCommandEvent
* instance.
*
* @Event
*
* @var string
*/
const COMMAND = 'console.command';
@ -37,6 +39,8 @@ final class ConsoleEvents
* The event listener method receives a Symfony\Component\Console\Event\ConsoleTerminateEvent
* instance.
*
* @Event
*
* @var string
*/
const TERMINATE = 'console.terminate';
@ -49,6 +53,8 @@ final class ConsoleEvents
* a Symfony\Component\Console\Event\ConsoleExceptionEvent
* instance.
*
* @Event
*
* @var string
*/
const EXCEPTION = 'console.exception';

@ -97,11 +97,11 @@ class JsonDescriptor extends Descriptor
private function getInputArgumentData(InputArgument $argument)
{
return array(
'name' => $argument->getName(),
'name' => $argument->getName(),
'is_required' => $argument->isRequired(),
'is_array' => $argument->isArray(),
'is_array' => $argument->isArray(),
'description' => $argument->getDescription(),
'default' => $argument->getDefault(),
'default' => $argument->getDefault(),
);
}
@ -113,13 +113,13 @@ class JsonDescriptor extends Descriptor
private function getInputOptionData(InputOption $option)
{
return array(
'name' => '--'.$option->getName(),
'shortcut' => $option->getShortcut() ? '-'.implode('|-', explode('|', $option->getShortcut())) : '',
'accept_value' => $option->acceptValue(),
'name' => '--'.$option->getName(),
'shortcut' => $option->getShortcut() ? '-'.implode('|-', explode('|', $option->getShortcut())) : '',
'accept_value' => $option->acceptValue(),
'is_value_required' => $option->isValueRequired(),
'is_multiple' => $option->isArray(),
'description' => $option->getDescription(),
'default' => $option->getDefault(),
'is_multiple' => $option->isArray(),
'description' => $option->getDescription(),
'default' => $option->getDefault(),
);
}
@ -154,12 +154,12 @@ class JsonDescriptor extends Descriptor
$command->mergeApplicationDefinition(false);
return array(
'name' => $command->getName(),
'usage' => $command->getSynopsis(),
'name' => $command->getName(),
'usage' => $command->getSynopsis(),
'description' => $command->getDescription(),
'help' => $command->getProcessedHelp(),
'aliases' => $command->getAliases(),
'definition' => $this->getInputDefinitionData($command->getNativeDefinition()),
'help' => $command->getProcessedHelp(),
'aliases' => $command->getAliases(),
'definition' => $this->getInputDefinitionData($command->getNativeDefinition()),
);
}
}

@ -128,7 +128,7 @@ class MarkdownDescriptor extends Descriptor
$this->write("\n\n");
$this->write(implode("\n", array_map(function ($commandName) {
return '* '.$commandName;
} , $namespace['commands'])));
}, $namespace['commands'])));
}
foreach ($description->getCommands() as $command) {

@ -157,10 +157,34 @@ class TextDescriptor extends Descriptor
$this->writeText("\n");
}
} else {
$width = $this->getColumnWidth($description->getCommands());
if ('' != $help = $application->getHelp()) {
$this->writeText("$help\n\n", $options);
}
$this->writeText("<comment>Usage:</comment>\n", $options);
$this->writeText(" [options] command [arguments]\n\n", $options);
$this->writeText('<comment>Options:</comment>', $options);
$inputOptions = $application->getDefinition()->getOptions();
$width = 0;
foreach ($inputOptions as $option) {
$nameLength = strlen($option->getName()) + 2;
if ($option->getShortcut()) {
$nameLength += strlen($option->getShortcut()) + 3;
}
$width = max($width, $nameLength);
}
++$width;
foreach ($inputOptions as $option) {
$this->writeText("\n", $options);
$this->describeInputOption($option, array_merge($options, array('name_width' => $width)));
}
$this->writeText("\n\n", $options);
$this->writeText($application->getHelp(), $options);
$this->writeText("\n\n");
$width = $this->getColumnWidth($description->getCommands());
if ($describedNamespace) {
$this->writeText(sprintf("<comment>Available commands for the \"%s\" namespace:</comment>", $describedNamespace), $options);
@ -177,7 +201,7 @@ class TextDescriptor extends Descriptor
foreach ($namespace['commands'] as $name) {
$this->writeText("\n");
$this->writeText(sprintf(" <info>%-${width}s</info> %s", $name, $description->getCommand($name)->getDescription()), $options);
$this->writeText(sprintf(" <info>%-${width}s</info> %s", $name, $description->getCommand($name)->getDescription()), $options);
}
}
@ -205,7 +229,7 @@ class TextDescriptor extends Descriptor
*/
private function formatDefaultValue($default)
{
if (version_compare(PHP_VERSION, '5.4', '<')) {
if (PHP_VERSION_ID < 50400) {
return str_replace('\/', '/', json_encode($default));
}

@ -12,10 +12,51 @@
namespace Symfony\Component\Console\Event;
/**
* Allows to do things before the command is executed.
* Allows to do things before the command is executed, like skipping the command or changing the input.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ConsoleCommandEvent extends ConsoleEvent
{
/**
* The return code for skipped commands, this will also be passed into the terminate event
*/
const RETURN_CODE_DISABLED = 113;
/**
* Indicates if the command should be run or skipped
*
* @var bool
*/
private $commandShouldRun = true;
/**
* Disables the command, so it won't be run
*
* @return bool
*/
public function disableCommand()
{
return $this->commandShouldRun = false;
}
/**
* Enables the command
*
* @return bool
*/
public function enableCommand()
{
return $this->commandShouldRun = true;
}
/**
* Returns true if the command is runnable, false otherwise
*
* @return bool
*/
public function commandShouldRun()
{
return $this->commandShouldRun;
}
}

@ -58,7 +58,7 @@ class ConsoleExceptionEvent extends ConsoleEvent
/**
* Gets the exit code.
*
* @return int The command exit code
* @return int The command exit code
*/
public function getExitCode()
{

@ -39,7 +39,7 @@ class ConsoleTerminateEvent extends ConsoleEvent
/**
* Sets the exit code.
*
* @param int $exitCode The command exit code
* @param int $exitCode The command exit code
*/
public function setExitCode($exitCode)
{
@ -49,7 +49,7 @@ class ConsoleTerminateEvent extends ConsoleEvent
/**
* Gets the exit code.
*
* @return int The command exit code
* @return int The command exit code
*/
public function getExitCode()
{

@ -39,7 +39,7 @@ class OutputFormatter implements OutputFormatterInterface
/**
* Initializes console output formatter.
*
* @param bool $decorated Whether this formatter should actually decorate strings
* @param bool $decorated Whether this formatter should actually decorate strings
* @param OutputFormatterStyleInterface[] $styles Array of "name => FormatterStyle" instances
*
* @api
@ -63,7 +63,7 @@ class OutputFormatter implements OutputFormatterInterface
/**
* Sets the decorated flag.
*
* @param bool $decorated Whether to decorate the messages or not
* @param bool $decorated Whether to decorate the messages or not
*
* @api
*/
@ -75,7 +75,7 @@ class OutputFormatter implements OutputFormatterInterface
/**
* Gets the decorated flag.
*
* @return bool true if the output will decorate messages, false otherwise
* @return bool true if the output will decorate messages, false otherwise
*
* @api
*/
@ -194,7 +194,7 @@ class OutputFormatter implements OutputFormatterInterface
*
* @param string $string
*
* @return OutputFormatterStyle|bool false if string is not format string
* @return OutputFormatterStyle|bool false if string is not format string
*/
private function createStyleFromString($string)
{

@ -23,7 +23,7 @@ interface OutputFormatterInterface
/**
* Sets the decorated flag.
*
* @param bool $decorated Whether to decorate the messages or not
* @param bool $decorated Whether to decorate the messages or not
*
* @api
*/
@ -32,7 +32,7 @@ interface OutputFormatterInterface
/**
* Gets the decorated flag.
*
* @return bool true if the output will decorate messages, false otherwise
* @return bool true if the output will decorate messages, false otherwise
*
* @api
*/

@ -21,31 +21,31 @@ namespace Symfony\Component\Console\Formatter;
class OutputFormatterStyle implements OutputFormatterStyleInterface
{
private static $availableForegroundColors = array(
'black' => array('set' => 30, 'unset' => 39),
'red' => array('set' => 31, 'unset' => 39),
'green' => array('set' => 32, 'unset' => 39),
'yellow' => array('set' => 33, 'unset' => 39),
'blue' => array('set' => 34, 'unset' => 39),
'magenta' => array('set' => 35, 'unset' => 39),
'cyan' => array('set' => 36, 'unset' => 39),
'white' => array('set' => 37, 'unset' => 39),
'black' => array('set' => 30, 'unset' => 39),
'red' => array('set' => 31, 'unset' => 39),
'green' => array('set' => 32, 'unset' => 39),
'yellow' => array('set' => 33, 'unset' => 39),
'blue' => array('set' => 34, 'unset' => 39),
'magenta' => array('set' => 35, 'unset' => 39),
'cyan' => array('set' => 36, 'unset' => 39),
'white' => array('set' => 37, 'unset' => 39),
);
private static $availableBackgroundColors = array(
'black' => array('set' => 40, 'unset' => 49),
'red' => array('set' => 41, 'unset' => 49),
'green' => array('set' => 42, 'unset' => 49),
'yellow' => array('set' => 43, 'unset' => 49),
'blue' => array('set' => 44, 'unset' => 49),
'magenta' => array('set' => 45, 'unset' => 49),
'cyan' => array('set' => 46, 'unset' => 49),
'white' => array('set' => 47, 'unset' => 49),
'black' => array('set' => 40, 'unset' => 49),
'red' => array('set' => 41, 'unset' => 49),
'green' => array('set' => 42, 'unset' => 49),
'yellow' => array('set' => 43, 'unset' => 49),
'blue' => array('set' => 44, 'unset' => 49),
'magenta' => array('set' => 45, 'unset' => 49),
'cyan' => array('set' => 46, 'unset' => 49),
'white' => array('set' => 47, 'unset' => 49),
);
private static $availableOptions = array(
'bold' => array('set' => 1, 'unset' => 22),
'underscore' => array('set' => 4, 'unset' => 24),
'blink' => array('set' => 5, 'unset' => 25),
'reverse' => array('set' => 7, 'unset' => 27),
'conceal' => array('set' => 8, 'unset' => 28),
'bold' => array('set' => 1, 'unset' => 22),
'underscore' => array('set' => 4, 'unset' => 24),
'blink' => array('set' => 5, 'unset' => 25),
'reverse' => array('set' => 7, 'unset' => 27),
'conceal' => array('set' => 8, 'unset' => 28),
);
private $foreground;

@ -62,7 +62,7 @@ class OutputFormatterStyleStack
*
* @return OutputFormatterStyleInterface
*
* @throws \InvalidArgumentException When style tags incorrectly nested
* @throws \InvalidArgumentException When style tags incorrectly nested
*/
public function pop(OutputFormatterStyleInterface $style = null)
{

@ -0,0 +1,127 @@
<?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\Component\Console\Helper;
/**
* Helps outputting debug information when running an external program from a command.
*
* An external program can be a Process, an HTTP request, or anything else.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DebugFormatterHelper extends Helper
{
private $colors = array('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white');
private $started = array();
private $count = -1;
/**
* Starts a debug formatting session
*
* @param string $id The id of the formatting session
* @param string $message The message to display
* @param string $prefix The prefix to use
*
* @return string
*/
public function start($id, $message, $prefix = 'RUN')
{
$this->started[$id] = array('border' => ++$this->count % count($this->colors));
return sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message);
}
/**
* Adds progress to a formatting session
*
* @param string $id The id of the formatting session
* @param string $buffer The message to display
* @param bool $error Whether to consider the buffer as error
* @param string $prefix The prefix for output
* @param string $errorPrefix The prefix for error output
*
* @return string
*/
public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPrefix = 'ERR')
{
$message = '';
if ($error) {
if (isset($this->started[$id]['out'])) {
$message .= "\n";
unset($this->started[$id]['out']);
}
if (!isset($this->started[$id]['err'])) {
$message .= sprintf("%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix);
$this->started[$id]['err'] = true;
}
$message .= str_replace("\n", sprintf("\n%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix), $buffer);
} else {
if (isset($this->started[$id]['err'])) {
$message .= "\n";
unset($this->started[$id]['err']);
}
if (!isset($this->started[$id]['out'])) {
$message .= sprintf("%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix);
$this->started[$id]['out'] = true;
}
$message .= str_replace("\n", sprintf("\n%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix), $buffer);
}
return $message;
}
/**
* Stops a formatting session
*
* @param string $id The id of the formatting session
* @param string $message The message to display
* @param bool $successful Whether to consider the result as success
* @param string $prefix The prefix for the end output
*
* @return string
*/
public function stop($id, $message, $successful, $prefix = 'RES')
{
$trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : '';
if ($successful) {
return sprintf("%s%s<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
}
$message = sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
unset($this->started[$id]['out'], $this->started[$id]['err']);
return $message;
}
/**
* @param string $id The id of the formatting session
*
* @return string
*/
private function getBorder($id)
{
return sprintf('<bg=%s> </>', $this->colors[$this->started[$id]['border']]);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'debug_formatter';
}
}

@ -59,8 +59,8 @@ class DescriptorHelper extends Helper
public function describe(OutputInterface $output, $object, array $options = array())
{
$options = array_merge(array(
'raw_text' => false,
'format' => 'txt',
'raw_text' => false,
'format' => 'txt',
), $options);
if (!isset($this->descriptors[$options['format']])) {

@ -35,11 +35,11 @@ class DialogHelper extends InputAwareHelper
* @param string|array $question The question to ask
* @param array $choices List of choices to pick from
* @param bool|string $default The default answer if the user enters nothing
* @param bool|int $attempts Max number of times to ask before giving up (false by default, which means infinite)
* @param bool|int $attempts Max number of times to ask before giving up (false by default, which means infinite)
* @param string $errorMessage Message which will be shown if invalid value from choice list would be picked
* @param bool $multiselect Select more than one value separated by comma
*
* @return int|string|array The selected value or values (the key of the choices array)
* @return int|string|array The selected value or values (the key of the choices array)
*
* @throws \InvalidArgumentException
*/
@ -233,7 +233,7 @@ class DialogHelper extends InputAwareHelper
* @param string|array $question The question to ask
* @param bool $default The default answer if the user enters nothing
*
* @return bool true if the user has confirmed, false otherwise
* @return bool true if the user has confirmed, false otherwise
*/
public function askConfirmation(OutputInterface $output, $question, $default = true)
{
@ -256,7 +256,7 @@ class DialogHelper extends InputAwareHelper
* @param string|array $question The question
* @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
*
* @return string The answer
* @return string The answer
*
* @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden
*/
@ -361,7 +361,7 @@ class DialogHelper extends InputAwareHelper
* @param int|false $attempts Max number of times to ask before giving up (false by default, which means infinite)
* @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
*
* @return string The response
* @return string The response
*
* @throws \Exception When any of the validators return an error
* @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden
@ -411,7 +411,7 @@ class DialogHelper extends InputAwareHelper
/**
* Return a valid Unix shell
*
* @return string|bool The valid shell name, false in case no valid shell is found
* @return string|bool The valid shell name, false in case no valid shell is found
*/
private function getShell()
{
@ -449,12 +449,12 @@ class DialogHelper extends InputAwareHelper
/**
* Validate an attempt
*
* @param callable $interviewer A callable that will ask for a question and return the result
* @param OutputInterface $output An Output instance
* @param callable $validator A PHP callback
* @param int|false $attempts Max number of times to ask before giving up ; false will ask infinitely
* @param callable $interviewer A callable that will ask for a question and return the result
* @param OutputInterface $output An Output instance
* @param callable $validator A PHP callback
* @param int|false $attempts Max number of times to ask before giving up ; false will ask infinitely
*
* @return string The validated response
* @return string The validated response
*
* @throws \Exception In case the max number of attempts has been reached and no valid response has been given
*/

@ -47,7 +47,7 @@ abstract class Helper implements HelperInterface
*
* @param string $string The string to check its length
*
* @return int The length of the string
* @return int The length of the string
*/
public static function strlen($string)
{

@ -56,7 +56,7 @@ class HelperSet implements \IteratorAggregate
*
* @param string $name The helper name
*
* @return bool true if the helper is defined, false otherwise
* @return bool true if the helper is defined, false otherwise
*/
public function has($name)
{

@ -0,0 +1,142 @@
<?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\Component\Console\Helper;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\ProcessBuilder;
/**
* The ProcessHelper class provides helpers to run external processes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ProcessHelper extends Helper
{
/**
* Runs an external process.
*
* @param OutputInterface $output An OutputInterface instance
* @param string|array|Process $cmd An instance of Process or an array of arguments to escape and run or a command to run
* @param string|null $error An error message that must be displayed if something went wrong
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
* @param int $verbosity The threshold for verbosity
*
* @return Process The process that ran
*/
public function run(OutputInterface $output, $cmd, $error = null, $callback = null, $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE)
{
$formatter = $this->getHelperSet()->get('debug_formatter');
if (is_array($cmd)) {
$process = ProcessBuilder::create($cmd)->getProcess();
} elseif ($cmd instanceof Process) {
$process = $cmd;
} else {
$process = new Process($cmd);
}
if ($verbosity <= $output->getVerbosity()) {
$output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine())));
}
if ($output->isDebug()) {
$callback = $this->wrapCallback($output, $process, $callback);
}
$process->run($callback);
if ($verbosity <= $output->getVerbosity()) {
$message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode());
$output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful()));
}
if (!$process->isSuccessful() && null !== $error) {
$output->writeln(sprintf('<error>%s</error>', $this->escapeString($error)));
}
return $process;
}
/**
* Runs the process.
*
* This is identical to run() except that an exception is thrown if the process
* exits with a non-zero exit code.
*
* @param OutputInterface $output An OutputInterface instance
* @param string|Process $cmd An instance of Process or a command to run
* @param string|null $error An error message that must be displayed if something went wrong
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @return Process The process that ran
*
* @throws ProcessFailedException
*
* @see run()
*/
public function mustRun(OutputInterface $output, $cmd, $error = null, $callback = null)
{
$process = $this->run($output, $cmd, $error, $callback);
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
return $process;
}
/**
* Wraps a Process callback to add debugging output.
*
* @param OutputInterface $output An OutputInterface interface
* @param Process $process The Process
* @param callable|null $callback A PHP callable
*
* @return callable
*/
public function wrapCallback(OutputInterface $output, Process $process, $callback = null)
{
$formatter = $this->getHelperSet()->get('debug_formatter');
$that = $this;
return function ($type, $buffer) use ($output, $process, $callback, $formatter, $that) {
$output->write($formatter->progress(spl_object_hash($process), $that->escapeString($buffer), Process::ERR === $type));
if (null !== $callback) {
call_user_func($callback, $type, $buffer);
}
};
}
/**
* This method is public for PHP 5.3 compatibility, it should be private.
*
* @internal
*/
public function escapeString($str)
{
return str_replace('<', '\\<', $str);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'process';
}
}

@ -11,7 +11,6 @@
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;
/**
@ -23,26 +22,26 @@ use Symfony\Component\Console\Output\OutputInterface;
class ProgressBar
{
// options
private $barWidth = 28;
private $barChar = '=';
private $barWidth = 28;
private $barChar;
private $emptyBarChar = '-';
private $progressChar = '>';
private $format = null;
private $redrawFreq = 1;
private $format = null;
private $redrawFreq = 1;
/**
* @var OutputInterface
*/
private $output;
private $step;
private $step = 0;
private $max;
private $startTime;
private $stepWidth;
private $percent;
private $lastMessagesLength;
private $barCharOriginal;
private $percent = 0.0;
private $lastMessagesLength = 0;
private $formatLineCount;
private $messages;
private $overwrite = true;
private static $formatters;
private static $formats;
@ -55,20 +54,22 @@ class ProgressBar
*/
public function __construct(OutputInterface $output, $max = 0)
{
// Disabling output when it does not support ANSI codes as it would result in a broken display anyway.
$this->output = $output->isDecorated() ? $output : new NullOutput();
$this->max = (int) $max;
$this->stepWidth = $this->max > 0 ? Helper::strlen($this->max) : 4;
$this->output = $output;
$this->setMaxSteps($max);
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
if (!$this->output->isDecorated()) {
// disable overwrite when output does not support ANSI codes.
$this->overwrite = false;
if (!self::$formats) {
self::$formats = self::initFormats();
if ($this->max > 10) {
// set a reasonable redraw frequency so output isn't flooded
$this->setRedrawFrequency($max / 10);
}
}
$this->setFormat($this->determineBestFormat());
$this->startTime = time();
}
/**
@ -170,9 +171,21 @@ class ProgressBar
/**
* Gets the progress bar step.
*
* @deprecated since 2.6, to be removed in 3.0. Use {@link getProgress()} instead.
*
* @return int The progress bar step
*/
public function getStep()
{
return $this->getProgress();
}
/**
* Gets the current step position.
*
* @return int The progress bar step
*/
public function getProgress()
{
return $this->step;
}
@ -180,6 +193,8 @@ class ProgressBar
/**
* Gets the progress bar step width.
*
* @internal This method is public for PHP 5.3 compatibility, it should not be used.
*
* @return int The progress bar step width
*/
public function getStepWidth()
@ -190,7 +205,7 @@ class ProgressBar
/**
* Gets the current progress bar percent.
*
* @return int The current progress bar percent
* @return float The current progress bar percent
*/
public function getProgressPercent()
{
@ -234,6 +249,10 @@ class ProgressBar
*/
public function getBarCharacter()
{
if (null === $this->barChar) {
return $this->max ? '=' : $this->emptyBarChar;
}
return $this->barChar;
}
@ -285,10 +304,10 @@ class ProgressBar
public function setFormat($format)
{
// try to use the _nomax variant if available
if (!$this->max && isset(self::$formats[$format.'_nomax'])) {
$this->format = self::$formats[$format.'_nomax'];
} elseif (isset(self::$formats[$format])) {
$this->format = self::$formats[$format];
if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) {
$this->format = self::getFormatDefinition($format.'_nomax');
} elseif (null !== self::getFormatDefinition($format)) {
$this->format = self::getFormatDefinition($format);
} else {
$this->format = $format;
}
@ -308,18 +327,17 @@ class ProgressBar
/**
* Starts the progress output.
*
* @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged
*/
public function start()
public function start($max = null)
{
$this->startTime = time();
$this->step = 0;
$this->percent = 0;
$this->lastMessagesLength = 0;
$this->barCharOriginal = '';
$this->percent = 0.0;
if (!$this->max) {
$this->barCharOriginal = $this->barChar;
$this->barChar = $this->emptyBarChar;
if (null !== $max) {
$this->setMaxSteps($max);
}
$this->display();
@ -334,35 +352,55 @@ class ProgressBar
*/
public function advance($step = 1)
{
$this->setCurrent($this->step + $step);
$this->setProgress($this->step + $step);
}
/**
* Sets the current progress.
*
* @deprecated since 2.6, to be removed in 3.0. Use {@link setProgress()} instead.
*
* @param int $step The current progress
*
* @throws \LogicException
*/
public function setCurrent($step)
{
if (null === $this->startTime) {
throw new \LogicException('You must start the progress bar before calling setCurrent().');
}
$this->setProgress($step);
}
/**
* Sets whether to overwrite the progressbar, false for new line
*
* @param bool $overwrite
*/
public function setOverwrite($overwrite)
{
$this->overwrite = (bool) $overwrite;
}
/**
* Sets the current progress.
*
* @param int $step The current progress
*
* @throws \LogicException
*/
public function setProgress($step)
{
$step = (int) $step;
if ($step < $this->step) {
throw new \LogicException('You can\'t regress the progress bar.');
}
if ($this->max > 0 && $step > $this->max) {
throw new \LogicException('You can\'t advance the progress bar past the max value.');
if ($this->max && $step > $this->max) {
$this->max = $step;
}
$prevPeriod = intval($this->step / $this->redrawFreq);
$currPeriod = intval($step / $this->redrawFreq);
$this->step = $step;
$this->percent = $this->max > 0 ? (float) $this->step / $this->max : 0;
$this->percent = $this->max ? (float) $this->step / $this->max : 0;
if ($prevPeriod !== $currPeriod || $this->max === $step) {
$this->display();
}
@ -373,32 +411,25 @@ class ProgressBar
*/
public function finish()
{
if (null === $this->startTime) {
throw new \LogicException('You must start the progress bar before calling finish().');
}
if (!$this->max) {
$this->barChar = $this->barCharOriginal;
$this->max = $this->step;
$this->setCurrent($this->max);
$this->max = 0;
$this->barChar = $this->emptyBarChar;
} else {
$this->setCurrent($this->max);
}
$this->startTime = null;
if ($this->step === $this->max && !$this->overwrite) {
// prevent double 100% output
return;
}
$this->setProgress($this->max);
}
/**
* Outputs the current progress string.
*
* @throws \LogicException
*/
public function display()
{
if (null === $this->startTime) {
throw new \LogicException('You must start the progress bar before calling display().');
if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
return;
}
// these 3 variables can be removed in favor of using $this in the closure when support for PHP 5.3 will be dropped.
@ -431,9 +462,24 @@ class ProgressBar
*/
public function clear()
{
if (!$this->overwrite) {
return;
}
$this->overwrite(str_repeat("\n", $this->formatLineCount));
}
/**
* Sets the progress bar maximal steps.
*
* @param int The progress bar max steps
*/
private function setMaxSteps($max)
{
$this->max = max(0, (int) $max);
$this->stepWidth = $this->max ? Helper::strlen($this->max) : 4;
}
/**
* Overwrites a previous message to the output.
*
@ -452,8 +498,14 @@ class ProgressBar
}
}
// move back to the beginning of the progress bar before redrawing it
$this->output->write("\x0D");
if ($this->overwrite) {
// move back to the beginning of the progress bar before redrawing it
$this->output->write("\x0D");
} elseif ($this->step > 0) {
// move to new line
$this->output->writeln('');
}
if ($this->formatLineCount) {
$this->output->write(sprintf("\033[%dA", $this->formatLineCount));
}
@ -473,13 +525,13 @@ class ProgressBar
switch ($this->output->getVerbosity()) {
// OutputInterface::VERBOSITY_QUIET: display is disabled anyway
case OutputInterface::VERBOSITY_VERBOSE:
return $this->max > 0 ? 'verbose' : 'verbose_nomax';
return $this->max ? 'verbose' : 'verbose_nomax';
case OutputInterface::VERBOSITY_VERY_VERBOSE:
return $this->max > 0 ? 'very_verbose' : 'very_verbose_nomax';
return $this->max ? 'very_verbose' : 'very_verbose_nomax';
case OutputInterface::VERBOSITY_DEBUG:
return $this->max > 0 ? 'debug' : 'debug_nomax';
return $this->max ? 'debug' : 'debug_nomax';
default:
return $this->max > 0 ? 'normal' : 'normal_nomax';
return $this->max ? 'normal' : 'normal_nomax';
}
}
@ -487,7 +539,7 @@ class ProgressBar
{
return array(
'bar' => function (ProgressBar $bar, OutputInterface $output) {
$completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getStep() % $bar->getBarWidth());
$completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth());
$display = str_repeat($bar->getBarCharacter(), $completeBars);
if ($completeBars < $bar->getBarWidth()) {
$emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter());
@ -504,10 +556,10 @@ class ProgressBar
throw new \LogicException('Unable to display the remaining time if the maximum number of steps is not set.');
}
if (!$bar->getStep()) {
if (!$bar->getProgress()) {
$remaining = 0;
} else {
$remaining = round((time() - $bar->getStartTime()) / $bar->getStep() * ($bar->getMaxSteps() - $bar->getStep()));
$remaining = round((time() - $bar->getStartTime()) / $bar->getProgress() * ($bar->getMaxSteps() - $bar->getProgress()));
}
return Helper::formatTime($remaining);
@ -517,10 +569,10 @@ class ProgressBar
throw new \LogicException('Unable to display the estimated time if the maximum number of steps is not set.');
}
if (!$bar->getStep()) {
if (!$bar->getProgress()) {
$estimated = 0;
} else {
$estimated = round((time() - $bar->getStartTime()) / $bar->getStep() * $bar->getMaxSteps());
$estimated = round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps());
}
return Helper::formatTime($estimated);
@ -529,7 +581,7 @@ class ProgressBar
return Helper::formatMemory(memory_get_usage(true));
},
'current' => function (ProgressBar $bar) {
return str_pad($bar->getStep(), $bar->getStepWidth(), ' ', STR_PAD_LEFT);
return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT);
},
'max' => function (ProgressBar $bar) {
return $bar->getMaxSteps();
@ -543,17 +595,17 @@ class ProgressBar
private static function initFormats()
{
return array(
'normal' => ' %current%/%max% [%bar%] %percent:3s%%',
'normal_nomax' => ' %current% [%bar%]',
'normal' => ' %current%/%max% [%bar%] %percent:3s%%',
'normal_nomax' => ' %current% [%bar%]',
'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%',
'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%',
'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%',
'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%',
'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%',
'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%',
'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%',
'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%',
'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%',
'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%',
'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%',
);
}
}

@ -24,20 +24,20 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
class ProgressHelper extends Helper
{
const FORMAT_QUIET = ' %percent%%';
const FORMAT_NORMAL = ' %current%/%max% [%bar%] %percent%%';
const FORMAT_VERBOSE = ' %current%/%max% [%bar%] %percent%% Elapsed: %elapsed%';
const FORMAT_QUIET_NOMAX = ' %current%';
const FORMAT_NORMAL_NOMAX = ' %current% [%bar%]';
const FORMAT_QUIET = ' %percent%%';
const FORMAT_NORMAL = ' %current%/%max% [%bar%] %percent%%';
const FORMAT_VERBOSE = ' %current%/%max% [%bar%] %percent%% Elapsed: %elapsed%';
const FORMAT_QUIET_NOMAX = ' %current%';
const FORMAT_NORMAL_NOMAX = ' %current% [%bar%]';
const FORMAT_VERBOSE_NOMAX = ' %current% [%bar%] Elapsed: %elapsed%';
// options
private $barWidth = 28;
private $barChar = '=';
private $barWidth = 28;
private $barChar = '=';
private $emptyBarChar = '-';
private $progressChar = '>';
private $format = null;
private $redrawFreq = 1;
private $format = null;
private $redrawFreq = 1;
private $lastMessagesLength;
private $barCharOriginal;
@ -95,7 +95,7 @@ class ProgressHelper extends Helper
*/
private $widths = array(
'current' => 4,
'max' => 4,
'max' => 4,
'percent' => 3,
'elapsed' => 6,
);
@ -186,11 +186,11 @@ class ProgressHelper extends Helper
public function start(OutputInterface $output, $max = null)
{
$this->startTime = time();
$this->current = 0;
$this->max = (int) $max;
$this->current = 0;
$this->max = (int) $max;
// Disabling output when it does not support ANSI codes as it would result in a broken display anyway.
$this->output = $output->isDecorated() ? $output : new NullOutput();
$this->output = $output->isDecorated() ? $output : new NullOutput();
$this->lastMessagesLength = 0;
$this->barCharOriginal = '';
@ -225,8 +225,8 @@ class ProgressHelper extends Helper
/**
* Advances the progress output X steps.
*
* @param int $step Number of steps to advance
* @param bool $redraw Whether to redraw or not
* @param int $step Number of steps to advance
* @param bool $redraw Whether to redraw or not
*
* @throws \LogicException
*/
@ -238,8 +238,8 @@ class ProgressHelper extends Helper
/**
* Sets the current progress.
*
* @param int $current The current progress
* @param bool $redraw Whether to redraw or not
* @param int $current The current progress
* @param bool $redraw Whether to redraw or not
*
* @throws \LogicException
*/
@ -272,7 +272,7 @@ class ProgressHelper extends Helper
/**
* Outputs the current progress string.
*
* @param bool $finish Forces the end result
* @param bool $finish Forces the end result
*
* @throws \LogicException
*/
@ -334,24 +334,24 @@ class ProgressHelper extends Helper
}
if ($this->max > 0) {
$this->widths['max'] = $this->strlen($this->max);
$this->widths['max'] = $this->strlen($this->max);
$this->widths['current'] = $this->widths['max'];
} else {
$this->barCharOriginal = $this->barChar;
$this->barChar = $this->emptyBarChar;
$this->barChar = $this->emptyBarChar;
}
}
/**
* Generates the array map of format variables to values.
*
* @param bool $finish Forces the end result
* @param bool $finish Forces the end result
*
* @return array Array of format vars and values
*/
private function generate($finish = false)
{
$vars = array();
$vars = array();
$percent = 0;
if ($this->max > 0) {
$percent = (float) $this->current / $this->max;
@ -403,7 +403,7 @@ class ProgressHelper extends Helper
/**
* Converts seconds into human-readable format.
*
* @param int $secs Number of seconds
* @param int $secs Number of seconds
*
* @return string Time in readable format
*/
@ -428,8 +428,8 @@ class ProgressHelper extends Helper
/**
* Overwrites a previous message to the output.
*
* @param OutputInterface $output An Output instance
* @param string $message The message
* @param OutputInterface $output An Output instance
* @param string $message The message
*/
private function overwrite(OutputInterface $output, $message)
{

@ -220,7 +220,7 @@ class TableHelper extends Helper
/**
* Sets cell padding type.
*
* @param int $padType STR_PAD_*
* @param int $padType STR_PAD_*
*
* @return TableHelper
*/

@ -278,7 +278,7 @@ class ArgvInput extends Input
*
* @param string|array $values The value(s) to look for in the raw parameters (can be an array)
*
* @return bool true if the value is contained in the raw parameters
* @return bool true if the value is contained in the raw parameters
*/
public function hasParameterOption($values)
{

@ -65,7 +65,7 @@ class ArrayInput extends Input
*
* @param string|array $values The values to look for in the raw parameters (can be an array)
*
* @return bool true if the value is contained in the raw parameters
* @return bool true if the value is contained in the raw parameters
*/
public function hasParameterOption($values)
{

@ -81,7 +81,7 @@ abstract class Input implements InputInterface
/**
* Checks if the input is interactive.
*
* @return bool Returns true if the input is interactive
* @return bool Returns true if the input is interactive
*/
public function isInteractive()
{
@ -91,7 +91,7 @@ abstract class Input implements InputInterface
/**
* Sets the input interactivity.
*
* @param bool $interactive If the input should be interactive
* @param bool $interactive If the input should be interactive
*/
public function setInteractive($interactive)
{
@ -146,9 +146,9 @@ abstract class Input implements InputInterface
/**
* Returns true if an InputArgument object exists by name or position.
*
* @param string|int $name The InputArgument name or position
* @param string|int $name The InputArgument name or position
*
* @return bool true if the InputArgument object exists, false otherwise
* @return bool true if the InputArgument object exists, false otherwise
*/
public function hasArgument($name)
{
@ -186,8 +186,8 @@ abstract class Input implements InputInterface
/**
* Sets an option value by name.
*
* @param string $name The option name
* @param string|bool $value The option value
* @param string $name The option name
* @param string|bool $value The option value
*
* @throws \InvalidArgumentException When option given doesn't exist
*/
@ -205,7 +205,7 @@ abstract class Input implements InputInterface
*
* @param string $name The InputOption name
*
* @return bool true if the InputOption object exists, false otherwise
* @return bool true if the InputOption object exists, false otherwise
*/
public function hasOption($name)
{

@ -32,10 +32,10 @@ class InputArgument
/**
* Constructor.
*
* @param string $name The argument name
* @param int $mode The argument mode: self::REQUIRED or self::OPTIONAL
* @param string $description A description text
* @param mixed $default The default value (for self::OPTIONAL mode only)
* @param string $name The argument name
* @param int $mode The argument mode: self::REQUIRED or self::OPTIONAL
* @param string $description A description text
* @param mixed $default The default value (for self::OPTIONAL mode only)
*
* @throws \InvalidArgumentException When argument mode is not valid
*
@ -49,8 +49,8 @@ class InputArgument
throw new \InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode));
}
$this->name = $name;
$this->mode = $mode;
$this->name = $name;
$this->mode = $mode;
$this->description = $description;
$this->setDefault($default);
@ -69,7 +69,7 @@ class InputArgument
/**
* Returns true if the argument is required.
*
* @return bool true if parameter mode is self::REQUIRED, false otherwise
* @return bool true if parameter mode is self::REQUIRED, false otherwise
*/
public function isRequired()
{
@ -79,7 +79,7 @@ class InputArgument
/**
* Returns true if the argument can take multiple values.
*
* @return bool true if mode is self::IS_ARRAY, false otherwise
* @return bool true if mode is self::IS_ARRAY, false otherwise
*/
public function isArray()
{

@ -11,11 +11,6 @@
namespace Symfony\Component\Console\Input;
if (!defined('JSON_UNESCAPED_UNICODE')) {
define('JSON_UNESCAPED_SLASHES', 64);
define('JSON_UNESCAPED_UNICODE', 256);
}
use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Output\BufferedOutput;
@ -87,9 +82,9 @@ class InputDefinition
*/
public function setArguments($arguments = array())
{
$this->arguments = array();
$this->requiredCount = 0;
$this->hasOptional = false;
$this->arguments = array();
$this->requiredCount = 0;
$this->hasOptional = false;
$this->hasAnArrayArgument = false;
$this->addArguments($arguments);
}
@ -149,7 +144,7 @@ class InputDefinition
/**
* Returns an InputArgument by name or by position.
*
* @param string|int $name The InputArgument name or position
* @param string|int $name The InputArgument name or position
*
* @return InputArgument An InputArgument object
*
@ -171,9 +166,9 @@ class InputDefinition
/**
* Returns true if an InputArgument object exists by name or position.
*
* @param string|int $name The InputArgument name or position
* @param string|int $name The InputArgument name or position
*
* @return bool true if the InputArgument object exists, false otherwise
* @return bool true if the InputArgument object exists, false otherwise
*
* @api
*/
@ -199,7 +194,7 @@ class InputDefinition
/**
* Returns the number of InputArguments.
*
* @return int The number of InputArguments
* @return int The number of InputArguments
*/
public function getArgumentCount()
{
@ -209,7 +204,7 @@ class InputDefinition
/**
* Returns the number of required InputArguments.
*
* @return int The number of required InputArguments
* @return int The number of required InputArguments
*/
public function getArgumentRequiredCount()
{
@ -315,7 +310,7 @@ class InputDefinition
*
* @param string $name The InputOption name
*
* @return bool true if the InputOption object exists, false otherwise
* @return bool true if the InputOption object exists, false otherwise
*
* @api
*/
@ -341,7 +336,7 @@ class InputDefinition
*
* @param string $name The InputOption shortcut
*
* @return bool true if the InputOption object exists, false otherwise
* @return bool true if the InputOption object exists, false otherwise
*/
public function hasShortcut($name)
{
@ -436,7 +431,7 @@ class InputDefinition
/**
* Returns an XML representation of the InputDefinition.
*
* @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 InputDefinition
*

@ -33,7 +33,7 @@ interface InputInterface
*
* @param string|array $values The values to look for in the raw parameters (can be an array)
*
* @return bool true if the value is contained in the raw parameters
* @return bool true if the value is contained in the raw parameters
*/
public function hasParameterOption($values);
@ -95,9 +95,9 @@ interface InputInterface
/**
* Returns true if an InputArgument object exists by name or position.
*
* @param string|int $name The InputArgument name or position
* @param string|int $name The InputArgument name or position
*
* @return bool true if the InputArgument object exists, false otherwise
* @return bool true if the InputArgument object exists, false otherwise
*/
public function hasArgument($name);
@ -120,8 +120,8 @@ interface InputInterface
/**
* Sets an option value by name.
*
* @param string $name The option name
* @param string|bool $value The option value
* @param string $name The option name
* @param string|bool $value The option value
*
* @throws \InvalidArgumentException When option given doesn't exist
*/
@ -132,7 +132,7 @@ interface InputInterface
*
* @param string $name The InputOption name
*
* @return bool true if the InputOption object exists, false otherwise
* @return bool true if the InputOption object exists, false otherwise
*/
public function hasOption($name);
@ -146,7 +146,7 @@ interface InputInterface
/**
* Sets the input interactivity.
*
* @param bool $interactive If the input should be interactive
* @param bool $interactive If the input should be interactive
*/
public function setInteractive($interactive);
}

@ -20,7 +20,7 @@ namespace Symfony\Component\Console\Input;
*/
class InputOption
{
const VALUE_NONE = 1;
const VALUE_NONE = 1;
const VALUE_REQUIRED = 2;
const VALUE_OPTIONAL = 4;
const VALUE_IS_ARRAY = 8;
@ -77,9 +77,9 @@ class InputOption
throw new \InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode));
}
$this->name = $name;
$this->shortcut = $shortcut;
$this->mode = $mode;
$this->name = $name;
$this->shortcut = $shortcut;
$this->mode = $mode;
$this->description = $description;
if ($this->isArray() && !$this->acceptValue()) {
@ -112,7 +112,7 @@ class InputOption
/**
* Returns true if the option accepts a value.
*
* @return bool true if value mode is not self::VALUE_NONE, false otherwise
* @return bool true if value mode is not self::VALUE_NONE, false otherwise
*/
public function acceptValue()
{
@ -122,7 +122,7 @@ class InputOption
/**
* Returns true if the option requires a value.
*
* @return bool true if value mode is self::VALUE_REQUIRED, false otherwise
* @return bool true if value mode is self::VALUE_REQUIRED, false otherwise
*/
public function isValueRequired()
{
@ -132,7 +132,7 @@ class InputOption
/**
* Returns true if the option takes an optional value.
*
* @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise
* @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise
*/
public function isValueOptional()
{
@ -142,7 +142,7 @@ class InputOption
/**
* Returns true if the option can take multiple values.
*
* @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise
* @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise
*/
public function isArray()
{
@ -194,9 +194,10 @@ class InputOption
}
/**
* Checks whether the given option equals this one
* Checks whether the given option equals this one.
*
* @param InputOption $option option to compare
*
* @return bool
*/
public function equals(InputOption $option)

@ -158,8 +158,8 @@ abstract class Output implements OutputInterface
/**
* Writes a message to the output.
*
* @param string $message A message to write to the output
* @param bool $newline Whether to add a newline or not
* @param string $message A message to write to the output
* @param bool $newline Whether to add a newline or not
*/
abstract protected function doWrite($message, $newline);
}

@ -22,15 +22,15 @@ use Symfony\Component\Console\Formatter\OutputFormatterInterface;
*/
interface OutputInterface
{
const VERBOSITY_QUIET = 0;
const VERBOSITY_NORMAL = 1;
const VERBOSITY_VERBOSE = 2;
const VERBOSITY_QUIET = 0;
const VERBOSITY_NORMAL = 1;
const VERBOSITY_VERBOSE = 2;
const VERBOSITY_VERY_VERBOSE = 3;
const VERBOSITY_DEBUG = 4;
const VERBOSITY_DEBUG = 4;
const OUTPUT_NORMAL = 0;
const OUTPUT_RAW = 1;
const OUTPUT_PLAIN = 2;
const OUTPUT_RAW = 1;
const OUTPUT_PLAIN = 2;
/**
* Writes a message to the output.
@ -60,7 +60,7 @@ interface OutputInterface
/**
* Sets the verbosity of the output.
*
* @param int $level The level of verbosity (one of the VERBOSITY constants)
* @param int $level The level of verbosity (one of the VERBOSITY constants)
*
* @api
*/
@ -69,7 +69,7 @@ interface OutputInterface
/**
* Gets the current verbosity of the output.
*
* @return int The current level of verbosity (one of the VERBOSITY constants)
* @return int The current level of verbosity (one of the VERBOSITY constants)
*
* @api
*/
@ -78,7 +78,7 @@ interface OutputInterface
/**
* Sets the decorated flag.
*
* @param bool $decorated Whether to decorate the messages
* @param bool $decorated Whether to decorate the messages
*
* @api
*/
@ -87,7 +87,7 @@ interface OutputInterface
/**
* Gets the decorated flag.
*
* @return bool true if the output will decorate messages, false otherwise
* @return bool true if the output will decorate messages, false otherwise
*
* @api
*/
@ -105,7 +105,7 @@ interface OutputInterface
/**
* Returns current output formatter instance.
*
* @return OutputFormatterInterface
* @return OutputFormatterInterface
*
* @api
*/

@ -90,7 +90,7 @@ class StreamOutput extends Output
* - Windows without Ansicon and ConEmu
* - non tty consoles
*
* @return bool true if the stream supports colorization, false otherwise
* @return bool true if the stream supports colorization, false otherwise
*/
protected function hasColorSupport()
{

@ -163,7 +163,7 @@ EOF;
*
* @param string $text The last segment of the entered text
*
* @return bool|array A list of guessed strings or true
* @return bool|array A list of guessed strings or true
*/
private function autocompleter($text)
{

@ -56,7 +56,7 @@ class ApplicationTester
* @param array $input An array of arguments and options
* @param array $options An array of options
*
* @return int The command exit code
* @return int The command exit code
*/
public function run(array $input, $options = array())
{
@ -79,7 +79,7 @@ class ApplicationTester
/**
* Gets the display returned by the last execution of the application.
*
* @param bool $normalize Whether to normalize end of lines to \n or not
* @param bool $normalize Whether to normalize end of lines to \n or not
*
* @return string The display
*/

@ -51,7 +51,7 @@ class CommandTester
* @param array $input An array of arguments and options
* @param array $options An array of options
*
* @return int The command exit code
* @return int The command exit code
*/
public function execute(array $input, array $options = array())
{
@ -83,7 +83,7 @@ class CommandTester
/**
* Gets the display returned by the last execution of the command.
*
* @param bool $normalize Whether to normalize end of lines to \n or not
* @param bool $normalize Whether to normalize end of lines to \n or not
*
* @return string The display
*/

@ -95,7 +95,7 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
public function testHelp()
{
$application = new Application();
$this->assertStringEqualsFile(self::$fixturesPath.'/application_gethelp.txt', $this->normalizeLineBreaks($application->getHelp()), '->setHelp() returns a help message');
$this->assertStringEqualsFile(self::$fixturesPath.'/application_gethelp.txt', $this->normalizeLineBreaks($application->getHelp()), '->getHelp() returns a help message');
}
public function testAll()
@ -548,10 +548,10 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
$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');
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth1.txt', $tester->getDisplay(true), '->renderException() renders 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');
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth1decorated.txt', $tester->getDisplay(true), '->renderException() renders a pretty exceptions with previous exceptions');
$application = $this->getMock('Symfony\Component\Console\Application', array('getTerminalWidth'));
$application->setAutoExit(false);
@ -903,6 +903,22 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
$this->assertContains('before.foo.after.caught.', $tester->getDisplay());
}
public function testRunWithDispatcherSkippingCommand()
{
$application = new Application();
$application->setDispatcher($this->getDispatcher(true));
$application->setAutoExit(false);
$application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) {
$output->write('foo.');
});
$tester = new ApplicationTester($application);
$exitCode = $tester->run(array('command' => 'foo'));
$this->assertContains('before.after.', $tester->getDisplay());
$this->assertEquals(ConsoleCommandEvent::RETURN_CODE_DISABLED, $exitCode);
}
public function testTerminalDimensions()
{
$application = new Application();
@ -918,16 +934,22 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array($width, 80), $application->getTerminalDimensions());
}
protected function getDispatcher()
protected function getDispatcher($skipCommand = false)
{
$dispatcher = new EventDispatcher();
$dispatcher->addListener('console.command', function (ConsoleCommandEvent $event) {
$dispatcher->addListener('console.command', function (ConsoleCommandEvent $event) use ($skipCommand) {
$event->getOutput()->write('before.');
if ($skipCommand) {
$event->disableCommand();
}
});
$dispatcher->addListener('console.terminate', function (ConsoleTerminateEvent $event) {
$dispatcher->addListener('console.terminate', function (ConsoleTerminateEvent $event) use ($skipCommand) {
$event->getOutput()->write('after.');
$event->setExitCode(128);
if (!$skipCommand) {
$event->setExitCode(113);
}
});
$dispatcher->addListener('console.exception', function (ConsoleExceptionEvent $event) {
$event->getOutput()->writeln('caught.');
@ -959,6 +981,25 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('interact called'.PHP_EOL.'called'.PHP_EOL, $tester->getDisplay(), 'Application runs the default set command if different from \'list\' command');
}
public function testCanCheckIfTerminalIsInteractive()
{
if (!function_exists('posix_isatty')) {
$this->markTestSkipped('posix_isatty function is required');
}
$application = new CustomDefaultCommandApplication();
$application->setAutoExit(false);
$tester = new ApplicationTester($application);
$tester->run(array('command' => 'help'));
$this->assertTrue($tester->getInput()->isInteractive());
$this->assertFalse($tester->getInput()->hasParameterOption(array('--no-interaction', '-n')));
$inputStream = $application->getHelperSet()->get('question')->getInputStream();
$this->assertEquals($tester->getInput()->isInteractive(), @posix_isatty($inputStream));
}
}
class CustomApplication extends Application

@ -41,7 +41,7 @@ class CommandTest extends \PHPUnit_Framework_TestCase
/**
* @expectedException \LogicException
* @expectedExceptionMessage The command name cannot be empty.
* @expectedExceptionMessage The command defined in "Symfony\Component\Console\Command\Command" cannot have an empty name.
*/
public function testCommandNameCannotBeEmpty()
{
@ -211,7 +211,7 @@ class CommandTest extends \PHPUnit_Framework_TestCase
$m = $r->getMethod('mergeApplicationDefinition');
$m->setAccessible(true);
$m->invoke($command, false);
$this->assertTrue($command->getDefinition()->hasOption('bar'), '->mergeApplicationDefinition(false) merges the application and the commmand options');
$this->assertTrue($command->getDefinition()->hasOption('bar'), '->mergeApplicationDefinition(false) merges the application and the command options');
$this->assertFalse($command->getDefinition()->hasArgument('foo'), '->mergeApplicationDefinition(false) does not merge the application arguments');
$m->invoke($command, true);
@ -243,7 +243,7 @@ class CommandTest extends \PHPUnit_Framework_TestCase
* @expectedException \LogicException
* @expectedExceptionMessage You must override the execute() method in the concrete command class.
*/
public function testExecuteMethodNeedsToBeOverriden()
public function testExecuteMethodNeedsToBeOverridden()
{
$command = new Command('foo');
$command->run(new StringInput(''), new NullOutput());

@ -1,17 +1,17 @@
<info>Console Tool</info>
<comment>Usage:</comment>
[options] command [arguments]
[options] command [arguments]
<comment>Options:</comment>
<info>--help</info> <info>-h</info> Display this help message.
<info>--quiet</info> <info>-q</info> Do not output any message.
<info>--verbose</info> <info>-v|vv|vvv</info> Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug.
<info>--version</info> <info>-V</info> Display this application version.
<info>--ansi</info> Force ANSI output.
<info>--no-ansi</info> Disable ANSI output.
<info>--no-interaction</info> <info>-n</info> Do not ask any interactive question.
<info>--help</info> (-h) Display this help message.
<info>--quiet</info> (-q) Do not output any message.
<info>--verbose</info> (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug.
<info>--version</info> (-V) Display this application version.
<info>--ansi</info> Force ANSI output.
<info>--no-ansi</info> Disable ANSI output.
<info>--no-interaction</info> (-n) Do not ask any interactive question.
<comment>Available commands:</comment>
<info>help </info> Displays help for a command
<info>list </info> Lists commands
<info>help </info> Displays help for a command
<info>list </info> Lists commands

@ -1,22 +1,22 @@
<info>My Symfony application</info> version <comment>v1.0</comment>
<comment>Usage:</comment>
[options] command [arguments]
[options] command [arguments]
<comment>Options:</comment>
<info>--help</info> <info>-h</info> Display this help message.
<info>--quiet</info> <info>-q</info> Do not output any message.
<info>--verbose</info> <info>-v|vv|vvv</info> Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug.
<info>--version</info> <info>-V</info> Display this application version.
<info>--ansi</info> Force ANSI output.
<info>--no-ansi</info> Disable ANSI output.
<info>--no-interaction</info> <info>-n</info> Do not ask any interactive question.
<info>--help</info> (-h) Display this help message.
<info>--quiet</info> (-q) Do not output any message.
<info>--verbose</info> (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug.
<info>--version</info> (-V) Display this application version.
<info>--ansi</info> Force ANSI output.
<info>--no-ansi</info> Disable ANSI output.
<info>--no-interaction</info> (-n) Do not ask any interactive question.
<comment>Available commands:</comment>
<info>alias1 </info> command 1 description
<info>alias2 </info> command 1 description
<info>help </info> Displays help for a command
<info>list </info> Lists commands
<info>alias1 </info> command 1 description
<info>alias2 </info> command 1 description
<info>help </info> Displays help for a command
<info>list </info> Lists commands
<comment>descriptor</comment>
<info>descriptor:command1 </info> command 1 description
<info>descriptor:command2 </info> command 2 description
<info>descriptor:command1 </info> command 1 description
<info>descriptor:command2 </info> command 2 description

@ -1,20 +1,20 @@
<info>Console Tool</info>
<comment>Usage:</comment>
[options] command [arguments]
[options] command [arguments]
<comment>Options:</comment>
<info>--help</info> <info>-h</info> Display this help message.
<info>--quiet</info> <info>-q</info> Do not output any message.
<info>--verbose</info> <info>-v|vv|vvv</info> Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug.
<info>--version</info> <info>-V</info> Display this application version.
<info>--ansi</info> Force ANSI output.
<info>--no-ansi</info> Disable ANSI output.
<info>--no-interaction</info> <info>-n</info> Do not ask any interactive question.
<info>--help</info> (-h) Display this help message.
<info>--quiet</info> (-q) Do not output any message.
<info>--verbose</info> (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug.
<info>--version</info> (-V) Display this application version.
<info>--ansi</info> Force ANSI output.
<info>--no-ansi</info> Disable ANSI output.
<info>--no-interaction</info> (-n) Do not ask any interactive question.
<comment>Available commands:</comment>
<info>afoobar </info> The foo:bar command
<info>help </info> Displays help for a command
<info>list </info> Lists commands
<info>afoobar </info> The foo:bar command
<info>help </info> Displays help for a command
<info>list </info> Lists commands
<comment>foo</comment>
<info>foo:bar </info> The foo:bar command
<info>foo:bar </info> The foo:bar command

@ -1,16 +1,16 @@
<info>Console Tool</info>
<comment>Usage:</comment>
[options] command [arguments]
[options] command [arguments]
<comment>Options:</comment>
<info>--help</info> <info>-h</info> Display this help message.
<info>--quiet</info> <info>-q</info> Do not output any message.
<info>--verbose</info> <info>-v|vv|vvv</info> Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug.
<info>--version</info> <info>-V</info> Display this application version.
<info>--ansi</info> Force ANSI output.
<info>--no-ansi</info> Disable ANSI output.
<info>--no-interaction</info> <info>-n</info> Do not ask any interactive question.
<info>--help</info> (-h) Display this help message.
<info>--quiet</info> (-q) Do not output any message.
<info>--verbose</info> (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug.
<info>--version</info> (-V) Display this application version.
<info>--ansi</info> Force ANSI output.
<info>--no-ansi</info> Disable ANSI output.
<info>--no-interaction</info> (-n) Do not ask any interactive question.
<comment>Available commands for the "foo" namespace:</comment>
<info>foo:bar </info> The foo:bar command
<info>foo:bar </info> The foo:bar command

@ -1,13 +1 @@
<info>Console Tool</info>
<comment>Usage:</comment>
[options] command [arguments]
<comment>Options:</comment>
<info>--help</info> <info>-h</info> Display this help message.
<info>--quiet</info> <info>-q</info> Do not output any message.
<info>--verbose</info> <info>-v|vv|vvv</info> Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug.
<info>--version</info> <info>-V</info> Display this application version.
<info>--ansi</info> Force ANSI output.
<info>--no-ansi</info> Disable ANSI output.
<info>--no-interaction</info> <info>-n</info> Do not ask any interactive question.
<info>Console Tool</info>

@ -1,17 +1,17 @@
Console Tool
Usage:
[options] command [arguments]
[options] command [arguments]
Options:
--help -h Display this help message.
--quiet -q Do not output any message.
--verbose -v|vv|vvv Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug.
--version -V Display this application version.
--ansi Force ANSI output.
--no-ansi Disable ANSI output.
--no-interaction -n Do not ask any interactive question.
--help (-h) Display this help message.
--quiet (-q) Do not output any message.
--verbose (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug.
--version (-V) Display this application version.
--ansi Force ANSI output.
--no-ansi Disable ANSI output.
--no-interaction (-n) Do not ask any interactive question.
Available commands:
help Displays help for a command
list Lists commands
help Displays help for a command
list Lists commands

@ -128,7 +128,7 @@ class HelperSetTest extends \PHPUnit_Framework_TestCase
}
}
/**
/**
* Create a generic mock for the helper interface. Optionally check for a call to setHelperSet with a specific
* helperset instance.
*

@ -0,0 +1,118 @@
<?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\Component\Console\Tests\Helper;
use Symfony\Component\Console\Helper\DebugFormatterHelper;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Output\StreamOutput;
use Symfony\Component\Console\Helper\ProcessHelper;
use Symfony\Component\Process\Process;
class ProcessHelperTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider provideCommandsAndOutput
*/
public function testVariousProcessRuns($expected, $cmd, $verbosity, $error)
{
$helper = new ProcessHelper();
$helper->setHelperSet(new HelperSet(array(new DebugFormatterHelper())));
$output = $this->getOutputStream($verbosity);
$helper->run($output, $cmd, $error);
$this->assertEquals($expected, $this->getOutput($output));
}
public function testPassedCallbackIsExecuted()
{
$helper = new ProcessHelper();
$helper->setHelperSet(new HelperSet(array(new DebugFormatterHelper())));
$output = $this->getOutputStream(StreamOutput::VERBOSITY_NORMAL);
$executed = false;
$callback = function () use (&$executed) { $executed = true; };
$helper->run($output, 'php -r "echo 42;"', null, $callback);
$this->assertTrue($executed);
}
public function provideCommandsAndOutput()
{
$successOutputVerbose = <<<EOT
RUN php -r "echo 42;"
RES Command ran successfully
EOT;
$successOutputDebug = <<<EOT
RUN php -r "echo 42;"
OUT 42
RES Command ran successfully
EOT;
$successOutputDebugWithTags = <<<EOT
RUN php -r "echo \"<info>42</info>\";"
OUT <info>42</info>
RES Command ran successfully
EOT;
$successOutputProcessDebug = <<<EOT
RUN 'php' '-r' 'echo 42;'
OUT 42
RES Command ran successfully
EOT;
$syntaxErrorOutputVerbose = <<<EOT
RUN php -r "fwrite(STDERR, 'error message');usleep(50000);fwrite(STDOUT, 'out message');exit(252);"
RES 252 Command did not run successfully
EOT;
$syntaxErrorOutputDebug = <<<EOT
RUN php -r "fwrite(STDERR, 'error message');usleep(50000);fwrite(STDOUT, 'out message');exit(252);"
ERR error message
OUT out message
RES 252 Command did not run successfully
EOT;
$errorMessage = 'An error occurred';
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
$successOutputProcessDebug = str_replace("'", '"', $successOutputProcessDebug);
}
return array(
array('', 'php -r "echo 42;"', StreamOutput::VERBOSITY_VERBOSE, null),
array($successOutputVerbose, 'php -r "echo 42;"', StreamOutput::VERBOSITY_VERY_VERBOSE, null),
array($successOutputDebug, 'php -r "echo 42;"', StreamOutput::VERBOSITY_DEBUG, null),
array($successOutputDebugWithTags, 'php -r "echo \"<info>42</info>\";"', StreamOutput::VERBOSITY_DEBUG, null),
array('', 'php -r "syntax error"', StreamOutput::VERBOSITY_VERBOSE, null),
array($syntaxErrorOutputVerbose, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERY_VERBOSE, null),
array($syntaxErrorOutputDebug, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_DEBUG, null),
array($errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERBOSE, $errorMessage),
array($syntaxErrorOutputVerbose.$errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERY_VERBOSE, $errorMessage),
array($syntaxErrorOutputDebug.$errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_DEBUG, $errorMessage),
array($successOutputProcessDebug, array('php', '-r', 'echo 42;'), StreamOutput::VERBOSITY_DEBUG, null),
array($successOutputDebug, new Process('php -r "echo 42;"'), StreamOutput::VERBOSITY_DEBUG, null),
);
}
private function getOutputStream($verbosity)
{
return new StreamOutput(fopen('php://memory', 'r+', false), $verbosity, false);
}
private function getOutput(StreamOutput $output)
{
rewind($output->getStream());
return stream_get_contents($output->getStream());
}
}

@ -17,7 +17,21 @@ use Symfony\Component\Console\Output\StreamOutput;
class ProgressBarTest extends \PHPUnit_Framework_TestCase
{
protected $lastMessagesLength;
public function testMultipleStart()
{
$bar = new ProgressBar($output = $this->getOutputStream());
$bar->start();
$bar->advance();
$bar->start();
rewind($output->getStream());
$this->assertEquals(
$this->generateOutput(' 0 [>---------------------------]').
$this->generateOutput(' 1 [->--------------------------]').
$this->generateOutput(' 0 [>---------------------------]'),
stream_get_contents($output->getStream())
);
}
public function testAdvance()
{
@ -63,6 +77,22 @@ class ProgressBarTest extends \PHPUnit_Framework_TestCase
);
}
public function testAdvanceOverMax()
{
$bar = new ProgressBar($output = $this->getOutputStream(), 10);
$bar->setProgress(9);
$bar->advance();
$bar->advance();
rewind($output->getStream());
$this->assertEquals(
$this->generateOutput(' 9/10 [=========================>--] 90%').
$this->generateOutput(' 10/10 [============================] 100%').
$this->generateOutput(' 11/11 [============================] 100%'),
stream_get_contents($output->getStream())
);
}
public function testCustomizations()
{
$bar = new ProgressBar($output = $this->getOutputStream(), 10);
@ -82,6 +112,42 @@ class ProgressBarTest extends \PHPUnit_Framework_TestCase
);
}
public function testDisplayWithoutStart()
{
$bar = new ProgressBar($output = $this->getOutputStream(), 50);
$bar->display();
rewind($output->getStream());
$this->assertEquals(
$this->generateOutput(' 0/50 [>---------------------------] 0%'),
stream_get_contents($output->getStream())
);
}
public function testDisplayWithQuietVerbosity()
{
$bar = new ProgressBar($output = $this->getOutputStream(true, StreamOutput::VERBOSITY_QUIET), 50);
$bar->display();
rewind($output->getStream());
$this->assertEquals(
'',
stream_get_contents($output->getStream())
);
}
public function testFinishWithoutStart()
{
$bar = new ProgressBar($output = $this->getOutputStream(), 50);
$bar->finish();
rewind($output->getStream());
$this->assertEquals(
$this->generateOutput(' 50/50 [============================] 100%'),
stream_get_contents($output->getStream())
);
}
public function testPercent()
{
$bar = new ProgressBar($output = $this->getOutputStream(), 50);
@ -122,14 +188,29 @@ class ProgressBarTest extends \PHPUnit_Framework_TestCase
);
}
public function testStartWithMax()
{
$bar = new ProgressBar($output = $this->getOutputStream());
$bar->setFormat('%current%/%max% [%bar%]');
$bar->start(50);
$bar->advance();
rewind($output->getStream());
$this->assertEquals(
$this->generateOutput(' 0/50 [>---------------------------]').
$this->generateOutput(' 1/50 [>---------------------------]'),
stream_get_contents($output->getStream())
);
}
public function testSetCurrentProgress()
{
$bar = new ProgressBar($output = $this->getOutputStream(), 50);
$bar->start();
$bar->display();
$bar->advance();
$bar->setCurrent(15);
$bar->setCurrent(25);
$bar->setProgress(15);
$bar->setProgress(25);
rewind($output->getStream());
$this->assertEquals(
@ -143,13 +224,12 @@ class ProgressBarTest extends \PHPUnit_Framework_TestCase
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage You must start the progress bar
*/
public function testSetCurrentBeforeStarting()
{
$bar = new ProgressBar($this->getOutputStream());
$bar->setCurrent(15);
$bar->setProgress(15);
$this->assertNotNull($bar->getStartTime());
}
/**
@ -160,8 +240,8 @@ class ProgressBarTest extends \PHPUnit_Framework_TestCase
{
$bar = new ProgressBar($output = $this->getOutputStream(), 50);
$bar->start();
$bar->setCurrent(15);
$bar->setCurrent(10);
$bar->setProgress(15);
$bar->setProgress(10);
}
public function testRedrawFrequency()
@ -171,7 +251,7 @@ class ProgressBarTest extends \PHPUnit_Framework_TestCase
$bar->setRedrawFrequency(2);
$bar->start();
$bar->setCurrent(1);
$bar->setProgress(1);
$bar->advance(2);
$bar->advance(2);
$bar->advance(1);
@ -200,7 +280,7 @@ class ProgressBarTest extends \PHPUnit_Framework_TestCase
{
$bar = new ProgressBar($output = $this->getOutputStream(), 50);
$bar->start();
$bar->setCurrent(25);
$bar->setProgress(25);
$bar->clear();
rewind($output->getStream());
@ -231,13 +311,63 @@ class ProgressBarTest extends \PHPUnit_Framework_TestCase
}
public function testNonDecoratedOutput()
{
$bar = new ProgressBar($output = $this->getOutputStream(false), 200);
$bar->start();
for ($i = 0; $i < 200; $i++) {
$bar->advance();
}
$bar->finish();
rewind($output->getStream());
$this->assertEquals(
" 0/200 [>---------------------------] 0%\n".
" 20/200 [==>-------------------------] 10%\n".
" 40/200 [=====>----------------------] 20%\n".
" 60/200 [========>-------------------] 30%\n".
" 80/200 [===========>----------------] 40%\n".
" 100/200 [==============>-------------] 50%\n".
" 120/200 [================>-----------] 60%\n".
" 140/200 [===================>--------] 70%\n".
" 160/200 [======================>-----] 80%\n".
" 180/200 [=========================>--] 90%\n".
" 200/200 [============================] 100%",
stream_get_contents($output->getStream())
);
}
public function testNonDecoratedOutputWithClear()
{
$bar = new ProgressBar($output = $this->getOutputStream(false), 50);
$bar->start();
$bar->setProgress(25);
$bar->clear();
$bar->setProgress(50);
$bar->finish();
rewind($output->getStream());
$this->assertEquals(
" 0/50 [>---------------------------] 0%\n".
" 25/50 [==============>-------------] 50%\n".
" 50/50 [============================] 100%",
stream_get_contents($output->getStream())
);
}
public function testNonDecoratedOutputWithoutMax()
{
$bar = new ProgressBar($output = $this->getOutputStream(false));
$bar->start();
$bar->advance();
rewind($output->getStream());
$this->assertEquals('', stream_get_contents($output->getStream()));
$this->assertEquals(
" 0 [>---------------------------]\n".
" 1 [->--------------------------]",
stream_get_contents($output->getStream())
);
}
public function testParallelBars()
@ -299,10 +429,32 @@ class ProgressBarTest extends \PHPUnit_Framework_TestCase
);
}
public function testWithoutMax()
{
$output = $this->getOutputStream();
$bar = new ProgressBar($output);
$bar->start();
$bar->advance();
$bar->advance();
$bar->advance();
$bar->finish();
rewind($output->getStream());
$this->assertEquals(
rtrim($this->generateOutput(' 0 [>---------------------------]')).
rtrim($this->generateOutput(' 1 [->--------------------------]')).
rtrim($this->generateOutput(' 2 [-->-------------------------]')).
rtrim($this->generateOutput(' 3 [--->------------------------]')).
rtrim($this->generateOutput(' 3 [============================]')),
stream_get_contents($output->getStream())
);
}
public function testAddingPlaceholderFormatter()
{
ProgressBar::setPlaceholderFormatterDefinition('remaining_steps', function (ProgressBar $bar) {
return $bar->getMaxSteps() - $bar->getStep();
return $bar->getMaxSteps() - $bar->getProgress();
});
$bar = new ProgressBar($output = $this->getOutputStream(), 3);
$bar->setFormat(' %remaining_steps% [%bar%]');
@ -432,9 +584,9 @@ class ProgressBarTest extends \PHPUnit_Framework_TestCase
);
}
protected function getOutputStream($decorated = true)
protected function getOutputStream($decorated = true, $verbosity = StreamOutput::VERBOSITY_NORMAL)
{
return new StreamOutput(fopen('php://memory', 'r+', false), StreamOutput::VERBOSITY_NORMAL, $decorated);
return new StreamOutput(fopen('php://memory', 'r+', false), $verbosity, $decorated);
}
protected function generateOutput($expected)

@ -20,10 +20,12 @@
},
"require-dev": {
"symfony/event-dispatcher": "~2.1",
"symfony/process": "~2.1",
"psr/log": "~1.0"
},
"suggest": {
"symfony/event-dispatcher": "",
"symfony/process": "",
"psr/log": "For using the console logger"
},
"autoload": {
@ -33,7 +35,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.5-dev"
"dev-master": "2.6-dev"
}
}
}

@ -1,6 +1,11 @@
CHANGELOG
=========
2.6.0
-----
* added LockHandler
2.3.12
------
@ -10,7 +15,7 @@ CHANGELOG
-----
* added the dumpFile() method to atomically write files
2.2.0
-----

@ -28,9 +28,9 @@ class Filesystem
*
* By default, if the target already exists, it is not overridden.
*
* @param string $originFile The original filename
* @param string $targetFile The target filename
* @param bool $override Whether to override an existing file or not
* @param string $originFile The original filename
* @param string $targetFile The target filename
* @param bool $override Whether to override an existing file or not
*
* @throws FileNotFoundException When originFile doesn't exist
* @throws IOException When copy fails
@ -51,10 +51,16 @@ class Filesystem
if ($doCopy) {
// https://bugs.php.net/bug.php?id=64634
$source = fopen($originFile, 'r');
if (false === $source = @fopen($originFile, 'r')) {
throw new IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading.', $originFile, $targetFile), 0, null, $originFile);
}
// Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default
$target = fopen($targetFile, 'w', null, stream_context_create(array('ftp' => array('overwrite' => true))));
stream_copy_to_stream($source, $target);
if (false === $target = @fopen($targetFile, 'w', null, stream_context_create(array('ftp' => array('overwrite' => true))))) {
throw new IOException(sprintf('Failed to copy "%s" to "%s" because target file could not be opened for writing.', $originFile, $targetFile), 0, null, $originFile);
}
$bytesCopied = stream_copy_to_stream($source, $target);
fclose($source);
fclose($target);
unset($source, $target);
@ -62,6 +68,10 @@ class Filesystem
if (!is_file($targetFile)) {
throw new IOException(sprintf('Failed to copy "%s" to "%s".', $originFile, $targetFile), 0, null, $originFile);
}
if (stream_is_local($originFile) && $bytesCopied !== filesize($originFile)) {
throw new IOException(sprintf('Failed to copy the whole content of "%s" to "%s %g bytes copied".', $originFile, $targetFile, $bytesCopied), 0, null, $originFile);
}
}
}
@ -98,7 +108,7 @@ class Filesystem
*
* @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to check
*
* @return bool true if the file exists, false otherwise
* @return bool true if the file exists, false otherwise
*/
public function exists($files)
{
@ -246,9 +256,9 @@ class Filesystem
/**
* Renames a file or a directory.
*
* @param string $origin The origin filename or directory
* @param string $target The new filename or directory
* @param bool $overwrite Whether to overwrite the target if it already exists
* @param string $origin The origin filename or directory
* @param string $target The new filename or directory
* @param bool $overwrite Whether to overwrite the target if it already exists
*
* @throws IOException When target file or directory already exists
* @throws IOException When origin cannot be renamed
@ -268,17 +278,18 @@ class Filesystem
/**
* Creates a symbolic link or copy a directory.
*
* @param string $originDir The origin directory path
* @param string $targetDir The symbolic link name
* @param bool $copyOnWindows Whether to copy files if on Windows
* @param string $originDir The origin directory path
* @param string $targetDir The symbolic link name
* @param bool $copyOnWindows Whether to copy files if on Windows
*
* @throws IOException When symlink fails
*/
public function symlink($originDir, $targetDir, $copyOnWindows = false)
{
if (!function_exists('symlink') && $copyOnWindows) {
$this->mirror($originDir, $targetDir);
$onWindows = strtoupper(substr(php_uname('s'), 0, 3)) === 'WIN';
if ($onWindows && $copyOnWindows) {
$this->mirror($originDir, $targetDir);
return;
}
@ -301,9 +312,12 @@ class Filesystem
throw new IOException('Unable to create symlink due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?');
}
}
throw new IOException(sprintf('Failed to create symbolic link from "%s" to "%s".', $originDir, $targetDir), 0, null, $targetDir);
}
if (!file_exists($targetDir)) {
throw new IOException(sprintf('Symbolic link "%s" is created but appears to be broken.', $targetDir), 0, null, $targetDir);
}
}
}
@ -354,10 +368,10 @@ class Filesystem
* @param string $targetDir The target directory
* @param \Traversable $iterator A Traversable instance
* @param array $options An array of boolean options
* Valid options are:
* - $options['override'] Whether to override an existing file on copy or not (see copy())
* - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink())
* - $options['delete'] Whether to delete files that are not in the source directory (defaults to false)
* Valid options are:
* - $options['override'] Whether to override an existing file on copy or not (see copy())
* - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink())
* - $options['delete'] Whether to delete files that are not in the source directory (defaults to false)
*
* @throws IOException When file type is unknown
*/
@ -382,7 +396,7 @@ class Filesystem
}
$copyOnWindows = false;
if (isset($options['copy_on_windows']) && !function_exists('symlink')) {
if (isset($options['copy_on_windows'])) {
$copyOnWindows = $options['copy_on_windows'];
}
@ -391,6 +405,10 @@ class Filesystem
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST);
}
if ($this->exists($originDir)) {
$this->mkdir($targetDir);
}
foreach ($iterator as $file) {
$target = str_replace($originDir, $targetDir, $file->getPathname());
@ -441,11 +459,12 @@ class Filesystem
/**
* Atomically dumps content into a file.
*
* @param string $filename The file to be written to.
* @param string $content The data to write into the file.
* @param null|int $mode The file mode (octal). If null, file permissions are not modified
* Deprecated since version 2.3.12, to be removed in 3.0.
* @throws IOException If the file cannot be written to.
* @param string $filename The file to be written to.
* @param string $content The data to write into the file.
* @param null|int $mode The file mode (octal). If null, file permissions are not modified
* Deprecated since version 2.3.12, to be removed in 3.0.
*
* @throws IOException If the file cannot be written to.
*/
public function dumpFile($filename, $content, $mode = 0666)
{

@ -0,0 +1,111 @@
<?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\Component\Filesystem;
use Symfony\Component\Filesystem\Exception\IOException;
/**
* LockHandler class provides a simple abstraction to lock anything by means of
* a file lock.
*
* A locked file is created based on the lock name when calling lock(). Other
* lock handlers will not be able to lock the same name until it is released
* (explicitly by calling release() or implicitly when the instance holding the
* lock is destroyed).
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
* @author Romain Neutron <imprec@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class LockHandler
{
private $file;
private $handle;
/**
* @param string $name The lock name
* @param string|null $lockPath The directory to store the lock. Default values will use temporary directory
* @throws IOException If the lock directory could not be created or is not writable
*/
public function __construct($name, $lockPath = null)
{
$lockPath = $lockPath ?: sys_get_temp_dir();
if (!is_dir($lockPath)) {
$fs = new Filesystem();
$fs->mkdir($lockPath);
}
if (!is_writable($lockPath)) {
throw new IOException(sprintf('The directory "%s" is not writable.', $lockPath), 0, null, $lockPath);
}
$this->file = sprintf('%s/sf.%s.%s.lock', $lockPath, preg_replace('/[^a-z0-9\._-]+/i', '-', $name), hash('sha256', $name));
}
/**
* Lock the resource
*
* @param bool $blocking wait until the lock is released
* @return bool Returns true if the lock was acquired, false otherwise
* @throws IOException If the lock file could not be created or opened
*/
public function lock($blocking = false)
{
if ($this->handle) {
return true;
}
// Silence both userland and native PHP error handlers
$errorLevel = error_reporting(0);
set_error_handler('var_dump', 0);
if (!$this->handle = fopen($this->file, 'r')) {
if ($this->handle = fopen($this->file, 'x')) {
chmod($this->file, 0444);
} elseif (!$this->handle = fopen($this->file, 'r')) {
usleep(100); // Give some time for chmod() to complete
$this->handle = fopen($this->file, 'r');
}
}
restore_error_handler();
error_reporting($errorLevel);
if (!$this->handle) {
$error = error_get_last();
throw new IOException($error['message'], 0, null, $this->file);
}
// On Windows, even if PHP doc says the contrary, LOCK_NB works, see
// https://bugs.php.net/54129
if (!flock($this->handle, LOCK_EX | ($blocking ? 0 : LOCK_NB))) {
fclose($this->handle);
$this->handle = null;
return false;
}
return true;
}
/**
* Release the resource
*/
public function release()
{
if ($this->handle) {
flock($this->handle, LOCK_UN | LOCK_NB);
fclose($this->handle);
$this->handle = null;
}
}
}

@ -18,6 +18,8 @@ $filesystem->touch($files, $time = null, $atime = null);
$filesystem->remove($files);
$filesystem->exists($files);
$filesystem->chmod($files, $mode, $umask = 0000, $recursive = false);
$filesystem->chown($files, $user, $recursive = false);

@ -53,6 +53,27 @@ class FilesystemTest extends FilesystemTestCase
$this->filesystem->copy($sourceFilePath, $targetFilePath);
}
/**
* @expectedException \Symfony\Component\Filesystem\Exception\IOException
*/
public function testCopyUnreadableFileFails()
{
// skip test on Windows; PHP can't easily set file as unreadable on Windows
if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
$this->markTestSkipped('This test cannot run on Windows.');
}
$sourceFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_source_file';
$targetFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_target_file';
file_put_contents($sourceFilePath, 'SOURCE FILE');
// make sure target cannot be read
$this->filesystem->chmod($sourceFilePath, 0222);
$this->filesystem->copy($sourceFilePath, $targetFilePath);
}
public function testCopyOverridesExistingFileIfModified()
{
$sourceFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_source_file';
@ -106,6 +127,33 @@ class FilesystemTest extends FilesystemTestCase
$this->assertEquals('SOURCE FILE', file_get_contents($targetFilePath));
}
/**
* @expectedException \Symfony\Component\Filesystem\Exception\IOException
*/
public function testCopyWithOverrideWithReadOnlyTargetFails()
{
// skip test on Windows; PHP can't easily set file as unwritable on Windows
if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
$this->markTestSkipped('This test cannot run on Windows.');
}
$sourceFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_source_file';
$targetFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_target_file';
file_put_contents($sourceFilePath, 'SOURCE FILE');
file_put_contents($targetFilePath, 'TARGET FILE');
// make sure both files have the same modification time
$modificationTime = time() - 1000;
touch($sourceFilePath, $modificationTime);
touch($targetFilePath, $modificationTime);
// make sure target is read-only
$this->filesystem->chmod($targetFilePath, 0444);
$this->filesystem->copy($sourceFilePath, $targetFilePath, true);
}
public function testCopyCreatesTargetDirectoryIfItDoesNotExist()
{
$sourceFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_source_file';
@ -799,6 +847,21 @@ class FilesystemTest extends FilesystemTestCase
$this->assertFalse($this->filesystem->exists($targetPath.'directory'.DIRECTORY_SEPARATOR.'file1'));
}
public function testMirrorCreatesEmptyDirectory()
{
$sourcePath = $this->workspace.DIRECTORY_SEPARATOR.'source'.DIRECTORY_SEPARATOR;
mkdir($sourcePath);
$targetPath = $this->workspace.DIRECTORY_SEPARATOR.'target'.DIRECTORY_SEPARATOR;
$this->filesystem->mirror($sourcePath, $targetPath);
$this->assertTrue(is_dir($targetPath));
$this->filesystem->remove($sourcePath);
}
public function testMirrorCopiesLinks()
{
$this->markAsSkippedIfSymlinkIsMissing();

@ -0,0 +1,85 @@
<?php
namespace Symfony\Component\Filesystem\Tests;
use Symfony\Component\Filesystem\LockHandler;
class LockHandlerTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException Symfony\Component\Filesystem\Exception\IOException
* @expectedExceptionMessage Failed to create "/a/b/c/d/e": mkdir(): Permission denied.
*/
public function testConstructWhenRepositoryDoesNotExist()
{
new LockHandler('lock', '/a/b/c/d/e');
}
/**
* @expectedException Symfony\Component\Filesystem\Exception\IOException
* @expectedExceptionMessage The directory "/" is not writable.
*/
public function testConstructWhenRepositoryIsNotWriteable()
{
new LockHandler('lock', '/');
}
public function testConstructSanitizeName()
{
$lock = new LockHandler('<?php echo "% hello word ! %" ?>');
$file = sprintf('%s/sf.-php-echo-hello-word-.4b3d9d0d27ddef3a78a64685dda3a963e478659a9e5240feaf7b4173a8f28d5f.lock', sys_get_temp_dir());
// ensure the file does not exist before the lock
@unlink($file);
$lock->lock();
$this->assertFileExists($file);
$lock->release();
}
public function testLockRelease()
{
$name = 'symfony-test-filesystem.lock';
$l1 = new LockHandler($name);
$l2 = new LockHandler($name);
$this->assertTrue($l1->lock());
$this->assertFalse($l2->lock());
$l1->release();
$this->assertTrue($l2->lock());
$l2->release();
}
public function testLockTwice()
{
$name = 'symfony-test-filesystem.lock';
$lockHandler = new LockHandler($name);
$this->assertTrue($lockHandler->lock());
$this->assertTrue($lockHandler->lock());
$lockHandler->release();
}
public function testLockIsReleased()
{
$name = 'symfony-test-filesystem.lock';
$l1 = new LockHandler($name);
$l2 = new LockHandler($name);
$this->assertTrue($l1->lock());
$this->assertFalse($l2->lock());
$l1 = null;
$this->assertTrue($l2->lock());
$l2->release();
}
}

@ -25,7 +25,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.5-dev"
"dev-master": "2.6-dev"
}
}
}

@ -31,11 +31,11 @@ class PhpProcess extends Process
/**
* Constructor.
*
* @param string $script The PHP script to run (as a string)
* @param string $cwd The working directory
* @param array $env The environment variables
* @param int $timeout The timeout in seconds
* @param array $options An array of options for proc_open
* @param string $script The PHP script to run (as a string)
* @param string $cwd The working directory
* @param array $env The environment variables
* @param int $timeout The timeout in seconds
* @param array $options An array of options for proc_open
*
* @api
*/

@ -0,0 +1,74 @@
<?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\Component\Process\Pipes;
/**
* @author Romain Neutron <imprec@gmail.com>
*
* @internal
*/
abstract class AbstractPipes implements PipesInterface
{
/** @var array */
public $pipes = array();
/** @var string */
protected $inputBuffer = '';
/** @var resource|null */
protected $input;
/** @var bool */
private $blocked = true;
/**
* {@inheritdoc}
*/
public function close()
{
foreach ($this->pipes as $pipe) {
fclose($pipe);
}
$this->pipes = array();
}
/**
* Returns true if a system call has been interrupted.
*
* @return bool
*/
protected function hasSystemCallBeenInterrupted()
{
$lastError = error_get_last();
// stream_select returns false when the `select` system call is interrupted by an incoming signal
return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call');
}
/**
* Unblocks streams
*/
protected function unblock()
{
if (!$this->blocked) {
return;
}
foreach ($this->pipes as $pipe) {
stream_set_blocking($pipe, 0);
}
if (null !== $this->input) {
stream_set_blocking($this->input, 0);
}
$this->blocked = false;
}
}

@ -0,0 +1,60 @@
<?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\Component\Process\Pipes;
/**
* PipesInterface manages descriptors and pipes for the use of proc_open.
*
* @author Romain Neutron <imprec@gmail.com>
*
* @internal
*/
interface PipesInterface
{
const CHUNK_SIZE = 16384;
/**
* Returns an array of descriptors for the use of proc_open.
*
* @return array
*/
public function getDescriptors();
/**
* Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
*
* @return string[]
*/
public function getFiles();
/**
* Reads data in file handles and pipes.
*
* @param bool $blocking Whether to use blocking calls or not.
* @param bool $close Whether to close pipes if they've reached EOF.
*
* @return string[] An array of read data indexed by their fd.
*/
public function readAndWrite($blocking, $close = false);
/**
* Returns if the current state has open file handles or pipes.
*
* @return bool
*/
public function areOpen();
/**
* Closes file handles and pipes.
*/
public function close();
}

@ -0,0 +1,214 @@
<?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\Component\Process\Pipes;
use Symfony\Component\Process\Process;
/**
* UnixPipes implementation uses unix pipes as handles.
*
* @author Romain Neutron <imprec@gmail.com>
*
* @internal
*/
class UnixPipes extends AbstractPipes
{
/** @var bool */
private $ttyMode;
/** @var bool */
private $ptyMode;
/** @var bool */
private $disableOutput;
public function __construct($ttyMode, $ptyMode, $input, $disableOutput)
{
$this->ttyMode = (bool) $ttyMode;
$this->ptyMode = (bool) $ptyMode;
$this->disableOutput = (bool) $disableOutput;
if (is_resource($input)) {
$this->input = $input;
} else {
$this->inputBuffer = (string) $input;
}
}
public function __destruct()
{
$this->close();
}
/**
* {@inheritdoc}
*/
public function getDescriptors()
{
if ($this->disableOutput) {
$nullstream = fopen('/dev/null', 'c');
return array(
array('pipe', 'r'),
$nullstream,
$nullstream,
);
}
if ($this->ttyMode) {
return array(
array('file', '/dev/tty', 'r'),
array('file', '/dev/tty', 'w'),
array('file', '/dev/tty', 'w'),
);
}
if ($this->ptyMode && Process::isPtySupported()) {
return array(
array('pty'),
array('pty'),
array('pty'),
);
}
return array(
array('pipe', 'r'),
array('pipe', 'w'), // stdout
array('pipe', 'w'), // stderr
);
}
/**
* {@inheritdoc}
*/
public function getFiles()
{
return array();
}
/**
* {@inheritdoc}
*/
public function readAndWrite($blocking, $close = false)
{
// only stdin is left open, job has been done !
// we can now close it
if (1 === count($this->pipes) && array(0) === array_keys($this->pipes)) {
fclose($this->pipes[0]);
unset($this->pipes[0]);
}
if (empty($this->pipes)) {
return array();
}
$this->unblock();
$read = array();
if (null !== $this->input) {
// if input is a resource, let's add it to stream_select argument to
// fill a buffer
$r = array_merge($this->pipes, array('input' => $this->input));
} else {
$r = $this->pipes;
}
// discard read on stdin
unset ($r[0]);
$w = isset($this->pipes[0]) ? array($this->pipes[0]) : null;
$e = null;
// let's have a look if something changed in streams
if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
// if a system call has been interrupted, forget about it, let's try again
// otherwise, an error occurred, let's reset pipes
if (!$this->hasSystemCallBeenInterrupted()) {
$this->pipes = array();
}
return $read;
}
// nothing has changed
if (0 === $n) {
return $read;
}
foreach ($r as $pipe) {
// prior PHP 5.4 the array passed to stream_select is modified and
// lose key association, we have to find back the key
$type = (false !== $found = array_search($pipe, $this->pipes)) ? $found : 'input';
$data = '';
while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) {
$data .= $dataread;
}
if ('' !== $data) {
if ($type === 'input') {
$this->inputBuffer .= $data;
} else {
$read[$type] = $data;
}
}
if (false === $data || (true === $close && feof($pipe) && '' === $data)) {
if ($type === 'input') {
// no more data to read on input resource
// use an empty buffer in the next reads
$this->input = null;
} else {
fclose($this->pipes[$type]);
unset($this->pipes[$type]);
}
}
}
if (null !== $w && 0 < count($w)) {
while ($len = strlen($this->inputBuffer)) {
$written = fwrite($w[0], $this->inputBuffer, 2 << 18); // write 512k
if ($written > 0) {
$this->inputBuffer = (string) substr($this->inputBuffer, $written);
} else {
break;
}
}
}
// no input to read on resource, buffer is empty and stdin still open
if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) {
fclose($this->pipes[0]);
unset($this->pipes[0]);
}
return $read;
}
/**
* {@inheritdoc}
*/
public function areOpen()
{
return (bool) $this->pipes;
}
/**
* Creates a new UnixPipes instance
*
* @param Process $process
* @param string|resource $input
*
* @return UnixPipes
*/
public static function create(Process $process, $input)
{
return new static($process->isTty(), $process->isPty(), $input, $process->isOutputDisabled());
}
}

@ -0,0 +1,254 @@
<?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\Component\Process\Pipes;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\RuntimeException;
/**
* WindowsPipes implementation uses temporary files as handles.
*
* @see https://bugs.php.net/bug.php?id=51800
* @see https://bugs.php.net/bug.php?id=65650
*
* @author Romain Neutron <imprec@gmail.com>
*
* @internal
*/
class WindowsPipes extends AbstractPipes
{
/** @var array */
private $files = array();
/** @var array */
private $fileHandles = array();
/** @var array */
private $readBytes = array(
Process::STDOUT => 0,
Process::STDERR => 0,
);
/** @var bool */
private $disableOutput;
public function __construct($disableOutput, $input)
{
$this->disableOutput = (bool) $disableOutput;
if (!$this->disableOutput) {
// Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
// Workaround for this problem is to use temporary files instead of pipes on Windows platform.
//
// @see https://bugs.php.net/bug.php?id=51800
$this->files = array(
Process::STDOUT => tempnam(sys_get_temp_dir(), 'sf_proc_stdout'),
Process::STDERR => tempnam(sys_get_temp_dir(), 'sf_proc_stderr'),
);
foreach ($this->files as $offset => $file) {
$this->fileHandles[$offset] = fopen($this->files[$offset], 'rb');
if (false === $this->fileHandles[$offset]) {
throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable');
}
}
}
if (is_resource($input)) {
$this->input = $input;
} else {
$this->inputBuffer = $input;
}
}
public function __destruct()
{
$this->close();
$this->removeFiles();
}
/**
* {@inheritdoc}
*/
public function getDescriptors()
{
if ($this->disableOutput) {
$nullstream = fopen('NUL', 'c');
return array(
array('pipe', 'r'),
$nullstream,
$nullstream,
);
}
// We're not using pipe on Windows platform as it hangs (https://bugs.php.net/bug.php?id=51800)
// We're not using file handles as it can produce corrupted output https://bugs.php.net/bug.php?id=65650
// So we redirect output within the commandline and pass the nul device to the process
return array(
array('pipe', 'r'),
array('file', 'NUL', 'w'),
array('file', 'NUL', 'w'),
);
}
/**
* {@inheritdoc}
*/
public function getFiles()
{
return $this->files;
}
/**
* {@inheritdoc}
*/
public function readAndWrite($blocking, $close = false)
{
$this->write($blocking, $close);
$read = array();
$fh = $this->fileHandles;
foreach ($fh as $type => $fileHandle) {
if (0 !== fseek($fileHandle, $this->readBytes[$type])) {
continue;
}
$data = '';
$dataread = null;
while (!feof($fileHandle)) {
if (false !== $dataread = fread($fileHandle, self::CHUNK_SIZE)) {
$data .= $dataread;
}
}
if (0 < $length = strlen($data)) {
$this->readBytes[$type] += $length;
$read[$type] = $data;
}
if (false === $dataread || (true === $close && feof($fileHandle) && '' === $data)) {
fclose($this->fileHandles[$type]);
unset($this->fileHandles[$type]);
}
}
return $read;
}
/**
* {@inheritdoc}
*/
public function areOpen()
{
return (bool) $this->pipes && (bool) $this->fileHandles;
}
/**
* {@inheritdoc}
*/
public function close()
{
parent::close();
foreach ($this->fileHandles as $handle) {
fclose($handle);
}
$this->fileHandles = array();
}
/**
* Creates a new WindowsPipes instance.
*
* @param Process $process The process
* @param $input
*
* @return WindowsPipes
*/
public static function create(Process $process, $input)
{
return new static($process->isOutputDisabled(), $input);
}
/**
* Removes temporary files
*/
private function removeFiles()
{
foreach ($this->files as $filename) {
if (file_exists($filename)) {
@unlink($filename);
}
}
$this->files = array();
}
/**
* Writes input to stdin
*
* @param bool $blocking
* @param bool $close
*/
private function write($blocking, $close)
{
if (empty($this->pipes)) {
return;
}
$this->unblock();
$r = null !== $this->input ? array('input' => $this->input) : null;
$w = isset($this->pipes[0]) ? array($this->pipes[0]) : null;
$e = null;
// let's have a look if something changed in streams
if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
// if a system call has been interrupted, forget about it, let's try again
// otherwise, an error occurred, let's reset pipes
if (!$this->hasSystemCallBeenInterrupted()) {
$this->pipes = array();
}
return;
}
// nothing has changed
if (0 === $n) {
return;
}
if (null !== $w && 0 < count($r)) {
$data = '';
while ($dataread = fread($r['input'], self::CHUNK_SIZE)) {
$data .= $dataread;
}
$this->inputBuffer .= $data;
if (false === $data || (true === $close && feof($r['input']) && '' === $data)) {
// no more data to read on input resource
// use an empty buffer in the next reads
unset($this->input);
}
}
if (null !== $w && 0 < count($w)) {
while ($len = strlen($this->inputBuffer)) {
$written = fwrite($w[0], $this->inputBuffer, 2 << 18);
if ($written > 0) {
$this->inputBuffer = (string) substr($this->inputBuffer, $written);
} else {
break;
}
}
}
// no input to read on resource, buffer is empty and stdin still open
if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) {
fclose($this->pipes[0]);
unset($this->pipes[0]);
}
}
}

@ -16,12 +16,16 @@ use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\Pipes\PipesInterface;
use Symfony\Component\Process\Pipes\UnixPipes;
use Symfony\Component\Process\Pipes\WindowsPipes;
/**
* Process is a thin wrapper around proc_* functions to easily
* start independent PHP processes.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Romain Neutron <imprec@gmail.com>
*
* @api
*/
@ -67,7 +71,7 @@ class Process
private $pty;
private $useFileHandles = false;
/** @var ProcessPipes */
/** @var PipesInterface */
private $processPipes;
private $latestSignal;
@ -127,12 +131,12 @@ class Process
/**
* Constructor.
*
* @param string $commandline The command line to run
* @param string|null $cwd The working directory or null to use the working dir of the current PHP process
* @param array|null $env The environment variables or null to inherit
* @param string|null $input The input
* @param int|float|null $timeout The timeout in seconds or null to disable
* @param array $options An array of options for proc_open
* @param string $commandline The command line to run
* @param string|null $cwd The working directory or null to use the working dir of the current PHP process
* @param array|null $env The environment variables or null to inherit
* @param string|null $input The input
* @param int|float|null $timeout The timeout in seconds or null to disable
* @param array $options An array of options for proc_open
*
* @throws RuntimeException When proc_open is not installed
*
@ -191,7 +195,7 @@ class Process
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @return int The exit status code
* @return int The exit status code
*
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process stopped after receiving signal
@ -291,13 +295,10 @@ class Process
}
$this->status = self::STATUS_STARTED;
$this->processPipes->unblock();
if ($this->tty) {
return;
}
$this->processPipes->write(false, $this->input);
$this->updateStatus(false);
$this->checkTimeout();
}
@ -338,7 +339,7 @@ class Process
*
* @param callable|null $callback A valid PHP callback
*
* @return int The exitcode of the process
* @return int The exitcode of the process
*
* @throws RuntimeException When process timed out
* @throws RuntimeException When process stopped after receiving signal
@ -355,7 +356,7 @@ class Process
do {
$this->checkTimeout();
$running = defined('PHP_WINDOWS_VERSION_BUILD') ? $this->isRunning() : $this->processPipes->hasOpenHandles();
$running = defined('PHP_WINDOWS_VERSION_BUILD') ? $this->isRunning() : $this->processPipes->areOpen();
$close = !defined('PHP_WINDOWS_VERSION_BUILD') || !$running;
$this->readPipes(true, $close);
} while ($running);
@ -374,7 +375,7 @@ class Process
/**
* Returns the Pid (process identifier), if applicable.
*
* @return int|null The process id if running, null otherwise
* @return int|null The process id if running, null otherwise
*
* @throws RuntimeException In case --enable-sigchild is activated
*/
@ -392,7 +393,7 @@ class Process
/**
* Sends a POSIX signal to the process.
*
* @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php)
* @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php)
*
* @return Process
*
@ -579,7 +580,7 @@ class Process
/**
* Returns the exit code returned by the process.
*
* @return null|int The exit status code, null if the Process is not terminated
* @return null|int The exit status code, null if the Process is not terminated
*
* @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
*
@ -621,7 +622,7 @@ class Process
/**
* Checks if the process ended successfully.
*
* @return bool true if the process ended successfully, false otherwise
* @return bool true if the process ended successfully, false otherwise
*
* @api
*/
@ -723,7 +724,7 @@ class Process
/**
* Checks if the process is currently running.
*
* @return bool true if the process is currently running, false otherwise
* @return bool true if the process is currently running, false otherwise
*/
public function isRunning()
{
@ -739,7 +740,7 @@ class Process
/**
* Checks if the process has been started with no regard to the current state.
*
* @return bool true if status is ready, false otherwise
* @return bool true if status is ready, false otherwise
*/
public function isStarted()
{
@ -749,7 +750,7 @@ class Process
/**
* Checks if the process is terminated.
*
* @return bool true if process is terminated, false otherwise
* @return bool true if process is terminated, false otherwise
*/
public function isTerminated()
{
@ -775,10 +776,10 @@ class Process
/**
* Stops the process.
*
* @param int|float $timeout The timeout in seconds
* @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL
* @param int|float $timeout The timeout in seconds
* @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL
*
* @return int The exit-code of the process
* @return int The exit-code of the process
*
* @throws RuntimeException if the process got signaled
*/
@ -888,7 +889,7 @@ class Process
*
* To disable the timeout, set this value to null.
*
* @param int|float|null $timeout The timeout in seconds
* @param int|float|null $timeout The timeout in seconds
*
* @return self The current Process instance
*
@ -927,7 +928,7 @@ class Process
/**
* Enables or disables the TTY mode.
*
* @param bool $tty True to enabled and false to disable
* @param bool $tty True to enabled and false to disable
*
* @return self The current Process instance
*
@ -947,7 +948,7 @@ class Process
/**
* Checks if the TTY mode is enabled.
*
* @return bool true if the TTY mode is enabled, false otherwise
* @return bool true if the TTY mode is enabled, false otherwise
*/
public function isTty()
{
@ -1072,8 +1073,6 @@ class Process
/**
* Sets the contents of STDIN.
*
* Deprecation: As of Symfony 2.5, this method only accepts scalar values.
*
* @param string|null $stdin The new contents
*
* @return self The current Process instance
@ -1150,7 +1149,7 @@ class Process
/**
* Sets whether or not Windows compatibility is enabled.
*
* @param bool $enhance
* @param bool $enhance
*
* @return self The current Process instance
*/
@ -1178,7 +1177,7 @@ class Process
* determine the success of a process when PHP has been compiled with
* the --enable-sigchild option
*
* @param bool $enhance
* @param bool $enhance
*
* @return self The current Process instance
*/
@ -1250,8 +1249,12 @@ class Process
*/
private function getDescriptors()
{
$this->processPipes = new ProcessPipes($this->useFileHandles, $this->tty, $this->pty, $this->outputDisabled);
$descriptors = $this->processPipes->getDescriptors();
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
$this->processPipes = WindowsPipes::create($this, $this->input);
} else {
$this->processPipes = UnixPipes::create($this, $this->input);
}
$descriptors = $this->processPipes->getDescriptors($this->outputDisabled);
if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
// last exit code is output on the fourth pipe and caught to work around --enable-sigchild
@ -1296,7 +1299,7 @@ class Process
/**
* Updates the status of the process, reads pipes.
*
* @param bool $blocking Whether to use a blocking read call.
* @param bool $blocking Whether to use a blocking read call.
*/
protected function updateStatus($blocking)
{
@ -1360,16 +1363,12 @@ class Process
/**
* Reads pipes, executes callback.
*
* @param bool $blocking Whether to use blocking calls or not.
* @param bool $close Whether to close file handles or not.
* @param bool $blocking Whether to use blocking calls or not.
* @param bool $close Whether to close file handles or not.
*/
private function readPipes($blocking, $close)
{
if ($close) {
$result = $this->processPipes->readAndCloseHandles($blocking);
} else {
$result = $this->processPipes->read($blocking);
}
$result = $this->processPipes->readAndWrite($blocking, $close);
foreach ($result as $type => $data) {
if (3 == $type) {
@ -1393,7 +1392,7 @@ class Process
/**
* Closes process resource, closes file handles, sets the exitcode.
*
* @return int The exitcode
* @return int The exitcode
*/
private function close()
{
@ -1439,10 +1438,10 @@ class Process
/**
* Sends a POSIX signal to the process.
*
* @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php)
* @param bool $throwException Whether to throw exception in case signal failed
* @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php)
* @param bool $throwException Whether to throw exception in case signal failed
*
* @return bool True if the signal was sent successfully, false otherwise
* @return bool True if the signal was sent successfully, false otherwise
*
* @throws LogicException In case the process is not running
* @throws RuntimeException In case --enable-sigchild is activated

@ -1,382 +0,0 @@
<?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\Component\Process;
use Symfony\Component\Process\Exception\RuntimeException;
/**
* ProcessPipes manages descriptors and pipes for the use of proc_open.
*/
class ProcessPipes
{
/** @var array */
public $pipes = array();
/** @var array */
private $files = array();
/** @var array */
private $fileHandles = array();
/** @var array */
private $readBytes = array();
/** @var bool */
private $useFiles;
/** @var bool */
private $ttyMode;
/** @var bool */
private $ptyMode;
/** @var bool */
private $disableOutput;
const CHUNK_SIZE = 16384;
public function __construct($useFiles, $ttyMode, $ptyMode = false, $disableOutput = false)
{
$this->useFiles = (bool) $useFiles;
$this->ttyMode = (bool) $ttyMode;
$this->ptyMode = (bool) $ptyMode;
$this->disableOutput = (bool) $disableOutput;
// Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
// Workaround for this problem is to use temporary files instead of pipes on Windows platform.
//
// @see https://bugs.php.net/bug.php?id=51800
if ($this->useFiles && !$this->disableOutput) {
$this->files = array(
Process::STDOUT => tempnam(sys_get_temp_dir(), 'sf_proc_stdout'),
Process::STDERR => tempnam(sys_get_temp_dir(), 'sf_proc_stderr'),
);
foreach ($this->files as $offset => $file) {
$this->fileHandles[$offset] = fopen($this->files[$offset], 'rb');
if (false === $this->fileHandles[$offset]) {
throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable');
}
}
$this->readBytes = array(
Process::STDOUT => 0,
Process::STDERR => 0,
);
}
}
public function __destruct()
{
$this->close();
$this->removeFiles();
}
/**
* Sets non-blocking mode on pipes.
*/
public function unblock()
{
foreach ($this->pipes as $pipe) {
stream_set_blocking($pipe, 0);
}
}
/**
* Closes file handles and pipes.
*/
public function close()
{
$this->closeUnixPipes();
foreach ($this->fileHandles as $handle) {
fclose($handle);
}
$this->fileHandles = array();
}
/**
* Closes Unix pipes.
*
* Nothing happens in case file handles are used.
*/
public function closeUnixPipes()
{
foreach ($this->pipes as $pipe) {
fclose($pipe);
}
$this->pipes = array();
}
/**
* Returns an array of descriptors for the use of proc_open.
*
* @return array
*/
public function getDescriptors()
{
if ($this->disableOutput) {
$nullstream = fopen(defined('PHP_WINDOWS_VERSION_BUILD') ? 'NUL' : '/dev/null', 'c');
return array(
array('pipe', 'r'),
$nullstream,
$nullstream,
);
}
if ($this->useFiles) {
// We're not using pipe on Windows platform as it hangs (https://bugs.php.net/bug.php?id=51800)
// We're not using file handles as it can produce corrupted output https://bugs.php.net/bug.php?id=65650
// So we redirect output within the commandline and pass the nul device to the process
return array(
array('pipe', 'r'),
array('file', 'NUL', 'w'),
array('file', 'NUL', 'w'),
);
}
if ($this->ttyMode) {
return array(
array('file', '/dev/tty', 'r'),
array('file', '/dev/tty', 'w'),
array('file', '/dev/tty', 'w'),
);
} elseif ($this->ptyMode && Process::isPtySupported()) {
return array(
array('pty'),
array('pty'),
array('pty'),
);
}
return array(
array('pipe', 'r'), // stdin
array('pipe', 'w'), // stdout
array('pipe', 'w'), // stderr
);
}
/**
* Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
*
* @return array
*/
public function getFiles()
{
if ($this->useFiles) {
return $this->files;
}
return array();
}
/**
* Reads data in file handles and pipes.
*
* @param bool $blocking Whether to use blocking calls or not.
*
* @return array An array of read data indexed by their fd.
*/
public function read($blocking)
{
return array_replace($this->readStreams($blocking), $this->readFileHandles());
}
/**
* Reads data in file handles and pipes, closes them if EOF is reached.
*
* @param bool $blocking Whether to use blocking calls or not.
*
* @return array An array of read data indexed by their fd.
*/
public function readAndCloseHandles($blocking)
{
return array_replace($this->readStreams($blocking, true), $this->readFileHandles(true));
}
/**
* Returns if the current state has open file handles or pipes.
*
* @return bool
*/
public function hasOpenHandles()
{
if (!$this->useFiles) {
return (bool) $this->pipes;
}
return (bool) $this->pipes && (bool) $this->fileHandles;
}
/**
* Writes stdin data.
*
* @param bool $blocking Whether to use blocking calls or not.
* @param string|null $stdin The data to write.
*/
public function write($blocking, $stdin)
{
if (null === $stdin) {
fclose($this->pipes[0]);
unset($this->pipes[0]);
return;
}
$writePipes = array($this->pipes[0]);
unset($this->pipes[0]);
$stdinLen = strlen($stdin);
$stdinOffset = 0;
while ($writePipes) {
$r = null;
$w = $writePipes;
$e = null;
if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? ceil(Process::TIMEOUT_PRECISION * 1E6) : 0)) {
// if a system call has been interrupted, forget about it, let's try again
if ($this->hasSystemCallBeenInterrupted()) {
continue;
}
break;
}
// nothing has changed, let's wait until the process is ready
if (0 === $n) {
continue;
}
if ($w) {
$written = fwrite($writePipes[0], (binary) substr($stdin, $stdinOffset), 8192);
if (false !== $written) {
$stdinOffset += $written;
}
if ($stdinOffset >= $stdinLen) {
fclose($writePipes[0]);
$writePipes = null;
}
}
}
}
/**
* Reads data in file handles.
*
* @param bool $close Whether to close file handles or not.
*
* @return array An array of read data indexed by their fd.
*/
private function readFileHandles($close = false)
{
$read = array();
$fh = $this->fileHandles;
foreach ($fh as $type => $fileHandle) {
if (0 !== fseek($fileHandle, $this->readBytes[$type])) {
continue;
}
$data = '';
$dataread = null;
while (!feof($fileHandle)) {
if (false !== $dataread = fread($fileHandle, self::CHUNK_SIZE)) {
$data .= $dataread;
}
}
if (0 < $length = strlen($data)) {
$this->readBytes[$type] += $length;
$read[$type] = $data;
}
if (false === $dataread || (true === $close && feof($fileHandle) && '' === $data)) {
fclose($this->fileHandles[$type]);
unset($this->fileHandles[$type]);
}
}
return $read;
}
/**
* Reads data in file pipes streams.
*
* @param bool $blocking Whether to use blocking calls or not.
* @param bool $close Whether to close file handles or not.
*
* @return array An array of read data indexed by their fd.
*/
private function readStreams($blocking, $close = false)
{
if (empty($this->pipes)) {
usleep(Process::TIMEOUT_PRECISION * 1E4);
return array();
}
$read = array();
$r = $this->pipes;
$w = null;
$e = null;
// let's have a look if something changed in streams
if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? ceil(Process::TIMEOUT_PRECISION * 1E6) : 0)) {
// if a system call has been interrupted, forget about it, let's try again
// otherwise, an error occurred, let's reset pipes
if (!$this->hasSystemCallBeenInterrupted()) {
$this->pipes = array();
}
return $read;
}
// nothing has changed
if (0 === $n) {
return $read;
}
foreach ($r as $pipe) {
$type = array_search($pipe, $this->pipes);
$data = '';
while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) {
$data .= $dataread;
}
if ('' !== $data) {
$read[$type] = $data;
}
if (false === $data || (true === $close && feof($pipe) && '' === $data)) {
fclose($this->pipes[$type]);
unset($this->pipes[$type]);
}
}
return $read;
}
/**
* Returns true if a system call has been interrupted.
*
* @return bool
*/
private function hasSystemCallBeenInterrupted()
{
$lastError = error_get_last();
// stream_select returns false when the `select` system call is interrupted by an incoming signal
return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call');
}
/**
* Removes temporary files
*/
private function removeFiles()
{
foreach ($this->files as $filename) {
if (file_exists($filename)) {
@unlink($filename);
}
}
$this->files = array();
}
}

@ -87,6 +87,9 @@ class ProcessUtils
public static function validateInput($caller, $input)
{
if (null !== $input) {
if (is_resource($input)) {
return $input;
}
if (is_scalar($input)) {
return (string) $input;
}
@ -95,7 +98,7 @@ class ProcessUtils
return (string) $input;
}
throw new InvalidArgumentException(sprintf('%s only accepts strings.', $caller));
throw new InvalidArgumentException(sprintf('%s only accepts strings or stream resources.', $caller));
}
return $input;

@ -13,9 +13,9 @@ namespace Symfony\Component\Process\Tests;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Pipes\PipesInterface;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\ProcessPipes;
/**
* @author Robert Schönthal <seroscho@googlemail.com>
@ -80,7 +80,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
}
$duration = microtime(true) - $start;
$this->assertLessThan(1.8, $duration);
$this->assertLessThan(4, $duration);
}
public function testAllOutputIsActuallyReadOnTermination()
@ -90,9 +90,9 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
// has terminated so the internal pipes array is already empty. normally
// the call to start() will not read any data as the process will not have
// generated output, but this is non-deterministic so we must count it as
// a possibility. therefore we need 2 * ProcessPipes::CHUNK_SIZE plus
// a possibility. therefore we need 2 * PipesInterface::CHUNK_SIZE plus
// another byte which will never be read.
$expectedOutputSize = ProcessPipes::CHUNK_SIZE * 2 + 2;
$expectedOutputSize = PipesInterface::CHUNK_SIZE * 2 + 2;
$code = sprintf('echo str_repeat(\'*\', %d);', $expectedOutputSize);
$p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code)));
@ -158,6 +158,28 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expectedLength, strlen($p->getErrorOutput()));
}
/**
* @dataProvider pipesCodeProvider
*/
public function testSetStreamAsInput($code, $size)
{
$expected = str_repeat(str_repeat('*', 1024), $size).'!';
$expectedLength = (1024 * $size) + 1;
$stream = fopen('php://temporary', 'w+');
fwrite($stream, $expected);
rewind($stream);
$p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code)));
$p->setInput($stream);
$p->run();
fclose($stream);
$this->assertEquals($expectedLength, strlen($p->getOutput()));
$this->assertEquals($expectedLength, strlen($p->getErrorOutput()));
}
public function testSetInputWhileRunningThrowsAnException()
{
$process = $this->getProcess('php -r "usleep(500000);"');
@ -175,7 +197,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
/**
* @dataProvider provideInvalidInputValues
* @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
* @expectedExceptionMessage Symfony\Component\Process\Process::setInput only accepts strings.
* @expectedExceptionMessage Symfony\Component\Process\Process::setInput only accepts strings or stream resources.
*/
public function testInvalidInput($value)
{
@ -188,7 +210,6 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
return array(
array(array()),
array(new NonStringifiable()),
array(fopen('php://temporary', 'w')),
);
}
@ -1079,12 +1100,12 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
}
/**
* @param string $commandline
* @param null|string $cwd
* @param null|array $env
* @param null|string $input
* @param int $timeout
* @param array $options
* @param string $commandline
* @param null|string $cwd
* @param null|array $env
* @param null|string $input
* @param int $timeout
* @param array $options
*
* @return Process
*/

@ -215,7 +215,7 @@ class ProcessBuilderTest extends \PHPUnit_Framework_TestCase
/**
* @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
* @expectedExceptionMessage Symfony\Component\Process\ProcessBuilder::setInput only accepts strings.
* @expectedExceptionMessage Symfony\Component\Process\ProcessBuilder::setInput only accepts strings or stream resources.
*/
public function testInvalidInput()
{

@ -25,7 +25,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.5-dev"
"dev-master": "2.6-dev"
}
}
}

@ -28,7 +28,7 @@ class Dumper
/**
* Sets the indentation.
*
* @param int $num The amount of spaces to use for indentation of nested nodes.
* @param int $num The amount of spaces to use for indentation of nested nodes.
*/
public function setIndentation($num)
{
@ -38,13 +38,13 @@ class Dumper
/**
* Dumps a PHP value to YAML.
*
* @param mixed $input The PHP value
* @param int $inline The level where you switch to inline YAML
* @param int $indent The level of indentation (used internally)
* @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
* @param bool $objectSupport true if object support is enabled, false otherwise
* @param mixed $input The PHP value
* @param int $inline The level where you switch to inline YAML
* @param int $indent The level of indentation (used internally)
* @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
* @param bool $objectSupport true if object support is enabled, false otherwise
*
* @return string The YAML representation of the PHP value
* @return string The YAML representation of the PHP value
*/
public function dump($input, $inline = 0, $indent = 0, $exceptionOnInvalidType = false, $objectSupport = false)
{

@ -32,7 +32,7 @@ class Escaper
"\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17",
"\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f",
"\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9",);
private static $escaped = array('\\\\', '\\"', '\\\\', '\\"',
private static $escaped = array('\\\\', '\\"', '\\\\', '\\"',
"\\0", "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\a",
"\\b", "\\t", "\\n", "\\v", "\\f", "\\r", "\\x0e", "\\x0f",
"\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17",
@ -44,7 +44,7 @@ class Escaper
*
* @param string $value A PHP value
*
* @return bool True if the value would require double quotes.
* @return bool True if the value would require double quotes.
*/
public static function requiresDoubleQuoting($value)
{
@ -68,7 +68,7 @@ class Escaper
*
* @param string $value A PHP value
*
* @return bool True if the value would require single quotes.
* @return bool True if the value would require single quotes.
*/
public static function requiresSingleQuoting($value)
{

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save