},
{
"name": "guzzlehttp/promises",
- "version": "1.4.0",
+ "version": "1.4.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
- "reference": "60d379c243457e073cff02bc323a2a86cb355631"
+ "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/promises/zipball/60d379c243457e073cff02bc323a2a86cb355631",
- "reference": "60d379c243457e073cff02bc323a2a86cb355631",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d",
+ "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d",
"shasum": ""
},
"require": {
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
- "source": "https://github.com/guzzle/promises/tree/1.4.0"
+ "source": "https://github.com/guzzle/promises/tree/1.4.1"
},
- "time": "2020-09-30T07:37:28+00:00"
+ "time": "2021-03-07T09:25:29+00:00"
},
{
"name": "guzzlehttp/psr7",
- "version": "1.7.0",
+ "version": "1.8.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
- "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3"
+ "reference": "35ea11d335fd638b5882ff1725228b3d35496ab1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/psr7/zipball/53330f47520498c0ae1f61f7e2c90f55690c06a3",
- "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/35ea11d335fd638b5882ff1725228b3d35496ab1",
+ "reference": "35ea11d335fd638b5882ff1725228b3d35496ab1",
"shasum": ""
},
"require": {
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
- "source": "https://github.com/guzzle/psr7/tree/1.7.0"
+ "source": "https://github.com/guzzle/psr7/tree/1.8.1"
},
- "time": "2020-09-30T07:37:11+00:00"
+ "time": "2021-03-21T16:25:00+00:00"
},
{
"name": "paragonie/constant_time_encoding",
},
{
"name": "pear/pear_exception",
- "version": "v1.0.1",
+ "version": "v1.0.2",
"source": {
"type": "git",
"url": "https://github.com/pear/PEAR_Exception.git",
- "reference": "dbb42a5a0e45f3adcf99babfb2a1ba77b8ac36a7"
+ "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pear/PEAR_Exception/zipball/dbb42a5a0e45f3adcf99babfb2a1ba77b8ac36a7",
- "reference": "dbb42a5a0e45f3adcf99babfb2a1ba77b8ac36a7",
+ "url": "https://api.github.com/repos/pear/PEAR_Exception/zipball/b14fbe2ddb0b9f94f5b24cf08783d599f776fff0",
+ "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0",
"shasum": ""
},
"require": {
- "php": ">=4.4.0"
+ "php": ">=5.2.0"
},
"require-dev": {
- "phpunit/phpunit": "*"
+ "phpunit/phpunit": "<9"
},
"type": "class",
"extra": {
"issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR_Exception",
"source": "https://github.com/pear/PEAR_Exception"
},
- "time": "2019-12-10T10:24:42+00:00"
+ "time": "2021-03-21T15:43:46+00:00"
},
{
"name": "pelago/emogrifier",
- "version": "v5.0.0",
+ "version": "v5.0.1",
"source": {
"type": "git",
"url": "https://github.com/MyIntervals/emogrifier.git",
- "reference": "b43b650880d189b0ada61d95d0729c7424b1752d"
+ "reference": "37595a9bb62c3c25969bdd9e8d7dd24c3ac62bc9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/b43b650880d189b0ada61d95d0729c7424b1752d",
- "reference": "b43b650880d189b0ada61d95d0729c7424b1752d",
+ "url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/37595a9bb62c3c25969bdd9e8d7dd24c3ac62bc9",
+ "reference": "37595a9bb62c3c25969bdd9e8d7dd24c3ac62bc9",
"shasum": ""
},
"require": {
},
"require-dev": {
"php-parallel-lint/php-parallel-lint": "^1.2.0",
+ "rawr/cross-data-providers": "^2.3.0",
"slevomat/coding-standard": "^6.4.1",
"squizlabs/php_codesniffer": "^3.5.8"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "5.0.x-dev"
+ "dev-main": "6.0.x-dev"
}
},
"autoload": {
"issues": "https://github.com/MyIntervals/emogrifier/issues",
"source": "https://github.com/MyIntervals/emogrifier"
},
- "time": "2020-11-23T18:37:25+00:00"
+ "time": "2021-04-06T08:18:22+00:00"
},
{
"name": "psr/http-client",
},
{
"name": "symfony/css-selector",
- "version": "v5.2.3",
+ "version": "v5.2.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
"description": "Converts CSS selectors to XPath expressions",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/css-selector/tree/v5.2.3"
+ "source": "https://github.com/symfony/css-selector/tree/v5.2.4"
},
"funding": [
{
),
'guzzlehttp/promises' =>
array (
- 'pretty_version' => '1.4.0',
- 'version' => '1.4.0.0',
+ 'pretty_version' => '1.4.1',
+ 'version' => '1.4.1.0',
'aliases' =>
array (
),
- 'reference' => '60d379c243457e073cff02bc323a2a86cb355631',
+ 'reference' => '8e7d04f1f6450fef59366c399cfad4b9383aa30d',
),
'guzzlehttp/psr7' =>
array (
- 'pretty_version' => '1.7.0',
- 'version' => '1.7.0.0',
+ 'pretty_version' => '1.8.1',
+ 'version' => '1.8.1.0',
'aliases' =>
array (
),
- 'reference' => '53330f47520498c0ae1f61f7e2c90f55690c06a3',
+ 'reference' => '35ea11d335fd638b5882ff1725228b3d35496ab1',
),
'paragonie/constant_time_encoding' =>
array (
),
'pear/pear_exception' =>
array (
- 'pretty_version' => 'v1.0.1',
- 'version' => '1.0.1.0',
+ 'pretty_version' => 'v1.0.2',
+ 'version' => '1.0.2.0',
'aliases' =>
array (
),
- 'reference' => 'dbb42a5a0e45f3adcf99babfb2a1ba77b8ac36a7',
+ 'reference' => 'b14fbe2ddb0b9f94f5b24cf08783d599f776fff0',
),
'pelago/emogrifier' =>
array (
- 'pretty_version' => 'v5.0.0',
- 'version' => '5.0.0.0',
+ 'pretty_version' => 'v5.0.1',
+ 'version' => '5.0.1.0',
'aliases' =>
array (
),
- 'reference' => 'b43b650880d189b0ada61d95d0729c7424b1752d',
+ 'reference' => '37595a9bb62c3c25969bdd9e8d7dd24c3ac62bc9',
),
'psr/http-client' =>
array (
),
'symfony/css-selector' =>
array (
- 'pretty_version' => 'v5.2.3',
- 'version' => '5.2.3.0',
+ 'pretty_version' => 'v5.2.4',
+ 'version' => '5.2.4.0',
'aliases' =>
array (
),
return array(
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
+ '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
- '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
);
{
public static $files = array (
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
+ '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
- '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'2cffec82183ee1cea088009cef9a6fc3' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
);
},
{
"name": "guzzlehttp/promises",
- "version": "1.4.0",
- "version_normalized": "1.4.0.0",
+ "version": "1.4.1",
+ "version_normalized": "1.4.1.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
- "reference": "60d379c243457e073cff02bc323a2a86cb355631"
+ "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/promises/zipball/60d379c243457e073cff02bc323a2a86cb355631",
- "reference": "60d379c243457e073cff02bc323a2a86cb355631",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d",
+ "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d",
"shasum": ""
},
"require": {
"require-dev": {
"symfony/phpunit-bridge": "^4.4 || ^5.1"
},
- "time": "2020-09-30T07:37:28+00:00",
+ "time": "2021-03-07T09:25:29+00:00",
"type": "library",
"extra": {
"branch-alias": {
"keywords": [
"promise"
],
+ "support": {
+ "issues": "https://github.com/guzzle/promises/issues",
+ "source": "https://github.com/guzzle/promises/tree/1.4.1"
+ },
"install-path": "../guzzlehttp/promises"
},
{
"name": "guzzlehttp/psr7",
- "version": "1.7.0",
- "version_normalized": "1.7.0.0",
+ "version": "1.8.1",
+ "version_normalized": "1.8.1.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
- "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3"
+ "reference": "35ea11d335fd638b5882ff1725228b3d35496ab1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/psr7/zipball/53330f47520498c0ae1f61f7e2c90f55690c06a3",
- "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/35ea11d335fd638b5882ff1725228b3d35496ab1",
+ "reference": "35ea11d335fd638b5882ff1725228b3d35496ab1",
"shasum": ""
},
"require": {
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
},
- "time": "2020-09-30T07:37:11+00:00",
+ "time": "2021-03-21T16:25:00+00:00",
"type": "library",
"extra": {
"branch-alias": {
"uri",
"url"
],
+ "support": {
+ "issues": "https://github.com/guzzle/psr7/issues",
+ "source": "https://github.com/guzzle/psr7/tree/1.8.1"
+ },
"install-path": "../guzzlehttp/psr7"
},
{
},
{
"name": "pear/pear_exception",
- "version": "v1.0.1",
- "version_normalized": "1.0.1.0",
+ "version": "v1.0.2",
+ "version_normalized": "1.0.2.0",
"source": {
"type": "git",
"url": "https://github.com/pear/PEAR_Exception.git",
- "reference": "dbb42a5a0e45f3adcf99babfb2a1ba77b8ac36a7"
+ "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pear/PEAR_Exception/zipball/dbb42a5a0e45f3adcf99babfb2a1ba77b8ac36a7",
- "reference": "dbb42a5a0e45f3adcf99babfb2a1ba77b8ac36a7",
+ "url": "https://api.github.com/repos/pear/PEAR_Exception/zipball/b14fbe2ddb0b9f94f5b24cf08783d599f776fff0",
+ "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0",
"shasum": ""
},
"require": {
- "php": ">=4.4.0"
+ "php": ">=5.2.0"
},
"require-dev": {
- "phpunit/phpunit": "*"
+ "phpunit/phpunit": "<9"
},
- "time": "2019-12-10T10:24:42+00:00",
+ "time": "2021-03-21T15:43:46+00:00",
"type": "class",
"extra": {
"branch-alias": {
"keywords": [
"exception"
],
+ "support": {
+ "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR_Exception",
+ "source": "https://github.com/pear/PEAR_Exception"
+ },
"install-path": "../pear/pear_exception"
},
{
"name": "pelago/emogrifier",
- "version": "v5.0.0",
- "version_normalized": "5.0.0.0",
+ "version": "v5.0.1",
+ "version_normalized": "5.0.1.0",
"source": {
"type": "git",
"url": "https://github.com/MyIntervals/emogrifier.git",
- "reference": "b43b650880d189b0ada61d95d0729c7424b1752d"
+ "reference": "37595a9bb62c3c25969bdd9e8d7dd24c3ac62bc9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/b43b650880d189b0ada61d95d0729c7424b1752d",
- "reference": "b43b650880d189b0ada61d95d0729c7424b1752d",
+ "url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/37595a9bb62c3c25969bdd9e8d7dd24c3ac62bc9",
+ "reference": "37595a9bb62c3c25969bdd9e8d7dd24c3ac62bc9",
"shasum": ""
},
"require": {
},
"require-dev": {
"php-parallel-lint/php-parallel-lint": "^1.2.0",
+ "rawr/cross-data-providers": "^2.3.0",
"slevomat/coding-standard": "^6.4.1",
"squizlabs/php_codesniffer": "^3.5.8"
},
- "time": "2020-11-23T18:37:25+00:00",
+ "time": "2021-04-06T08:18:22+00:00",
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "5.0.x-dev"
+ "dev-main": "6.0.x-dev"
}
},
"installation-source": "dist",
},
{
"name": "symfony/css-selector",
- "version": "v5.2.3",
- "version_normalized": "5.2.3.0",
+ "version": "v5.2.4",
+ "version_normalized": "5.2.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
"description": "Converts CSS selectors to XPath expressions",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/css-selector/tree/v5.2.3"
+ "source": "https://github.com/symfony/css-selector/tree/v5.2.4"
},
"funding": [
{
),
'guzzlehttp/promises' =>
array (
- 'pretty_version' => '1.4.0',
- 'version' => '1.4.0.0',
+ 'pretty_version' => '1.4.1',
+ 'version' => '1.4.1.0',
'aliases' =>
array (
),
- 'reference' => '60d379c243457e073cff02bc323a2a86cb355631',
+ 'reference' => '8e7d04f1f6450fef59366c399cfad4b9383aa30d',
),
'guzzlehttp/psr7' =>
array (
- 'pretty_version' => '1.7.0',
- 'version' => '1.7.0.0',
+ 'pretty_version' => '1.8.1',
+ 'version' => '1.8.1.0',
'aliases' =>
array (
),
- 'reference' => '53330f47520498c0ae1f61f7e2c90f55690c06a3',
+ 'reference' => '35ea11d335fd638b5882ff1725228b3d35496ab1',
),
'paragonie/constant_time_encoding' =>
array (
),
'pear/pear_exception' =>
array (
- 'pretty_version' => 'v1.0.1',
- 'version' => '1.0.1.0',
+ 'pretty_version' => 'v1.0.2',
+ 'version' => '1.0.2.0',
'aliases' =>
array (
),
- 'reference' => 'dbb42a5a0e45f3adcf99babfb2a1ba77b8ac36a7',
+ 'reference' => 'b14fbe2ddb0b9f94f5b24cf08783d599f776fff0',
),
'pelago/emogrifier' =>
array (
- 'pretty_version' => 'v5.0.0',
- 'version' => '5.0.0.0',
+ 'pretty_version' => 'v5.0.1',
+ 'version' => '5.0.1.0',
'aliases' =>
array (
),
- 'reference' => 'b43b650880d189b0ada61d95d0729c7424b1752d',
+ 'reference' => '37595a9bb62c3c25969bdd9e8d7dd24c3ac62bc9',
),
'psr/http-client' =>
array (
),
'symfony/css-selector' =>
array (
- 'pretty_version' => 'v5.2.3',
- 'version' => '5.2.3.0',
+ 'pretty_version' => 'v5.2.4',
+ 'version' => '5.2.4.0',
'aliases' =>
array (
),
+++ /dev/null
-<?php
-
-$config = PhpCsFixer\Config::create()
- ->setRiskyAllowed(true)
- ->setRules([
- '@PSR2' => true,
- 'array_syntax' => ['syntax' => 'short'],
- 'binary_operator_spaces' => ['operators' => ['=>' => null]],
- 'blank_line_after_opening_tag' => true,
- 'class_attributes_separation' => ['elements' => ['method']],
- 'compact_nullable_typehint' => true,
- 'concat_space' => ['spacing' => 'one'],
- 'declare_equal_normalize' => ['space' => 'none'],
- 'declare_strict_types' => false,
- 'dir_constant' => true,
- 'final_static_access' => true,
- 'fully_qualified_strict_types' => true,
- 'function_to_constant' => true,
- 'function_typehint_space' => true,
- 'header_comment' => false,
- 'is_null' => ['use_yoda_style' => false],
- 'list_syntax' => ['syntax' => 'short'],
- 'lowercase_cast' => true,
- 'magic_method_casing' => true,
- 'modernize_types_casting' => true,
- 'multiline_comment_opening_closing' => true,
- //'native_constant_invocation' => true,
- 'no_alias_functions' => true,
- 'no_alternative_syntax' => true,
- 'no_blank_lines_after_phpdoc' => true,
- 'no_empty_comment' => true,
- 'no_empty_phpdoc' => true,
- 'no_extra_blank_lines' => true,
- 'no_leading_import_slash' => true,
- 'no_leading_namespace_whitespace' => true,
- 'no_spaces_around_offset' => true,
- 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true],
- 'no_trailing_comma_in_singleline_array' => true,
- 'no_unneeded_control_parentheses' => true,
- 'no_unset_cast' => true,
- 'no_unused_imports' => true,
- 'no_useless_else' => true,
- 'no_useless_return' => true,
- 'no_whitespace_in_blank_line' => true,
- 'normalize_index_brace' => true,
- 'ordered_imports' => true,
- 'php_unit_construct' => true,
- 'php_unit_dedicate_assert' => ['target' => 'newest'],
- 'php_unit_dedicate_assert_internal_type' => ['target' => 'newest'],
- 'php_unit_expectation' => ['target' => 'newest'],
- 'php_unit_mock' => ['target' => 'newest'],
- 'php_unit_mock_short_will_return' => true,
- 'php_unit_no_expectation_annotation' => ['target' => 'newest'],
- 'php_unit_test_annotation' => ['style' => 'prefix'],
- //'php_unit_test_case_static_method_calls' => ['call_type' => 'self'],
- 'phpdoc_align' => ['align' => 'vertical'],
- //'phpdoc_line_span' => ['method' => 'multi', 'property' => 'multi'],
- 'phpdoc_no_package' => true,
- 'phpdoc_no_useless_inheritdoc' => true,
- 'phpdoc_scalar' => true,
- 'phpdoc_separation' => true,
- 'phpdoc_single_line_var_spacing' => true,
- 'phpdoc_trim' => true,
- 'phpdoc_trim_consecutive_blank_line_separation' => true,
- 'phpdoc_types' => true,
- 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
- 'phpdoc_var_without_name' => true,
- 'return_assignment' => true,
- 'short_scalar_cast' => true,
- 'single_trait_insert_per_statement' => true,
- 'standardize_not_equals' => true,
- //'static_lambda' => true,
- 'ternary_to_null_coalescing' => true,
- 'trim_array_spaces' => true,
- 'visibility_required' => true,
- 'yoda_style' => false,
- // 'native_function_invocation' => true,
- 'braces' => ['allow_single_line_closure'=>true],
- ])
- ->setFinder(
- PhpCsFixer\Finder::create()
- ->in(__DIR__.'/src')
- ->in(__DIR__.'/tests')
- ->name('*.php')
- )
-;
-
-return $config;
# CHANGELOG
+## 1.4.1 - 2021-02-18
+
+- Fixed `each_limit` skipping promises and failing
## 1.4.0 - 2020-09-30
+++ /dev/null
-parameters:
- ignoreErrors:
- -
- message: "#^Parameter \\#1 \\$function of function register_shutdown_function expects callable\\(\\)\\: void, Closure\\(\\)\\: mixed given\\.$#"
- count: 1
- path: src/TaskQueue.php
-
+++ /dev/null
-includes:
- - phpstan-baseline.neon
-
-parameters:
- level: 5
- paths:
- - src
-
- ignoreErrors:
- - "#^Dead catch - Exception is already caught by Throwable above\\.$#"
+++ /dev/null
-<?xml version="1.0"?>
-<psalm
- errorLevel="4"
- resolveFromConfigFile="true"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns="https://getpsalm.org/schema/config"
- xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
->
- <projectFiles>
- <directory name="src" />
- <ignoreFiles>
- <directory name="vendor" />
- </ignoreFiles>
- </projectFiles>
-</psalm>
{
private $pending = [];
+ private $nextPendingIndex = 0;
+
/** @var \Iterator|null */
private $iterable;
$clearFn = function () {
$this->iterable = $this->concurrency = $this->pending = null;
$this->onFulfilled = $this->onRejected = null;
+ $this->nextPendingIndex = 0;
};
$this->aggregate->then($clearFn, $clearFn);
$promise = Create::promiseFor($this->iterable->current());
$key = $this->iterable->key();
- // Iterable keys may not be unique, so we add the promises at the end
- // of the pending array and retrieve the array index being used
- $this->pending[] = null;
- end($this->pending);
- $idx = key($this->pending);
+ // Iterable keys may not be unique, so we use a counter to
+ // guarantee uniqueness
+ $idx = $this->nextPendingIndex++;
$this->pending[$idx] = $promise->then(
function ($value) use ($idx, $key) {
--- /dev/null
+name: BC Check
+
+on:
+ pull_request:
+
+jobs:
+ roave-bc-check:
+ name: Roave BC Check
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+
+ - name: Roave BC Check
+ uses: docker://nyholm/roave-bc-check-ga
--- /dev/null
+name: CI
+
+on:
+ pull_request:
+
+jobs:
+ build:
+ name: Build
+ runs-on: ubuntu-latest
+ strategy:
+ max-parallel: 10
+ matrix:
+ php: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4']
+
+ steps:
+ - name: Set up PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ coverage: 'none'
+ extensions: mbstring
+
+ - name: Checkout code
+ uses: actions/checkout@v2
+
+ - name: Install dependencies
+ run: composer update --no-interaction --no-progress --prefer-dist
+
+ - name: Run tests
+ run: make test
--- /dev/null
+name: Integration
+
+on:
+ pull_request:
+
+jobs:
+
+ build:
+ name: Test
+ runs-on: ubuntu-latest
+ strategy:
+ max-parallel: 10
+ matrix:
+ php: ['7.2', '7.3', '7.4', '8.0']
+
+ steps:
+ - name: Set up PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ coverage: none
+
+ - name: Checkout code
+ uses: actions/checkout@v2
+
+ - name: Download dependencies
+ uses: ramsey/composer-install@v1
+ with:
+ composer-options: --no-interaction --prefer-dist --optimize-autoloader
+
+ - name: Start server
+ run: php -S 127.0.0.1:10002 tests/Integration/server.php &
+
+ - name: Run tests
+ env:
+ TEST_SERVER: 127.0.0.1:10002
+ run: ./vendor/bin/phpunit --testsuite Integration
--- /dev/null
+<?php
+
+$config = PhpCsFixer\Config::create()
+ ->setRiskyAllowed(true)
+ ->setRules([
+ '@PSR2' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ 'concat_space' => ['spacing' => 'one'],
+ 'declare_strict_types' => false,
+ 'final_static_access' => true,
+ 'fully_qualified_strict_types' => true,
+ 'header_comment' => false,
+ 'is_null' => ['use_yoda_style' => true],
+ 'list_syntax' => ['syntax' => 'long'],
+ 'lowercase_cast' => true,
+ 'magic_method_casing' => true,
+ 'modernize_types_casting' => true,
+ 'multiline_comment_opening_closing' => true,
+ 'no_alias_functions' => true,
+ 'no_alternative_syntax' => true,
+ 'no_blank_lines_after_phpdoc' => true,
+ 'no_empty_comment' => true,
+ 'no_empty_phpdoc' => true,
+ 'no_empty_statement' => true,
+ 'no_extra_blank_lines' => true,
+ 'no_leading_import_slash' => true,
+ 'no_trailing_comma_in_singleline_array' => true,
+ 'no_unset_cast' => true,
+ 'no_unused_imports' => true,
+ 'no_whitespace_in_blank_line' => true,
+ 'ordered_imports' => true,
+ 'php_unit_ordered_covers' => true,
+ 'php_unit_test_annotation' => ['style' => 'prefix'],
+ 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'],
+ 'phpdoc_align' => ['align' => 'vertical'],
+ 'phpdoc_no_useless_inheritdoc' => true,
+ 'phpdoc_scalar' => true,
+ 'phpdoc_separation' => true,
+ 'phpdoc_single_line_var_spacing' => true,
+ 'phpdoc_trim' => true,
+ 'phpdoc_trim_consecutive_blank_line_separation' => true,
+ 'phpdoc_types' => true,
+ 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
+ 'phpdoc_var_without_name' => true,
+ 'single_trait_insert_per_statement' => true,
+ 'standardize_not_equals' => true,
+ ])
+ ->setFinder(
+ PhpCsFixer\Finder::create()
+ ->in(__DIR__.'/src')
+ ->in(__DIR__.'/tests')
+ ->name('*.php')
+ )
+;
+
+return $config;
## [Unreleased]
+## [1.8.1] - 2021-03-21
+
+### Fixed
+
+- Issue parsing IPv6 URLs
+- Issue modifying ServerRequest lost all its attributes
+
+## [1.8.0] - 2021-03-21
+
+### Added
+
+- Locale independent URL parsing
+- Most classes got a `@final` annotation to prepare for 2.0
+
+### Fixed
+
+- Issue when creating stream from `php://input` and curl-ext is not installed
+- Broken `Utils::tryFopen()` on PHP 8
+
## [1.7.0] - 2020-09-30
### Added
* Reads from multiple streams, one after the other.
*
* This is a read-only stream decorator.
+ *
+ * @final
*/
class AppendStream implements StreamInterface
{
* This stream returns a "hwm" metadata value that tells upstream consumers
* what the configured high water mark of the stream is, or the maximum
* preferred size of the buffer.
+ *
+ * @final
*/
class BufferStream implements StreamInterface
{
/**
* Stream decorator that can cache previously read bytes from a sequentially
* read stream.
+ *
+ * @final
*/
class CachingStream implements StreamInterface
{
/**
* We will treat the buffer object as the body of the stream
*
- * @param StreamInterface $stream Stream to cache
+ * @param StreamInterface $stream Stream to cache. The cursor is assumed to be at the beginning of the stream.
* @param StreamInterface $target Optionally specify where data is cached
*/
public function __construct(
StreamInterface $target = null
) {
$this->remoteStream = $stream;
- $this->stream = $target ?: new Stream(fopen('php://temp', 'r+'));
+ $this->stream = $target ?: new Stream(Utils::tryFopen('php://temp', 'r+'));
}
public function getSize()
/**
* Stream decorator that begins dropping data once the size of the underlying
* stream becomes too full.
+ *
+ * @final
*/
class DroppingStream implements StreamInterface
{
*
* Allows for easy testing and extension of a provided stream without needing
* to create a concrete class for a simple extension point.
+ *
+ * @final
*/
class FnStream implements StreamInterface
{
/**
* An unserialize would allow the __destruct to run when the unserialized value goes out of scope.
+ *
* @throws \LogicException
*/
public function __wakeup()
*
* @link http://tools.ietf.org/html/rfc1952
* @link http://php.net/manual/en/filters.compression.php
+ *
+ * @final
*/
class InflateStream implements StreamInterface
{
/**
* @param StreamInterface $stream
* @param $header
+ *
* @return int
*/
private function getLengthOfPossibleFilenameHeader(StreamInterface $stream, $header)
/**
* Lazily reads or writes to a file that is opened only after an IO operation
* take place on the stream.
+ *
+ * @final
*/
class LazyOpenStream implements StreamInterface
{
/** @var string File to open */
private $filename;
- /** @var string $mode */
+ /** @var string */
private $mode;
/**
use Psr\Http\Message\StreamInterface;
-
/**
- * Decorator used to return only a subset of a stream
+ * Decorator used to return only a subset of a stream.
+ *
+ * @final
*/
class LimitStream implements StreamInterface
{
/**
* Stream that when read returns bytes for a streaming multipart or
* multipart/form-data stream.
+ *
+ * @final
*/
class MultipartStream implements StreamInterface
{
$disposition = $this->getHeader($headers, 'content-disposition');
if (!$disposition) {
$headers['Content-Disposition'] = ($filename === '0' || $filename)
- ? sprintf('form-data; name="%s"; filename="%s"',
+ ? sprintf(
+ 'form-data; name="%s"; filename="%s"',
$name,
- basename($filename))
+ basename($filename)
+ )
: "form-data; name=\"{$name}\"";
}
use Psr\Http\Message\StreamInterface;
/**
- * Stream decorator that prevents a stream from being seeked
+ * Stream decorator that prevents a stream from being seeked.
+ *
+ * @final
*/
class NoSeekStream implements StreamInterface
{
* returned by the provided callable is buffered internally until drained using
* the read() function of the PumpStream. The provided callable MUST return
* false when there is no more data to read.
+ *
+ * @final
*/
class PumpStream implements StreamInterface
{
private $buffer;
/**
- * @param callable $source Source of the stream data. The callable MAY
- * accept an integer argument used to control the
- * amount of data to return. The callable MUST
- * return a string when called, or false on error
- * or EOF.
- * @param array $options Stream options:
- * - metadata: Hash of metadata to use with stream.
- * - size: Size of the stream, if known.
+ * @param callable $source Source of the stream data. The callable MAY
+ * accept an integer argument used to control the
+ * amount of data to return. The callable MUST
+ * return a string when called, or false on error
+ * or EOF.
+ * @param array $options Stream options:
+ * - metadata: Hash of metadata to use with stream.
+ * - size: Size of the stream, if known.
*/
public function __construct(callable $source, array $options = [])
{
} elseif ($urlEncoding === PHP_QUERY_RFC1738) {
$decoder = 'urldecode';
} else {
- $decoder = function ($str) { return $str; };
+ $decoder = function ($str) {
+ return $str;
+ };
}
foreach (explode('&', $str) as $kvp) {
* @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986
* to encode using RFC3986, or PHP_QUERY_RFC1738
* to encode using RFC1738.
+ *
* @return string
*/
public static function build(array $params, $encoding = PHP_QUERY_RFC3986)
}
if ($encoding === false) {
- $encoder = function ($str) { return $str; };
+ $encoder = function ($str) {
+ return $str;
+ };
} elseif ($encoding === PHP_QUERY_RFC3986) {
$encoder = 'rawurlencode';
} elseif ($encoding === PHP_QUERY_RFC1738) {
/** @var string */
private $method;
- /** @var null|string */
+ /** @var string|null */
private $requestTarget;
/** @var UriInterface */
* @param string $method HTTP method
* @param string|UriInterface $uri URI
* @param array $headers Request headers
- * @param string|null|resource|StreamInterface $body Request body
+ * @param string|resource|StreamInterface|null $body Request body
* @param string $version Protocol version
*/
public function __construct(
/**
* @param int $status Status code
* @param array $headers Response headers
- * @param string|null|resource|StreamInterface $body Response body
+ * @param string|resource|StreamInterface|null $body Response body
* @param string $version Protocol version
* @param string|null $reason Reason phrase (when empty a default will be used based on the status code)
*/
* Note: header delimiter (\r\n) is modified to \r?\n to accept line feed only delimiters for BC reasons.
*
* @link https://github.com/amphp/http/blob/v1.0.1/src/Rfc7230.php#L12-L15
+ *
* @license https://github.com/amphp/http/blob/v1.0.1/LICENSE
*/
const HEADER_REGEX = "(^([^()<>@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m";
use InvalidArgumentException;
use Psr\Http\Message\ServerRequestInterface;
-use Psr\Http\Message\UriInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;
+use Psr\Http\Message\UriInterface;
/**
* Server-side HTTP request
private $cookieParams = [];
/**
- * @var null|array|object
+ * @var array|object|null
*/
private $parsedBody;
* @param string $method HTTP method
* @param string|UriInterface $uri URI
* @param array $headers Request headers
- * @param string|null|resource|StreamInterface $body Request body
+ * @param string|resource|StreamInterface|null $body Request body
* @param string $version Protocol version
* @param array $serverParams Typically the $_SERVER superglobal
*/
* delegate to normalizeNestedFileSpec() and return that return value.
*
* @param array $value $_FILES struct
+ *
* @return array|UploadedFileInterface
*/
private static function createUploadedFileFromSpec(array $value)
* UploadedFileInterface instances.
*
* @param array $files
+ *
* @return UploadedFileInterface[]
*/
private static function normalizeNestedFileSpec(array $files = [])
private static function extractHostAndPortFromAuthority($authority)
{
- $uri = 'http://'.$authority;
+ $uri = 'http://' . $authority;
$parts = parse_url($uri);
if (false === $parts) {
return [null, null];
return $uri;
}
-
/**
* {@inheritdoc}
*/
/**
* Stream decorator trait
+ *
* @property StreamInterface stream
*/
trait StreamDecoratorTrait
/**
* Converts Guzzle streams into PHP stream resources.
+ *
+ * @final
*/
class StreamWrapper
{
private $error;
/**
- * @var null|string
+ * @var string|null
*/
private $file;
/**
* @param StreamInterface|string|resource $streamOrFile
- * @param int $size
- * @param int $errorStatus
- * @param string|null $clientFilename
- * @param string|null $clientMediaType
+ * @param int $size
+ * @param int $errorStatus
+ * @param string|null $clientFilename
+ * @param string|null $clientMediaType
*/
public function __construct(
$streamOrFile,
/**
* @param mixed $param
- * @return boolean
+ *
+ * @return bool
*/
private function isStringOrNull($param)
{
/**
* @param mixed $param
- * @return boolean
+ *
+ * @return bool
*/
private function isStringNotEmpty($param)
{
/**
* Return true if there is no upload error
*
- * @return boolean
+ * @return bool
*/
private function isOk()
{
}
/**
- * @return boolean
+ * @return bool
*/
public function isMoved()
{
*
* @param string $targetPath Path to which to move the uploaded file.
*
- * @throws RuntimeException if the upload was not successful.
+ * @throws RuntimeException if the upload was not successful.
* @throws InvalidArgumentException if the $path specified is invalid.
- * @throws RuntimeException on any error during the move operation, or on
- * the second or subsequent call to the method.
+ * @throws RuntimeException on any error during the move operation, or on
+ * the second or subsequent call to the method.
*/
public function moveTo($targetPath)
{
* {@inheritdoc}
*
* @see http://php.net/manual/en/features.file-upload.errors.php
+ *
* @return int One of PHP's UPLOAD_ERR_XXX constants.
*/
public function getError()
* {@inheritdoc}
*
* @return string|null The filename sent by the client or null if none
- * was provided.
+ * was provided.
*/
public function getClientFilename()
{
{
// weak type check to also accept null until we can add scalar type hints
if ($uri != '') {
- $parts = parse_url($uri);
+ $parts = self::parse($uri);
if ($parts === false) {
throw new \InvalidArgumentException("Unable to parse URI: $uri");
}
}
}
+ /**
+ * UTF-8 aware \parse_url() replacement.
+ *
+ * The internal function produces broken output for non ASCII domain names
+ * (IDN) when used with locales other than "C".
+ *
+ * On the other hand, cURL understands IDN correctly only when UTF-8 locale
+ * is configured ("C.UTF-8", "en_US.UTF-8", etc.).
+ *
+ * @see https://bugs.php.net/bug.php?id=52923
+ * @see https://www.php.net/manual/en/function.parse-url.php#114817
+ * @see https://curl.haxx.se/libcurl/c/CURLOPT_URL.html#ENCODING
+ *
+ * @param string $url
+ *
+ * @return array|false
+ */
+ private static function parse($url)
+ {
+ // If IPv6
+ $prefix = '';
+ if (preg_match('%^(.*://\[[0-9:a-f]+\])(.*?)$%', $url, $matches)) {
+ $prefix = $matches[1];
+ $url = $matches[2];
+ }
+
+ $encodedUrl = preg_replace_callback(
+ '%[^:/@?&=#]+%usD',
+ static function ($matches) {
+ return urlencode($matches[0]);
+ },
+ $url
+ );
+
+ $result = parse_url($prefix.$encodedUrl);
+
+ if ($result === false) {
+ return false;
+ }
+
+ return array_map('urldecode', $result);
+ }
+
public function __toString()
{
return self::composeComponents(
* @param UriInterface $uri
*
* @return bool
+ *
* @see Uri::isNetworkPathReference
* @see Uri::isAbsolutePathReference
* @see Uri::isRelativePathReference
* @param UriInterface $uri
*
* @return bool
+ *
* @link https://tools.ietf.org/html/rfc3986#section-4.2
*/
public static function isNetworkPathReference(UriInterface $uri)
* @param UriInterface $uri
*
* @return bool
+ *
* @link https://tools.ietf.org/html/rfc3986#section-4.2
*/
public static function isAbsolutePathReference(UriInterface $uri)
* @param UriInterface $uri
*
* @return bool
+ *
* @link https://tools.ietf.org/html/rfc3986#section-4.2
*/
public static function isRelativePathReference(UriInterface $uri)
* @param UriInterface|null $base An optional base URI to compare against
*
* @return bool
+ *
* @link https://tools.ietf.org/html/rfc3986#section-4.4
*/
public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null)
* @param array $parts
*
* @return UriInterface
+ *
* @link http://php.net/manual/en/function.parse-url.php
*
* @throws \InvalidArgumentException If the components do not form a valid URI.
throw new \InvalidArgumentException('Scheme must be a string');
}
- return strtolower($scheme);
+ return \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
}
/**
throw new \InvalidArgumentException('Host must be a string');
}
- return strtolower($host);
+ return \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
}
/**
/**
* @param UriInterface $uri
* @param array $keys
- *
+ *
* @return array
*/
private static function getFilteredQueryString(UriInterface $uri, array $keys)
/**
* @param string $key
* @param string|null $value
- *
+ *
* @return string
*/
private static function generateQueryString($key, $value)
'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.',
E_USER_DEPRECATED
);
- $this->path = '/'. $this->path;
+ $this->path = '/' . $this->path;
//throw new \InvalidArgumentException('The path of a URI with an authority must start with a slash "/" or be empty');
}
}
* @param int $flags A bitmask of normalizations to apply, see constants
*
* @return UriInterface The normalized URI
+ *
* @link https://tools.ietf.org/html/rfc3986#section-6.2
*/
public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS)
* @param int $normalizations A bitmask of normalizations to apply, see constants
*
* @return bool
+ *
* @link https://tools.ietf.org/html/rfc3986#section-6.1
*/
public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS)
* @param string $path
*
* @return string
+ *
* @link http://tools.ietf.org/html/rfc3986#section-5.2.4
*/
public static function removeDotSegments($path)
* @param UriInterface $rel Relative URI
*
* @return UriInterface
+ *
* @link http://tools.ietf.org/html/rfc3986#section-5.2
*/
public static function resolve(UriInterface $base, UriInterface $rel)
* @param StreamInterface $stream Stream to read
* @param int $maxLen Maximum number of bytes to read. Pass -1
* to read the entire stream.
+ *
* @return string
*
* @throws \RuntimeException on error.
$standardPorts = ['http' => 80, 'https' => 443];
$scheme = $changes['uri']->getScheme();
if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) {
- $changes['set_headers']['Host'] .= ':'.$port;
+ $changes['set_headers']['Host'] .= ':' . $port;
}
}
}
}
if ($request instanceof ServerRequestInterface) {
- return (new ServerRequest(
+ $new = (new ServerRequest(
isset($changes['method']) ? $changes['method'] : $request->getMethod(),
$uri,
$headers,
->withQueryParams($request->getQueryParams())
->withCookieParams($request->getCookieParams())
->withUploadedFiles($request->getUploadedFiles());
+
+ foreach ($request->getAttributes() as $key => $value) {
+ $new = $new->withAttribute($key, $value);
+ }
+
+ return $new;
}
return new Request(
* number of requested bytes are available. Any additional bytes will be
* buffered and used in subsequent reads.
*
- * @param resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource Entity body data
+ * @param resource|string|int|float|bool|StreamInterface|callable|\Iterator|null $resource Entity body data
* @param array $options Additional options
*
* @return StreamInterface
public static function streamFor($resource = '', array $options = [])
{
if (is_scalar($resource)) {
- $stream = fopen('php://temp', 'r+');
+ $stream = self::tryFopen('php://temp', 'r+');
if ($resource !== '') {
fwrite($stream, $resource);
fseek($stream, 0);
switch (gettype($resource)) {
case 'resource':
+ /*
+ * The 'php://input' is a special stream with quirks and inconsistencies.
+ * We avoid using that stream by reading it into php://temp
+ */
+ if (\stream_get_meta_data($resource)['uri'] === 'php://input') {
+ $stream = self::tryFopen('php://temp', 'w+');
+ fwrite($stream, stream_get_contents($resource));
+ fseek($stream, 0);
+ $resource = $stream;
+ }
return new Stream($resource, $options);
case 'object':
if ($resource instanceof StreamInterface) {
}
break;
case 'NULL':
- return new Stream(fopen('php://temp', 'r+'), $options);
+ return new Stream(self::tryFopen('php://temp', 'r+'), $options);
}
if (is_callable($resource)) {
$ex = null;
set_error_handler(function () use ($filename, $mode, &$ex) {
$ex = new \RuntimeException(sprintf(
- 'Unable to open %s using mode %s: %s',
+ 'Unable to open "%s" using mode "%s": %s',
$filename,
$mode,
func_get_args()[1]
));
+
+ return true;
});
- $handle = fopen($filename, $mode);
+ try {
+ $handle = fopen($filename, $mode);
+ } catch (\Throwable $e) {
+ $ex = new \RuntimeException(sprintf(
+ 'Unable to open "%s" using mode "%s": %s',
+ $filename,
+ $mode,
+ $e->getMessage()
+ ), 0, $e);
+ }
+
restore_error_handler();
if ($ex) {
* number of requested bytes are available. Any additional bytes will be
* buffered and used in subsequent reads.
*
- * @param resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource Entity body data
+ * @param resource|string|int|float|bool|StreamInterface|callable|\Iterator|null $resource Entity body data
* @param array $options Additional options
*
* @return StreamInterface
* @param StreamInterface $stream Stream to read
* @param int $maxLen Maximum number of bytes to read. Pass -1
* to read the entire stream.
+ *
* @return string
*
* @throws \RuntimeException on error.
* @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986
* to encode using RFC3986, or PHP_QUERY_RFC1738
* to encode using RFC1738.
+ *
* @return string
*
* @deprecated build_query will be removed in guzzlehttp/psr7:2.0. Use Query::build instead.
* @return array
*
* @internal
+ *
* @deprecated _parse_message will be removed in guzzlehttp/psr7:2.0. Use Message::parseMessage instead.
*/
function _parse_message($message)
* @return string
*
* @internal
+ *
* @deprecated _parse_request_uri will be removed in guzzlehttp/psr7:2.0. Use Message::parseRequestUri instead.
*/
function _parse_request_uri($path, array $headers)
* @return array
*
* @internal
+ *
* @deprecated _caseless_remove will be removed in guzzlehttp/psr7:2.0. Use Utils::caselessRemove instead.
*/
function _caseless_remove($keys, array $data)
+++ /dev/null
-PEAR_Exception*.tgz
-
-# composer related
-composer.lock
-composer.phar
-vendor
+++ /dev/null
-language: php
-php:
- - 5.6
- - 5.5
- - 5.4
-script:
- - cd tests && phpunit --coverage-text .
$code = null;
$this->cause = null;
}
- parent::__construct($message, $code);
+ parent::__construct($message, (int) $code);
$this->signal();
}
}
],
"require": {
- "php": ">=4.4.0"
+ "php": ">=5.2.0"
},
"autoload": {
"classmap": ["PEAR/"]
"source": "https://github.com/pear/PEAR_Exception"
},
"require-dev": {
- "phpunit/phpunit": "*"
+ "phpunit/phpunit": "<9"
}
}
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<package packagerversion="1.9.4" version="2.0"
- xmlns="http://pear.php.net/dtd/package-2.0"
- xmlns:tasks="http://pear.php.net/dtd/tasks-1.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd"
->
- <name>PEAR_Exception</name>
- <channel>pear.php.net</channel>
- <summary>The PEAR Exception base class</summary>
- <description>PEAR_Exception PHP5 error handling mechanism</description>
-
- <lead>
- <name>Christian Weiske</name>
- <user>cweiske</user>
- <email>cweiske@php.net</email>
- <active>yes</active>
- </lead>
- <lead>
- <name>Helgi Thormar</name>
- <user>dufuz</user>
- <email>dufuz@php.net</email>
- <active>no</active>
- </lead>
- <developer>
- <name>Greg Beaver</name>
- <user>cellog</user>
- <email>cellog@php.net</email>
- <active>no</active>
- </developer>
-
- <date>2015-02-10</date>
- <time>21:02:23</time>
- <version>
- <release>1.0.0</release>
- <api>1.0.0</api>
- </version>
- <stability>
- <release>stable</release>
- <api>stable</api>
- </stability>
- <license uri="http://opensource.org/licenses/bsd-license.php">New BSD License</license>
- <notes>
- This package was split out from the PEAR package.
- If you use PEAR_Exception in your package and use nothing from the PEAR package
- then it's better to depend on just PEAR_Exception.
- </notes>
- <contents>
- <dir name="/">
- <file name="/PEAR/Exception.php" role="php">
- <tasks:replace from="@package_version@" to="version" type="package-info" />
- </file>
- <dir name="tests">
- <dir name="PEAR">
- <file name="ExceptionTest.php" role="test"/>
- </dir>
- </dir>
- </dir>
- </contents>
-
- <dependencies>
- <required>
- <php>
- <min>5.4.0</min>
- </php>
- <pearinstaller>
- <min>1.9.5</min>
- </pearinstaller>
- </required>
- </dependencies>
-
- <phprelease />
-
- <changelog>
- <release>
- <version>
- <release>1.0.0</release>
- <api>1.0.0</api>
- </version>
- <stability>
- <release>stable</release>
- <api>stable</api>
- </stability>
- <date>2015-02-10</date>
- <license uri="http://opensource.org/licenses/bsd-license.php">New BSD License</license>
- <notes>Release stable version</notes>
- </release>
-
- <release>
- <version>
- <release>1.0.0beta2</release>
- <api>1.0.0</api>
- </version>
- <stability>
- <release>beta</release>
- <api>stable</api>
- </stability>
- <date>2014-02-21</date>
- <license uri="http://opensource.org/licenses/bsd-license.php">New BSD License</license>
- <notes>Bump up PEAR dependency.</notes>
- </release>
-
- <release>
- <version>
- <release>1.0.0beta1</release>
- <api>1.0.0</api>
- </version>
- <stability>
- <release>beta</release>
- <api>stable</api>
- </stability>
- <date>2012-05-10</date>
- <license uri="http://opensource.org/licenses/bsd-license.php">New BSD License</license>
- <notes>
-This packge was split out from the PEAR package. If you use PEAR_Exception in your package
-and use nothing from the PEAR package then it's better to depend on just PEAR_Exception.
- </notes>
- </release>
- </changelog>
-</package>
+++ /dev/null
-<?php
-$localFile = __DIR__ . '/../../PEAR/Exception.php';
-if (file_exists($localFile)) {
- require_once $localFile;
-} else {
- require_once 'PEAR/Exception.php';
-}
-
-class PEAR_ExceptionTest extends PHPUnit_Framework_TestCase
-{
- /**
- * @expectedException PEAR_Exception
- * @expectedExceptionMessage foo
- */
- public function testThrow()
- {
- throw new PEAR_Exception('foo');
- }
-
- public function testGetCauseNone()
- {
- $e = new PEAR_Exception('foo bar');
- $this->assertNull($e->getCause());
- }
-
- public function testGetCauseException()
- {
- $cause = new Exception('foo bar');
- $e = new PEAR_Exception('I caught an exception', $cause);
- $this->assertNotNull($e->getCause());
- $this->assertInstanceOf('Exception', $e->getCause());
- $this->assertEquals($cause, $e->getCause());
- }
-
- public function testGetCauseMessage()
- {
- $cause = new Exception('foo bar');
- $e = new PEAR_Exception('I caught an exception', $cause);
-
- $e->getCauseMessage($causes);
- $this->assertEquals('I caught an exception', $causes[0]['message']);
- $this->assertEquals('foo bar', $causes[1]['message']);
- }
-
- public function testGetTraceSafe()
- {
- $e = new PEAR_Exception('oops');
- $this->assertInternalType('array', $e->getTraceSafe());
- }
-
- public function testGetErrorClass()
- {
- $e = new PEAR_Exception('oops');
- $this->assertEquals('PEAR_ExceptionTest', $e->getErrorClass());
- }
-
- public function testGetErrorMethod()
- {
- $e = new PEAR_Exception('oops');
- $this->assertEquals('testGetErrorMethod', $e->getErrorMethod());
- }
-
- public function test__toString()
- {
- $e = new PEAR_Exception('oops');
- $this->assertInternalType('string', (string) $e);
- $this->assertContains('oops', (string) $e);
- }
-
- public function testToHtml()
- {
- $e = new PEAR_Exception('oops');
- $html = $e->toHtml();
- $this->assertInternalType('string', $html);
- $this->assertContains('oops', $html);
- }
-}
-?>
### Changed
### Deprecated
+- Support for PHP 7.2 will be removed in Emogrifier 7.0.
### Removed
### Fixed
+## 5.0.1
+
+### Changed
+- Switch the default branch from `master` to `main`
+ ([#951](https://github.com/MyIntervals/emogrifier/pull/951))
+
+### Fixed
+- Ignore `http-equiv` `Content-Type` in `<body>`
+ ([#961](https://github.com/MyIntervals/emogrifier/pull/961))
+- Allow "Content-Type" in content
+ ([#959](https://github.com/MyIntervals/emogrifier/pull/959))
+
## 5.0.0
### Added
# Emogrifier
-[![Build Status](https://github.com/MyIntervals/emogrifier/workflows/CI/badge.svg?branch=master)](https://github.com/MyIntervals/emogrifier/actions/)
+[![Build Status](https://github.com/MyIntervals/emogrifier/workflows/CI/badge.svg?branch=main)](https://github.com/MyIntervals/emogrifier/actions/)
[![Latest Stable Version](https://poser.pugx.org/pelago/emogrifier/v/stable.svg)](https://packagist.org/packages/pelago/emogrifier)
[![Total Downloads](https://poser.pugx.org/pelago/emogrifier/downloads.svg)](https://packagist.org/packages/pelago/emogrifier)
[![Latest Unstable Version](https://poser.pugx.org/pelago/emogrifier/v/unstable.svg)](https://packagist.org/packages/pelago/emogrifier)
without a type
* [optional](https://developer.mozilla.org/en-US/docs/Web/CSS/:optional)
* [required](https://developer.mozilla.org/en-US/docs/Web/CSS/:required)
-
+
Rules involving the following selectors cannot be applied as inline styles.
They will, however, be preserved and copied to a `<style>` element in the HTML:
-
+
* dynamic [pseudo-classes](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes)
(such as `:hover`)
* [pseudo-elements](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements)
to some of the declarations within them so that they will override CSS styles
that have been inlined. For example, with the following CSS, the `font-size`
declaration in the `@media` rule would not override the font size for `p`
- elements from the preceding rule after that has been inlined as
+ elements from the preceding rule after that has been inlined as
`<p style="font-size: 16px;">` in the HTML, without the `!important` directive
(even though `!important` would not be necessary if the CSS were not inlined):
```css
p {
font-size: 14px !important;
}
- }
+ }
```
* Emogrifier cannot inline CSS rules involving selectors with pseudo-elements
(such as `::after`) or dynamic pseudo-classes (such as `:hover`) – it is
},
"require-dev": {
"php-parallel-lint/php-parallel-lint": "^1.2.0",
+ "rawr/cross-data-providers": "^2.3.0",
"slevomat/coding-standard": "^6.4.1",
"squizlabs/php_codesniffer": "^3.5.8"
},
},
"extra": {
"branch-alias": {
- "dev-master": "5.0.x-dev"
+ "dev-main": "6.0.x-dev"
}
}
}
*/
protected const PHP_UNRECOGNIZED_VOID_TAGNAME_MATCHER = '(?:command|embed|keygen|source|track|wbr)';
+ /**
+ * Regular expression part to match tag names that may appear before the start of the `<body>` element. A start tag
+ * for any other element would implicitly start the `<body>` element due to tag omission rules.
+ *
+ * @var string
+ */
+ protected const TAGNAME_ALLOWED_BEFORE_BODY_MATCHER
+ = '(?:html|head|base|command|link|meta|noscript|script|style|template|title)';
+
+ /**
+ * regular expression pattern to match an HTML comment, including delimiters and modifiers
+ *
+ * @var string
+ */
+ protected const HTML_COMMENT_PATTERN = '/<!--[^-]*+(?:-(?!->)[^-]*+)*+(?:-->|$)/';
+
+ /**
+ * regular expression pattern to match an HTML `<template>` element, including delimiters and modifiers
+ *
+ * @var string
+ */
+ protected const HTML_TEMPLATE_ELEMENT_PATTERN
+ = '%<template[\\s>][^<]*+(?:<(?!/template>)[^<]*+)*+(?:</template>|$)%i';
+
/**
* @var \DOMDocument|null
*/
*/
private function removeSelfClosingTagsClosingTags(string $html): string
{
- return \preg_replace('%</' . static::PHP_UNRECOGNIZED_VOID_TAGNAME_MATCHER . '>%', '', $html);
+ return \preg_replace('%</' . self::PHP_UNRECOGNIZED_VOID_TAGNAME_MATCHER . '>%', '', $html);
}
/**
return $this->normalizeDocumentType($html);
}
- return static::DEFAULT_DOCUMENT_TYPE . $html;
+ return self::DEFAULT_DOCUMENT_TYPE . $html;
}
/**
*/
private function addContentTypeMetaTag(string $html): string
{
- $hasContentTypeMetaTag = \stripos($html, 'Content-Type') !== false;
- if ($hasContentTypeMetaTag) {
+ if ($this->hasContentTypeMetaTagInHead($html)) {
return $html;
}
if ($hasHeadTag) {
$reworkedHtml = \preg_replace(
'/<head(?=[\\s>])([^>]*+)>/i',
- '<head$1>' . static::CONTENT_TYPE_META_TAG,
+ '<head$1>' . self::CONTENT_TYPE_META_TAG,
$html
);
} elseif ($hasHtmlTag) {
$reworkedHtml = \preg_replace(
'/<html(.*?)>/i',
- '<html$1><head>' . static::CONTENT_TYPE_META_TAG . '</head>',
+ '<html$1><head>' . self::CONTENT_TYPE_META_TAG . '</head>',
$html
);
} else {
- $reworkedHtml = static::CONTENT_TYPE_META_TAG . $html;
+ $reworkedHtml = self::CONTENT_TYPE_META_TAG . $html;
}
return $reworkedHtml;
}
+ /**
+ * Tests whether the given HTML has a valid `Content-Type` metadata element within the `<head>` element. Due to tag
+ * omission rules, HTML parsers are expected to end the `<head>` element and start the `<body>` element upon
+ * encountering a start tag for any element which is permitted only within the `<body>`.
+ *
+ * @param string $html
+ *
+ * @return bool
+ */
+ private function hasContentTypeMetaTagInHead(string $html): bool
+ {
+ \preg_match('%^.*?(?=<meta(?=\\s)[^>]*\\shttp-equiv=(["\']?+)Content-Type\\g{-1}[\\s/>])%is', $html, $matches);
+ if (isset($matches[0])) {
+ $htmlBefore = $matches[0];
+ try {
+ $hasContentTypeMetaTagInHead = !$this->hasEndOfHeadElement($htmlBefore);
+ } catch (\RuntimeException $exception) {
+ // If something unexpected occurs, assume the `Content-Type` that was found is valid.
+ \trigger_error($exception->getMessage());
+ $hasContentTypeMetaTagInHead = true;
+ }
+ } else {
+ $hasContentTypeMetaTagInHead = false;
+ }
+
+ return $hasContentTypeMetaTagInHead;
+ }
+
+ /**
+ * Tests whether the `<head>` element ends within the given HTML. Due to tag omission rules, HTML parsers are
+ * expected to end the `<head>` element and start the `<body>` element upon encountering a start tag for any element
+ * which is permitted only within the `<body>`.
+ *
+ * @param string $html
+ *
+ * @return bool
+ *
+ * @throws \RuntimeException
+ */
+ private function hasEndOfHeadElement(string $html): bool
+ {
+ $headEndTagMatchCount
+ = \preg_match('%<(?!' . self::TAGNAME_ALLOWED_BEFORE_BODY_MATCHER . '[\\s/>])\\w|</head>%i', $html);
+ if (\is_int($headEndTagMatchCount) && $headEndTagMatchCount > 0) {
+ // An exception to the implicit end of the `<head>` is any content within a `<template>` element, as well in
+ // comments. As an optimization, this is only checked for if a potential `<head>` end tag is found.
+ $htmlWithoutCommentsOrTemplates = $this->removeHtmlTemplateElements($this->removeHtmlComments($html));
+ $hasEndOfHeadElement = $htmlWithoutCommentsOrTemplates === $html
+ || $this->hasEndOfHeadElement($htmlWithoutCommentsOrTemplates);
+ } else {
+ $hasEndOfHeadElement = false;
+ }
+
+ return $hasEndOfHeadElement;
+ }
+
+ /**
+ * Removes comments from the given HTML, including any which are unterminated, for which the remainder of the string
+ * is removed.
+ *
+ * @param string $html
+ *
+ * @return string
+ *
+ * @throws \RuntimeException
+ */
+ private function removeHtmlComments(string $html): string
+ {
+ $result = \preg_replace(self::HTML_COMMENT_PATTERN, '', $html);
+ if (!\is_string($result)) {
+ throw new \RuntimeException('Internal PCRE error', 1616521475);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Removes `<template>` elements from the given HTML, including any without an end tag, for which the remainder of
+ * the string is removed.
+ *
+ * @param string $html
+ *
+ * @return string
+ *
+ * @throws \RuntimeException
+ */
+ private function removeHtmlTemplateElements(string $html): string
+ {
+ $result = \preg_replace(self::HTML_TEMPLATE_ELEMENT_PATTERN, '', $html);
+ if (!\is_string($result)) {
+ throw new \RuntimeException('Internal PCRE error', 1616519652);
+ }
+
+ return $result;
+ }
+
/**
* Makes sure that any self-closing tags not recognized as such by PHP's DOMDocument implementation have a
* self-closing slash.
private function ensurePhpUnrecognizedSelfClosingTagsAreXml(string $html): string
{
return \preg_replace(
- '%<' . static::PHP_UNRECOGNIZED_VOID_TAGNAME_MATCHER . '\\b[^>]*+(?<!/)(?=>)%',
+ '%<' . self::PHP_UNRECOGNIZED_VOID_TAGNAME_MATCHER . '\\b[^>]*+(?<!/)(?=>)%',
'$0/',
$html
);