This commit is contained in:
Roland Gruber 2025-09-01 20:41:04 +02:00
parent d78ddb43b1
commit 0593b55ed9
345 changed files with 3911 additions and 1907 deletions

430
lam/composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -60,9 +60,7 @@ abstract class AbstractErrorParser
foreach ($errors as $key => $error) { foreach ($errors as $key => $error) {
// If error code matches a known error shape, populate the body // If error code matches a known error shape, populate the body
if ($data['code'] == $error['name'] if ($this->errorCodeMatches($data, $error)) {
&& $error instanceof StructureShape
) {
$modeledError = $error; $modeledError = $error;
$data['body'] = $this->extractPayload( $data['body'] = $this->extractPayload(
$modeledError, $modeledError,
@ -92,4 +90,10 @@ abstract class AbstractErrorParser
return $data; return $data;
} }
private function errorCodeMatches(array $data, $error): bool
{
return $data['code'] == $error['name']
|| (isset($error['error']['code']) && $data['code'] === $error['error']['code']);
}
} }

View file

@ -12,41 +12,133 @@ trait JsonParserTrait
{ {
use PayloadParserTrait; use PayloadParserTrait;
private function genericHandler(ResponseInterface $response) private function genericHandler(ResponseInterface $response): array
{ {
$code = (string) $response->getStatusCode(); $code = (string) $response->getStatusCode();
$error_code = null;
$error_type = null;
// Parse error code and type for query compatible services
if ($this->api if ($this->api
&& !is_null($this->api->getMetadata('awsQueryCompatible')) && !is_null($this->api->getMetadata('awsQueryCompatible'))
&& $response->getHeaderLine('x-amzn-query-error') && $response->hasHeader('x-amzn-query-error')
) { ) {
$queryError = $response->getHeaderLine('x-amzn-query-error'); $awsQueryError = $this->parseAwsQueryCompatibleHeader($response);
$parts = explode(';', $queryError); if ($awsQueryError) {
if (isset($parts) && count($parts) == 2 && $parts[0] && $parts[1]) { $error_code = $awsQueryError['code'];
$error_code = $parts[0]; $error_type = $awsQueryError['type'];
$error_type = $parts[1];
} }
} }
// Parse error code from X-Amzn-Errortype header
if (!$error_code && $response->hasHeader('X-Amzn-Errortype')) {
$error_code = $this->extractErrorCode(
$response->getHeaderLine('X-Amzn-Errortype')
);
}
$parsedBody = null;
$body = $response->getBody();
if (!$body->isSeekable() || $body->getSize()) {
$parsedBody = $this->parseJson((string) $body, $response);
}
// Parse error code from response body
if (!$error_code && $parsedBody) {
$error_code = $this->parseErrorFromBody($parsedBody);
}
if (!isset($error_type)) { if (!isset($error_type)) {
$error_type = $code[0] == '4' ? 'client' : 'server'; $error_type = $code[0] == '4' ? 'client' : 'server';
} }
return [ return [
'request_id' => (string) $response->getHeaderLine('x-amzn-requestid'), 'request_id' => $response->getHeaderLine('x-amzn-requestid'),
'code' => isset($error_code) ? $error_code : null, 'code' => $error_code ?? null,
'message' => null, 'message' => null,
'type' => $error_type, 'type' => $error_type,
'parsed' => $this->parseJson($response->getBody(), $response) 'parsed' => $parsedBody
]; ];
} }
/**
* Parse AWS Query Compatible error from header
*
* @param ResponseInterface $response
* @return array|null Returns ['code' => string, 'type' => string] or null
*/
private function parseAwsQueryCompatibleHeader(ResponseInterface $response): ?array
{
$queryError = $response->getHeaderLine('x-amzn-query-error');
$parts = explode(';', $queryError);
if (count($parts) === 2 && $parts[0] && $parts[1]) {
return [
'code' => $parts[0],
'type' => $parts[1]
];
}
return null;
}
/**
* Parse error code from response body
*
* @param array|null $parsedBody
* @return string|null
*/
private function parseErrorFromBody(?array $parsedBody): ?string
{
if (!$parsedBody
|| (!isset($parsedBody['code']) && !isset($parsedBody['__type']))
) {
return null;
}
$error_code = $parsedBody['code'] ?? $parsedBody['__type'];
return $this->extractErrorCode($error_code);
}
/**
* Extract error code from raw error string containing # and/or : delimiters
*
* @param string $rawErrorCode
* @return string
*/
private function extractErrorCode(string $rawErrorCode): string
{
// Handle format with both # and uri (e.g., "namespace#http://foo-bar")
if (str_contains($rawErrorCode, ':') && str_contains($rawErrorCode, '#')) {
$start = strpos($rawErrorCode, '#') + 1;
$end = strpos($rawErrorCode, ':', $start);
return substr($rawErrorCode, $start, $end - $start);
}
// Handle format with uri only : (e.g., "ErrorCode:http://foo-bar.com/baz")
if (str_contains($rawErrorCode, ':')) {
return substr($rawErrorCode, 0, strpos($rawErrorCode, ':'));
}
// Handle format with only # (e.g., "namespace#ErrorCode")
if (str_contains($rawErrorCode, '#')) {
return substr($rawErrorCode, strpos($rawErrorCode, '#') + 1);
}
return $rawErrorCode;
}
protected function payload( protected function payload(
ResponseInterface $response, ResponseInterface $response,
StructureShape $member StructureShape $member
) { ) {
$jsonBody = $this->parseJson($response->getBody(), $response); $body = $response->getBody();
if (!$body->isSeekable() || $body->getSize()) {
$jsonBody = $this->parseJson($body, $response);
} else {
$jsonBody = (string) $body;
}
if ($jsonBody) {
return $this->parser->parse($member, $jsonBody); return $this->parser->parse($member, $jsonBody);
} }
}
} }

View file

@ -37,9 +37,7 @@ class JsonRpcErrorParser extends AbstractErrorParser
$parts = explode('#', $data['parsed']['__type']); $parts = explode('#', $data['parsed']['__type']);
$data['code'] = isset($parts[1]) ? $parts[1] : $parts[0]; $data['code'] = isset($parts[1]) ? $parts[1] : $parts[0];
} }
$data['message'] = isset($data['parsed']['message']) $data['message'] = $data['parsed']['message'] ?? null;
? $data['parsed']['message']
: null;
} }
$this->populateShape($data, $response, $command); $this->populateShape($data, $response, $command);

View file

@ -30,7 +30,7 @@ class RestJsonErrorParser extends AbstractErrorParser
// Merge in error data from the JSON body // Merge in error data from the JSON body
if ($json = $data['parsed']) { if ($json = $data['parsed']) {
$data = array_replace($data, $json); $data = array_replace($json, $data);
} }
// Correct error type from services like Amazon Glacier // Correct error type from services like Amazon Glacier
@ -38,18 +38,9 @@ class RestJsonErrorParser extends AbstractErrorParser
$data['type'] = strtolower($data['type']); $data['type'] = strtolower($data['type']);
} }
// Retrieve the error code from services like Amazon Elastic Transcoder
if ($code = $response->getHeaderLine('x-amzn-errortype')) {
$colon = strpos($code, ':');
$data['code'] = $colon ? substr($code, 0, $colon) : $code;
}
// Retrieve error message directly // Retrieve error message directly
$data['message'] = isset($data['parsed']['message']) $data['message'] = $data['parsed']['message']
? $data['parsed']['message'] ?? ($data['parsed']['Message'] ?? null);
: (isset($data['parsed']['Message'])
? $data['parsed']['Message']
: null);
$this->populateShape($data, $response, $command); $this->populateShape($data, $response, $command);

View file

@ -55,8 +55,9 @@ abstract class AbstractRestParser extends AbstractParser
} }
} }
$body = $response->getBody();
if (!$payload if (!$payload
&& $response->getBody()->getSize() > 0 && (!$body->isSeekable() || $body->getSize())
&& count($output->getMembers()) > 0 && count($output->getMembers()) > 0
) { ) {
// if no payload was found, then parse the contents of the body // if no payload was found, then parse the contents of the body
@ -73,20 +74,26 @@ abstract class AbstractRestParser extends AbstractParser
array &$result array &$result
) { ) {
$member = $output->getMember($payload); $member = $output->getMember($payload);
$body = $response->getBody();
if (!empty($member['eventstream'])) { if (!empty($member['eventstream'])) {
$result[$payload] = new EventParsingIterator( $result[$payload] = new EventParsingIterator(
$response->getBody(), $body,
$member, $member,
$this $this
); );
} else if ($member instanceof StructureShape) { } elseif ($member instanceof StructureShape) {
// Structure members parse top-level data into a specific key. //Unions must have at least one member set to a non-null value
// If the body is empty, we can assume it is unset
if (!empty($member['union']) && ($body->isSeekable() && !$body->getSize())) {
return;
}
$result[$payload] = []; $result[$payload] = [];
$this->payload($response, $member, $result[$payload]); $this->payload($response, $member, $result[$payload]);
} else { } else {
// Streaming data is just the stream from the response body. // Always set the payload to the body stream, regardless of content
$result[$payload] = $response->getBody(); $result[$payload] = $body;
} }
} }
@ -100,13 +107,21 @@ abstract class AbstractRestParser extends AbstractParser
&$result &$result
) { ) {
$value = $response->getHeaderLine($shape['locationName'] ?: $name); $value = $response->getHeaderLine($shape['locationName'] ?: $name);
// Empty headers should not be deserialized
if ($value === null || $value === '') {
return;
}
switch ($shape->getType()) { switch ($shape->getType()) {
case 'float': case 'float':
case 'double': case 'double':
$value = (float) $value; $value = match ($value) {
'NaN', 'Infinity', '-Infinity' => $value,
default => (float) $value
};
break; break;
case 'long': case 'long':
case 'integer':
$value = (int) $value; $value = (int) $value;
break; break;
case 'boolean': case 'boolean':
@ -143,6 +158,23 @@ abstract class AbstractRestParser extends AbstractParser
//output structure. //output structure.
return; return;
} }
case 'list':
$listMember = $shape->getMember();
$type = $listMember->getType();
// Only boolean lists require special handling
// other types can be returned as-is
if ($type !== 'boolean') {
break;
}
$items = array_map('trim', explode(',', $value));
$value = array_map(
static fn($item) => filter_var($item, FILTER_VALIDATE_BOOLEAN),
$items
);
break;
} }
$result[$name] = $value; $result[$name] = $value;

View file

@ -50,8 +50,11 @@ class JsonParser
$values = $shape->getValue(); $values = $shape->getValue();
$target = []; $target = [];
foreach ($value as $k => $v) { foreach ($value as $k => $v) {
// null map values should not be deserialized
if (!is_null($v)) {
$target[$k] = $this->parse($values, $v); $target[$k] = $this->parse($values, $v);
} }
}
return $target; return $target;
case 'timestamp': case 'timestamp':

View file

@ -17,6 +17,10 @@ trait MetadataParserTrait
&$result &$result
) { ) {
$value = $response->getHeaderLine($shape['locationName'] ?: $name); $value = $response->getHeaderLine($shape['locationName'] ?: $name);
// Empty values should not be deserialized
if ($value === null || $value === '') {
return;
}
switch ($shape->getType()) { switch ($shape->getType()) {
case 'float': case 'float':
@ -24,6 +28,7 @@ trait MetadataParserTrait
$value = (float) $value; $value = (float) $value;
break; break;
case 'long': case 'long':
case 'integer':
$value = (int) $value; $value = (int) $value;
break; break;
case 'boolean': case 'boolean':

View file

@ -40,7 +40,15 @@ class QueryParser extends AbstractParser
ResponseInterface $response ResponseInterface $response
) { ) {
$output = $this->api->getOperation($command->getName())->getOutput(); $output = $this->api->getOperation($command->getName())->getOutput();
$xml = $this->parseXml($response->getBody(), $response); $body = $response->getBody();
$xml = !$body->isSeekable() || $body->getSize()
? $this->parseXml($body, $response)
: null;
// Empty request bodies should not be deserialized.
if (is_null($xml)) {
return new Result();
}
if ($this->honorResultWrapper && $output['resultWrapper']) { if ($this->honorResultWrapper && $output['resultWrapper']) {
$xml = $xml->{$output['resultWrapper']}; $xml = $xml->{$output['resultWrapper']};

View file

@ -28,10 +28,25 @@ class RestJsonParser extends AbstractRestParser
StructureShape $member, StructureShape $member,
array &$result array &$result
) { ) {
$jsonBody = $this->parseJson($response->getBody(), $response); $responseBody = (string) $response->getBody();
if ($jsonBody) { // Parse JSON if we have content
$result += $this->parser->parse($member, $jsonBody); $parsedJson = null;
if (!empty($responseBody)) {
$parsedJson = $this->parseJson($responseBody, $response);
} else {
// An empty response body should be deserialized as null
$result = $parsedJson;
return;
}
$parsedBody = $this->parser->parse($member, $parsedJson);
if (is_string($parsedBody) && $member['document']) {
// Document types can be strings: replace entire result
$result = $parsedBody;
} else {
// Merge array/object results into existing result
$result = array_merge($result, (array) $parsedBody);
} }
} }

View file

@ -76,16 +76,19 @@ class XmlParser
private function memberKey(Shape $shape, $name) private function memberKey(Shape $shape, $name)
{ {
if (null !== $shape['locationName']) { // Check if locationName came from shape definition
return $shape['locationName']; if ($shape instanceof StructureShape && isset($shape['locationName'])) {
} $originalDef = $shape->getOriginalDefinition($shape->getName());
if ($shape instanceof ListShape && $shape['flattened']) {
return $shape->getMember()['locationName'] ?: $name;
}
if ($originalDef && isset($originalDef['locationName'])
&& $originalDef['locationName'] === $shape['locationName']
) {
return $name; return $name;
} }
}
return $shape['locationName'] ?? $name;
}
private function parse_list(ListShape $shape, \SimpleXMLElement $value) private function parse_list(ListShape $shape, \SimpleXMLElement $value)
{ {
@ -132,7 +135,12 @@ class XmlParser
private function parse_float(Shape $shape, $value) private function parse_float(Shape $shape, $value)
{ {
return (float) (string) $value; $value = (string) $value;
return match ($value) {
'NaN', 'Infinity', '-Infinity' => $value,
default => (float) $value
};
} }
private function parse_integer(Shape $shape, $value) private function parse_integer(Shape $shape, $value)
@ -162,12 +170,8 @@ class XmlParser
private function parse_xml_attribute(Shape $shape, Shape $memberShape, $value) private function parse_xml_attribute(Shape $shape, Shape $memberShape, $value)
{ {
$namespace = $shape['xmlNamespace']['uri'] $namespace = $shape['xmlNamespace']['uri'] ?? '';
? $shape['xmlNamespace']['uri'] $prefix = $shape['xmlNamespace']['prefix'] ?? '';
: '';
$prefix = $shape['xmlNamespace']['prefix']
? $shape['xmlNamespace']['prefix']
: '';
if (!empty($prefix)) { if (!empty($prefix)) {
$prefix .= ':'; $prefix .= ':';
} }

View file

@ -45,14 +45,22 @@ class JsonBody
* Builds the JSON body based on an array of arguments. * Builds the JSON body based on an array of arguments.
* *
* @param Shape $shape Operation being constructed * @param Shape $shape Operation being constructed
* @param array $args Associative array of arguments * @param array|string $args Associative array of arguments, or a string.
* *
* @return string * @return string
*/ */
public function build(Shape $shape, array $args) public function build(Shape $shape, array|string $args)
{ {
$result = json_encode($this->format($shape, $args)); try {
return $result == '[]' ? '{}' : $result; $result = json_encode($this->format($shape, $args), JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
throw new InvalidJsonException(
'Unable to encode JSON document ' . $shape->getName() . ': ' .
$e->getMessage() . PHP_EOL
);
}
return $result === '[]' ? '{}' : $result;
} }
private function format(Shape $shape, $value) private function format(Shape $shape, $value)
@ -60,7 +68,7 @@ class JsonBody
switch ($shape['type']) { switch ($shape['type']) {
case 'structure': case 'structure':
$data = []; $data = [];
if (isset($shape['document']) && $shape['document']) { if ($shape['document'] ?? false) {
return $value; return $value;
} }
foreach ($value as $k => $v) { foreach ($value as $k => $v) {

View file

@ -62,23 +62,26 @@ class JsonRpcSerializer
$operationName = $command->getName(); $operationName = $command->getName();
$operation = $this->api->getOperation($operationName); $operation = $this->api->getOperation($operationName);
$commandArgs = $command->toArray(); $commandArgs = $command->toArray();
$body = $this->jsonFormatter->build($operation->getInput(), $commandArgs);
$headers = [ $headers = [
'X-Amz-Target' => $this->api->getMetadata('targetPrefix') . '.' . $operationName, 'X-Amz-Target' => $this->api->getMetadata('targetPrefix') . '.' . $operationName,
'Content-Type' => $this->contentType 'Content-Type' => $this->contentType,
'Content-Length' => strlen($body)
]; ];
if ($endpoint instanceof RulesetEndpoint) { if ($endpoint instanceof RulesetEndpoint) {
$this->setEndpointV2RequestOptions($endpoint, $headers); $this->setEndpointV2RequestOptions($endpoint, $headers);
} }
$requestUri = $operation['http']['requestUri'] ?? null;
$absoluteUri = str_ends_with($this->endpoint, '/')
? $this->endpoint : $this->endpoint . $requestUri;
return new Request( return new Request(
$operation['http']['method'], $operation['http']['method'],
$this->endpoint, $absoluteUri,
$headers, $headers,
$this->jsonFormatter->build( $body
$operation->getInput(),
$commandArgs
)
); );
} }
} }

View file

@ -98,7 +98,8 @@ class QueryParamBuilder
if (!$this->isFlat($shape)) { if (!$this->isFlat($shape)) {
$locationName = $shape->getMember()['locationName'] ?: 'member'; $locationName = $shape->getMember()['locationName'] ?: 'member';
$prefix .= ".$locationName"; $prefix .= ".$locationName";
} elseif ($name = $this->queryName($items)) { // flattened lists can also model a `locationName`
} elseif ($name = $shape['locationName'] ?? $this->queryName($items)) {
$parts = explode('.', $prefix); $parts = explode('.', $prefix);
$parts[count($parts) - 1] = $name; $parts[count($parts) - 1] = $name;
$prefix = implode('.', $parts); $prefix = implode('.', $parts);

View file

@ -36,9 +36,7 @@ class QuerySerializer
* containing "method", "uri", "headers", and "body" key value pairs. * containing "method", "uri", "headers", and "body" key value pairs.
* *
* @param CommandInterface $command Command to serialize into a request. * @param CommandInterface $command Command to serialize into a request.
* @param $endpointProvider Provider used for dynamic endpoint resolution. * @param null $endpoint Endpoint resolved using EndpointProviderV2
* @param $clientArgs Client arguments used for dynamic endpoint resolution.
*
* @return RequestInterface * @return RequestInterface
*/ */
public function __invoke( public function __invoke(
@ -66,14 +64,17 @@ class QuerySerializer
'Content-Length' => strlen($body), 'Content-Length' => strlen($body),
'Content-Type' => 'application/x-www-form-urlencoded' 'Content-Type' => 'application/x-www-form-urlencoded'
]; ];
$requestUri = $operation['http']['requestUri'] ?? null;
if ($endpoint instanceof RulesetEndpoint) { if ($endpoint instanceof RulesetEndpoint) {
$this->setEndpointV2RequestOptions($endpoint, $headers); $this->setEndpointV2RequestOptions($endpoint, $headers);
} }
$absoluteUri = str_ends_with($this->endpoint, '/')
? $this->endpoint : $this->endpoint . $requestUri;
return new Request( return new Request(
'POST', 'POST',
$this->endpoint, $absoluteUri,
$headers, $headers,
$body $body
); );

View file

@ -31,12 +31,11 @@ class RestJsonSerializer extends RestSerializer
$this->jsonFormatter = $jsonFormatter ?: new JsonBody($api); $this->jsonFormatter = $jsonFormatter ?: new JsonBody($api);
} }
protected function payload(StructureShape $member, array $value, array &$opts) protected function payload(StructureShape $member, array|string $value, array &$opts)
{ {
$body = isset($value) ?
((string) $this->jsonFormatter->build($member, $value))
: "{}";
$opts['headers']['Content-Type'] = $this->contentType; $opts['headers']['Content-Type'] = $this->contentType;
$body = $this->jsonFormatter->build($member, $value);
$opts['headers']['Content-Length'] = strlen($body);
$opts['body'] = $body; $opts['body'] = $body;
} }
} }

View file

@ -1,6 +1,7 @@
<?php <?php
namespace Aws\Api\Serializer; namespace Aws\Api\Serializer;
use Aws\Api\ListShape;
use Aws\Api\MapShape; use Aws\Api\MapShape;
use Aws\Api\Service; use Aws\Api\Service;
use Aws\Api\Operation; use Aws\Api\Operation;
@ -10,11 +11,13 @@ use Aws\Api\TimestampShape;
use Aws\CommandInterface; use Aws\CommandInterface;
use Aws\EndpointV2\EndpointV2SerializerTrait; use Aws\EndpointV2\EndpointV2SerializerTrait;
use Aws\EndpointV2\Ruleset\RulesetEndpoint; use Aws\EndpointV2\Ruleset\RulesetEndpoint;
use DateTimeInterface;
use GuzzleHttp\Psr7; use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Uri; use GuzzleHttp\Psr7\Uri;
use GuzzleHttp\Psr7\UriResolver; use GuzzleHttp\Psr7\UriResolver;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;
/** /**
* Serializes HTTP locations like header, uri, payload, etc... * Serializes HTTP locations like header, uri, payload, etc...
@ -22,10 +25,15 @@ use Psr\Http\Message\RequestInterface;
*/ */
abstract class RestSerializer abstract class RestSerializer
{ {
use EndpointV2SerializerTrait; private const TEMPLATE_STRING_REGEX = '/\{([^\}]+)\}/';
private static array $excludeContentType = [
's3' => true,
'glacier' => true
];
/** @var Service */ /** @var Service */
private $api; private Service $api;
/** @var Uri */ /** @var Uri */
private $endpoint; private $endpoint;
@ -33,6 +41,8 @@ abstract class RestSerializer
/** @var bool */ /** @var bool */
private $isUseEndpointV2; private $isUseEndpointV2;
use EndpointV2SerializerTrait;
/** /**
* @param Service $api Service API description * @param Service $api Service API description
* @param string $endpoint Endpoint to connect to * @param string $endpoint Endpoint to connect to
@ -45,19 +55,18 @@ abstract class RestSerializer
/** /**
* @param CommandInterface $command Command to serialize into a request. * @param CommandInterface $command Command to serialize into a request.
* @param $clientArgs Client arguments used for dynamic endpoint resolution. * @param mixed|null $endpoint
*
* @return RequestInterface * @return RequestInterface
*/ */
public function __invoke( public function __invoke(
CommandInterface $command, CommandInterface $command,
$endpoint = null mixed $endpoint = null
) )
{ {
$operation = $this->api->getOperation($command->getName()); $operation = $this->api->getOperation($command->getName());
$commandArgs = $command->toArray(); $commandArgs = $command->toArray();
$opts = $this->serialize($operation, $commandArgs); $opts = $this->serialize($operation, $commandArgs);
$headers = isset($opts['headers']) ? $opts['headers'] : []; $headers = $opts['headers'] ?? [];
if ($endpoint instanceof RulesetEndpoint) { if ($endpoint instanceof RulesetEndpoint) {
$this->isUseEndpointV2 = true; $this->isUseEndpointV2 = true;
@ -70,7 +79,7 @@ abstract class RestSerializer
$operation['http']['method'], $operation['http']['method'],
$uri, $uri,
$headers, $headers,
isset($opts['body']) ? $opts['body'] : null $opts['body'] ?? null
); );
} }
@ -103,20 +112,20 @@ abstract class RestSerializer
$location = $member['location']; $location = $member['location'];
if (!$payload && !$location) { if (!$payload && !$location) {
$bodyMembers[$name] = $value; $bodyMembers[$name] = $value;
} elseif ($location == 'header') { } elseif ($location === 'header') {
$this->applyHeader($name, $member, $value, $opts); $this->applyHeader($name, $member, $value, $opts);
} elseif ($location == 'querystring') { } elseif ($location === 'querystring') {
$this->applyQuery($name, $member, $value, $opts); $this->applyQuery($name, $member, $value, $opts);
} elseif ($location == 'headers') { } elseif ($location === 'headers') {
$this->applyHeaderMap($name, $member, $value, $opts); $this->applyHeaderMap($name, $member, $value, $opts);
} }
} }
} }
if (isset($bodyMembers)) { if (isset($bodyMembers)) {
$this->payload($operation->getInput(), $bodyMembers, $opts); $this->payload($input, $bodyMembers, $opts);
} else if (!isset($opts['body']) && $this->hasPayloadParam($input, $payload)) { } else if (!isset($opts['body']) && $this->hasPayloadParam($input, $payload)) {
$this->payload($operation->getInput(), [], $opts); $this->payload($input, [], $opts);
} }
return $opts; return $opts;
@ -130,12 +139,32 @@ abstract class RestSerializer
$m = $input->getMember($name); $m = $input->getMember($name);
$type = $m->getType();
if ($m['streaming'] || if ($m['streaming'] ||
($m['type'] == 'string' || $m['type'] == 'blob') ($type === 'string' || $type === 'blob')
) { ) {
// This path skips setting the content-type header usually done in
// RestJsonSerializer and RestXmlSerializer.certain S3 and glacier
// operations determine content type in Middleware::ContentType()
if (!isset(self::$excludeContentType[$this->api->getServiceName()])) {
switch ($type) {
case 'string':
$opts['headers']['Content-Type'] = 'text/plain';
break;
case 'blob':
$opts['headers']['Content-Type'] = 'application/octet-stream';
break;
}
}
$body = $args[$name];
if (!$m['streaming'] && is_string($body)) {
$opts['headers']['Content-Length'] = strlen($body);
}
// Streaming bodies or payloads that are strings are // Streaming bodies or payloads that are strings are
// always just a stream of data. // always just a stream of data.
$opts['body'] = Psr7\Utils::streamFor($args[$name]); $opts['body'] = Psr7\Utils::streamFor($body);
return; return;
} }
@ -144,13 +173,29 @@ abstract class RestSerializer
private function applyHeader($name, Shape $member, $value, array &$opts) private function applyHeader($name, Shape $member, $value, array &$opts)
{ {
if ($member->getType() === 'timestamp') { // Handle lists by recursively applying header logic to each element
$timestampFormat = !empty($member['timestampFormat']) if ($member instanceof ListShape) {
? $member['timestampFormat'] $listMember = $member->getMember();
: 'rfc822'; $headerValues = [];
$value = TimestampShape::format($value, $timestampFormat);
} elseif ($member->getType() === 'boolean') { foreach ($value as $listValue) {
$value = $value ? 'true' : 'false'; $tempOpts = ['headers' => []];
$this->applyHeader('temp', $listMember, $listValue, $tempOpts);
$convertedValue = $tempOpts['headers']['temp'];
$headerValues[] = $convertedValue;
}
$value = $headerValues;
} elseif (!is_null($value)) {
switch ($member->getType()) {
case 'timestamp':
$timestampFormat = $member['timestampFormat'] ?? 'rfc822';
$value = $this->formatTimestamp($value, $timestampFormat);
break;
case 'boolean':
$value = $this->formatBoolean($value);
break;
}
} }
if ($member['jsonvalue']) { if ($member['jsonvalue']) {
@ -183,148 +228,259 @@ abstract class RestSerializer
$opts['query'] = isset($opts['query']) && is_array($opts['query']) $opts['query'] = isset($opts['query']) && is_array($opts['query'])
? $opts['query'] + $value ? $opts['query'] + $value
: $value; : $value;
} elseif ($value !== null) { } elseif ($member instanceof ListShape) {
$type = $member->getType(); $listMember = $member->getMember();
if ($type === 'boolean') { $paramName = $member['locationName'] ?: $name;
$value = $value ? 'true' : 'false';
} elseif ($type === 'timestamp') { foreach ($value as $listValue) {
$timestampFormat = !empty($member['timestampFormat']) // Recursively call applyQuery for each list element
? $member['timestampFormat'] $tempOpts = ['query' => []];
: 'iso8601'; $this->applyQuery('temp', $listMember, $listValue, $tempOpts);
$value = TimestampShape::format($value, $timestampFormat); $opts['query'][$paramName][] = $tempOpts['query']['temp'];
}
} elseif (!is_null($value)) {
switch ($member->getType()) {
case 'timestamp':
$timestampFormat = $member['timestampFormat'] ?? 'iso8601';
$value = $this->formatTimestamp($value, $timestampFormat);
break;
case 'boolean':
$value = $this->formatBoolean($value);
break;
} }
$opts['query'][$member['locationName'] ?: $name] = $value; $opts['query'][$member['locationName'] ?: $name] = $value;
} }
} }
private function buildEndpoint(Operation $operation, array $args, array $opts) private function buildEndpoint(
Operation $operation,
array $args,
array $opts
): UriInterface
{
// Expand `requestUri` field members
$relativeUri = $this->expandUriTemplate($operation, $args);
// Add query members to relativeUri
if (!empty($opts['query'])) {
$relativeUri = $this->appendQuery($opts['query'], $relativeUri);
}
// Special case - S3 keys that need path preservation
if ($this->api->getServiceName() === 's3'
&& isset($args['Key'])
&& $this->shouldPreservePath($args['Key'])
) {
return new Uri($this->endpoint . $relativeUri);
}
return $this->resolveUri($relativeUri, $opts);
}
/**
* Expands `requestUri` members
*
* @param Operation $operation
* @param array $args
*
* @return string
*/
private function expandUriTemplate(Operation $operation, array $args): string
{ {
$serviceName = $this->api->getServiceName();
// Create an associative array of variable definitions used in expansions
$varDefinitions = $this->getVarDefinitions($operation, $args); $varDefinitions = $this->getVarDefinitions($operation, $args);
$relative = preg_replace_callback( return preg_replace_callback(
'/\{([^\}]+)\}/', self::TEMPLATE_STRING_REGEX,
function (array $matches) use ($varDefinitions) { static function (array $matches) use ($varDefinitions) {
$isGreedy = substr($matches[1], -1, 1) == '+'; $isGreedy = str_ends_with($matches[1], '+');
$k = $isGreedy ? substr($matches[1], 0, -1) : $matches[1]; $varName = $isGreedy ? substr($matches[1], 0, -1) : $matches[1];
if (!isset($varDefinitions[$k])) {
if (!isset($varDefinitions[$varName])) {
return ''; return '';
} }
$value = $varDefinitions[$varName];
if ($isGreedy) { if ($isGreedy) {
return str_replace('%2F', '/', rawurlencode($varDefinitions[$k])); return str_replace('%2F', '/', rawurlencode($value));
} }
return rawurlencode($varDefinitions[$k]); return rawurlencode($value);
}, },
$operation['http']['requestUri'] $operation['http']['requestUri']
); );
// Add the query string variables or appending to one if needed.
if (!empty($opts['query'])) {
$relative = $this->appendQuery($opts['query'], $relative);
} }
$path = $this->endpoint->getPath(); /**
* Checks for path-like key names. If detected, traditional
if ($this->isUseEndpointV2 && $serviceName === 's3') { * URI resolution is bypassed.
if (substr($path, -1) === '/' && $relative[0] === '/') { *
$path = rtrim($path, '/'); * @param string $key
* @return bool
*/
private function shouldPreservePath(string $key): bool
{
// Keys with dot segments
if (str_contains($key, '.')) {
$segments = explode('/', $key);
foreach ($segments as $segment) {
if ($segment === '.' || $segment === '..') {
return true;
}
}
} }
$relative = $path . $relative;
if (strpos($relative, '../') !== false // Keys starting with slash
|| substr($relative, -2) === '..' if (str_starts_with($key, '/')) {
return true;
}
return false;
}
/**
* @param string $relativeUri
* @param array $opts
*
* @return UriInterface
*/
private function resolveUri(string $relativeUri, array $opts): UriInterface
{
$basePath = $this->endpoint->getPath();
// Only process if we have a non-empty base path
if (!empty($basePath) && $basePath !== '/') {
// if relative is just '/', we want just the base path without trailing slash
if ($relativeUri === '/' || empty($relativeUri)) {
// Remove trailing slash if present
return $this->endpoint->withPath(rtrim($basePath, '/'));
}
// if relative is '/?query', we want base path without trailing slash + query
// for now, this is only seen with S3 GetBucketLocation after processing the model
if (empty($opts['query'])
&& str_starts_with($relativeUri, '/?')
) { ) {
if ($relative[0] !== '/') { $query = substr($relativeUri, 2); // Remove '/?'
$relative = '/' . $relative; return $this->endpoint->withQuery($query);
} }
return new Uri($this->endpoint->withPath('') . $relative); // Ensure base path has trailing slash
if (!str_ends_with($basePath, '/')) {
$this->endpoint = $this->endpoint->withPath($basePath . '/');
}
// Remove leading slash from relative path to make it relative
if (str_starts_with($relativeUri, '/')) {
$relativeUri = substr($relativeUri, 1);
} }
} }
if (((!empty($relative) && $relative !== '/') return UriResolver::resolve($this->endpoint, new Uri($relativeUri));
&& !$this->isUseEndpointV2)
|| (isset($serviceName) && str_starts_with($serviceName, 'geo-'))
) {
$this->normalizePath($path);
}
// If endpoint has path, remove leading '/' to preserve URI resolution.
if ($path && $relative[0] === '/') {
$relative = substr($relative, 1);
}
//Append path to endpoint when leading '//...'
// present as uri cannot be properly resolved
if ($this->isUseEndpointV2 && strpos($relative, '//') === 0) {
return new Uri($this->endpoint . $relative);
}
// Expand path place holders using Amazon's slightly different URI
// template syntax.
return UriResolver::resolve($this->endpoint, new Uri($relative));
} }
/** /**
* @param StructureShape $input * @param StructureShape $input
* @param $payload
*
* @return bool
*/ */
private function hasPayloadParam(StructureShape $input, $payload) private function hasPayloadParam(StructureShape $input, $payload)
{ {
if ($payload) { if ($payload) {
$potentiallyEmptyTypes = ['blob','string']; $potentiallyEmptyTypes = ['blob','string'];
if ($this->api->getMetadata('protocol') == 'rest-xml') { if ($this->api->getProtocol() === 'rest-xml') {
$potentiallyEmptyTypes[] = 'structure'; $potentiallyEmptyTypes[] = 'structure';
} }
$payloadMember = $input->getMember($payload); $payloadMember = $input->getMember($payload);
if (in_array($payloadMember['type'], $potentiallyEmptyTypes)) { //unions may also be empty/unset
if (!empty($payloadMember['union'])
|| in_array($payloadMember['type'], $potentiallyEmptyTypes)
) {
return false; return false;
} }
} }
foreach ($input->getMembers() as $member) { foreach ($input->getMembers() as $member) {
if (!isset($member['location'])) { if (!isset($member['location'])) {
return true; return true;
} }
} }
return false; return false;
} }
private function appendQuery($query, $endpoint) /**
* @param $query
* @param $relativeUri
*
* @return string
*/
private function appendQuery($query, $relativeUri): string
{ {
$append = Psr7\Query::build($query); $append = Psr7\Query::build($query);
return $endpoint .= strpos($endpoint, '?') !== false ? "&{$append}" : "?{$append}"; return $relativeUri
. (str_contains($relativeUri, '?') ? "&{$append}" : "?{$append}");
} }
private function getVarDefinitions($command, $args) /**
* @param CommandInterface $command
* @param array $args
*
* @return array
*/
private function getVarDefinitions(
Operation $operation,
array $args
): array
{ {
$varDefinitions = []; $varDefinitions = [];
foreach ($command->getInput()->getMembers() as $name => $member) { foreach ($operation->getInput()->getMembers() as $name => $member) {
if ($member['location'] == 'uri') { if ($member['location'] === 'uri') {
$varDefinitions[$member['locationName'] ?: $name] = $value = $args[$name] ?? null;
isset($args[$name]) if (!is_null($value)) {
? $args[$name] switch ($member->getType()) {
: null; case 'timestamp':
$timestampFormat = $member['timestampFormat'] ?? 'iso8601';
$value = $this->formatTimestamp($value, $timestampFormat);
break;
case 'boolean':
$value = $this->formatBoolean($value);
break;
} }
} }
$varDefinitions[$member['locationName'] ?: $name] = $value;
}
}
return $varDefinitions; return $varDefinitions;
} }
/** /**
* Appends trailing slash to non-empty paths with at least one segment * @param DateTimeInterface|string|int $value
* to ensure proper URI resolution * @param string $timestampFormat
* *
* @param string $path * @return string
*
* @return void
*/ */
private function normalizePath(string $path): void private function formatTimestamp(
DateTimeInterface|string|int $value,
string $timestampFormat
): string
{ {
if (!empty($path) && $path !== '/' && substr($path, -1) !== '/') { return TimestampShape::format($value, $timestampFormat);
$this->endpoint = $this->endpoint->withPath($path . '/');
} }
/**
* @param $value
*
* @return string
*/
private function formatBoolean($value): string
{
return $value ? 'true' : 'false';
} }
} }

View file

@ -29,7 +29,9 @@ class RestXmlSerializer extends RestSerializer
protected function payload(StructureShape $member, array $value, array &$opts) protected function payload(StructureShape $member, array $value, array &$opts)
{ {
$opts['headers']['Content-Type'] = 'application/xml'; $opts['headers']['Content-Type'] = 'application/xml';
$opts['body'] = $this->getXmlBody($member, $value); $body = $this->getXmlBody($member, $value);
$opts['headers']['Content-Length'] = strlen($body);
$opts['body'] = $body;
} }
/** /**

View file

@ -14,8 +14,8 @@ use XMLWriter;
*/ */
class XmlBody class XmlBody
{ {
/** @var \Aws\Api\Service */ /** @var Service */
private $api; private Service $api;
/** /**
* @param Service $api API being used to create the XML body. * @param Service $api API being used to create the XML body.
@ -38,7 +38,10 @@ class XmlBody
$xml = new XMLWriter(); $xml = new XMLWriter();
$xml->openMemory(); $xml->openMemory();
$xml->startDocument('1.0', 'UTF-8'); $xml->startDocument('1.0', 'UTF-8');
$this->format($shape, $shape['locationName'] ?: $shape['name'], $args, $xml);
$rootElementName = $this->determineRootElementName($shape);
$this->format($shape, $rootElementName, $args, $xml);
$xml->endDocument(); $xml->endDocument();
return $xml->outputMemory(); return $xml->outputMemory();
@ -51,7 +54,7 @@ class XmlBody
if ($ns = $shape['xmlNamespace']) { if ($ns = $shape['xmlNamespace']) {
$xml->writeAttribute( $xml->writeAttribute(
isset($ns['prefix']) ? "xmlns:{$ns['prefix']}" : 'xmlns', isset($ns['prefix']) ? "xmlns:{$ns['prefix']}" : 'xmlns',
$shape['xmlNamespace']['uri'] $ns['uri']
); );
} }
} }
@ -93,9 +96,19 @@ class XmlBody
$this->startElement($shape, $name, $xml); $this->startElement($shape, $name, $xml);
foreach ($this->getStructureMembers($shape, $value) as $k => $definition) { foreach ($this->getStructureMembers($shape, $value) as $k => $definition) {
// Default to member name
$elementName = $k;
// Only use locationName for non-structure members
if (!($definition['member'] instanceof StructureShape)
&& $definition['member']['locationName']
) {
$elementName = $definition['member']['locationName'];
}
$this->format( $this->format(
$definition['member'], $definition['member'],
$definition['member']['locationName'] ?: $k, $elementName,
$definition['value'], $definition['value'],
$xml $xml
); );
@ -157,11 +170,13 @@ class XmlBody
array $value, array $value,
XMLWriter $xml XMLWriter $xml
) { ) {
$xmlEntry = $shape['flattened'] ? $shape['locationName'] : 'entry'; $xmlEntry = $shape['flattened'] ? $name : 'entry';
$xmlKey = $shape->getKey()['locationName'] ?: 'key'; $xmlKey = $shape->getKey()['locationName'] ?: 'key';
$xmlValue = $shape->getValue()['locationName'] ?: 'value'; $xmlValue = $shape->getValue()['locationName'] ?: 'value';
if (!$shape['flattened']) {
$this->startElement($shape, $name, $xml); $this->startElement($shape, $name, $xml);
}
foreach ($value as $key => $v) { foreach ($value as $key => $v) {
$this->startElement($shape, $xmlEntry, $xml); $this->startElement($shape, $xmlEntry, $xml);
@ -170,8 +185,10 @@ class XmlBody
$xml->endElement(); $xml->endElement();
} }
if (!$shape['flattened']) {
$xml->endElement(); $xml->endElement();
} }
}
private function add_blob(Shape $shape, $name, $value, XMLWriter $xml) private function add_blob(Shape $shape, $name, $value, XMLWriter $xml)
{ {
@ -217,4 +234,23 @@ class XmlBody
$this->defaultShape($shape, $name, $value, $xml); $this->defaultShape($shape, $name, $value, $xml);
} }
} }
private function determineRootElementName(Shape $shape): string
{
$shapeName = $shape->getName();
// Look up the shape definition first
if ($shapeName && $shapeMap = $shape->getShapeMap()) {
if (isset($shapeMap[$shapeName]['locationName'])) {
return $shapeMap[$shapeName]['locationName'];
}
}
// Fall back to shape's current locationName
if ($shape['locationName']) {
return $shape['locationName'];
}
return $shapeName;
}
} }

View file

@ -4,7 +4,7 @@ namespace Aws\Api;
/** /**
* Builds shape based on shape references. * Builds shape based on shape references.
*/ */
class ShapeMap class ShapeMap implements \ArrayAccess
{ {
/** @var array */ /** @var array */
private $definitions; private $definitions;
@ -65,4 +65,45 @@ class ShapeMap
return $result; return $result;
} }
/**
* @param mixed $offset
* @return bool
*/
public function offsetExists(mixed $offset): bool
{
return isset($this->definitions[$offset]);
}
/**
* @param mixed $offset
* @return mixed
*/
public function offsetGet(mixed $offset): mixed
{
return $this->definitions[$offset] ?? null;
}
/**
* @param mixed $offset
* @param mixed $value
* @throws \BadMethodCallException
*/
public function offsetSet(mixed $offset, mixed $value): void
{
throw new \BadMethodCallException(
'ShapeMap is read-only and cannot be modified.'
);
}
/**
* @param mixed $offset
* @throws \BadMethodCallException
*/
public function offsetUnset(mixed $offset): void
{
throw new \BadMethodCallException(
'ShapeMap is read-only and cannot be modified.'
);
}
} }

View file

@ -67,6 +67,32 @@ class StructureShape extends Shape
return $members[$name]; return $members[$name];
} }
/**
* Used to look up the shape's original definition.
* ShapeMap::resolve() merges properties from both
* member and target shape definitions, causing certain
* properties like `locationName` to be overwritten.
*
* @return ShapeMap
* @internal This method is for internal use only and should not be used
* by external code. It may be changed or removed without notice.
*/
public function getShapeMap(): ShapeMap
{
return $this->shapeMap;
}
/**
* Used to look up a shape's original definition.
*
* @param string $name
*
* @return array|null
*/
public function getOriginalDefinition(string $name): ?array
{
return $this->shapeMap[$name] ?? null;
}
private function generateMembersHash() private function generateMembersHash()
{ {

View file

@ -13,9 +13,9 @@ interface AuthSchemeResolverInterface
* Selects an auth scheme for request signing. * Selects an auth scheme for request signing.
* *
* @param array $authSchemes a priority-ordered list of authentication schemes. * @param array $authSchemes a priority-ordered list of authentication schemes.
* @param IdentityInterface $identity Credentials to be used in request signing. * @param array $args
* *
* @return string * @return string|null
*/ */
public function selectAuthScheme( public function selectAuthScheme(
array $authSchemes, array $authSchemes,

View file

@ -28,38 +28,50 @@ class AuthSelectionMiddleware
/** @var Service */ /** @var Service */
private $api; private $api;
/** @var array|null */
private ?array $configuredAuthSchemes;
/** /**
* Create a middleware wrapper function * Create a middleware wrapper function
* *
* @param AuthSchemeResolverInterface $authResolver * @param AuthSchemeResolverInterface $authResolver
* @param Service $api * @param Service $api
* @param array|null $configuredAuthSchemes
*
* @return Closure * @return Closure
*/ */
public static function wrap( public static function wrap(
AuthSchemeResolverInterface $authResolver, AuthSchemeResolverInterface $authResolver,
Service $api Service $api,
?array $configuredAuthSchemes
): Closure ): Closure
{ {
return function (callable $handler) use ($authResolver, $api) { return function (callable $handler) use (
return new self($handler, $authResolver, $api); $authResolver,
$api,
$configuredAuthSchemes
) {
return new self($handler, $authResolver, $api, $configuredAuthSchemes);
}; };
} }
/** /**
* @param callable $nextHandler * @param callable $nextHandler
* @param $authResolver * @param AuthSchemeResolverInterface $authResolver
* @param callable $identityProvider
* @param Service $api * @param Service $api
* @param array|null $configuredAuthSchemes
*/ */
public function __construct( public function __construct(
callable $nextHandler, callable $nextHandler,
AuthSchemeResolverInterface $authResolver, AuthSchemeResolverInterface $authResolver,
Service $api Service $api,
?array $configuredAuthSchemes = null
) )
{ {
$this->nextHandler = $nextHandler; $this->nextHandler = $nextHandler;
$this->authResolver = $authResolver; $this->authResolver = $authResolver;
$this->api = $api; $this->api = $api;
$this->configuredAuthSchemes = $configuredAuthSchemes;
} }
/** /**
@ -86,21 +98,62 @@ class AuthSelectionMiddleware
} }
try { try {
$selectedAuthScheme = $resolver->selectAuthScheme( $authSchemeList = $this->buildAuthSchemeList(
$resolvableAuth, $resolvableAuth,
$command['@context']['auth_scheme_preference']
?? null,
);
$selectedAuthScheme = $resolver->selectAuthScheme(
$authSchemeList,
['unsigned_payload' => $unsignedPayload] ['unsigned_payload' => $unsignedPayload]
); );
} catch (UnresolvedAuthSchemeException $e) {
// There was an error resolving auth
// The signature version will fall back to the modeled `signatureVersion`
// or auth schemes resolved during endpoint resolution
}
if (!empty($selectedAuthScheme)) { if (!empty($selectedAuthScheme)) {
$command['@context']['signature_version'] = $selectedAuthScheme; $command['@context']['signature_version'] = $selectedAuthScheme;
} }
} catch (UnresolvedAuthSchemeException $ignored) {
// There was an error resolving auth
// The signature version will fall back to the modeled `signatureVersion`
// or auth schemes resolved during endpoint resolution
}
} }
return $nextHandler($command); return $nextHandler($command);
} }
/**
* Prioritizes auth schemes according to user preference order.
* User-preferred schemes that are available will be placed first,
* followed by remaining available schemes.
*
* @param array $resolvableAuthSchemeList Available auth schemes
* @param array|null $commandConfiguredAuthSchemes Command-level preferences (overrides config)
*
* @return array Reordered auth schemes with user preferences first
*/
private function buildAuthSchemeList(
array $resolvableAuthSchemeList,
?array $commandConfiguredAuthSchemes,
): array
{
$userConfiguredAuthSchemes = $commandConfiguredAuthSchemes
?? $this->configuredAuthSchemes;
if (empty($userConfiguredAuthSchemes)) {
return $resolvableAuthSchemeList;
}
$prioritizedAuthSchemes = array_intersect(
$userConfiguredAuthSchemes,
$resolvableAuthSchemeList
);
// Get remaining schemes not in user preferences
$remainingAuthSchemes = array_diff(
$resolvableAuthSchemeList,
$prioritizedAuthSchemes
);
return array_merge($prioritizedAuthSchemes, $remainingAuthSchemes);
}
} }

View file

@ -272,7 +272,7 @@ class AwsClient implements AwsClientInterface
if ($this->isUseEndpointV2()) { if ($this->isUseEndpointV2()) {
$this->addEndpointV2Middleware(); $this->addEndpointV2Middleware();
} }
$this->addAuthSelectionMiddleware(); $this->addAuthSelectionMiddleware($config);
if (!is_null($this->api->getMetadata('awsQueryCompatible'))) { if (!is_null($this->api->getMetadata('awsQueryCompatible'))) {
$this->addQueryCompatibleInputMiddleware($this->api); $this->addQueryCompatibleInputMiddleware($this->api);
@ -303,6 +303,12 @@ class AwsClient implements AwsClientInterface
return $fn(); return $fn();
} }
public function getToken()
{
$fn = $this->tokenProvider;
return $fn();
}
public function getEndpoint() public function getEndpoint()
{ {
@ -589,14 +595,15 @@ class AwsClient implements AwsClientInterface
); );
} }
private function addAuthSelectionMiddleware() private function addAuthSelectionMiddleware(array $args)
{ {
$list = $this->getHandlerList(); $list = $this->getHandlerList();
$list->prependBuild( $list->prependBuild(
AuthSelectionMiddleware::wrap( AuthSelectionMiddleware::wrap(
$this->authSchemeResolver, $this->authSchemeResolver,
$this->getApi() $this->getApi(),
$args['auth_scheme_preference'] ?? null
), ),
'auth-selection' 'auth-selection'
); );

View file

@ -34,6 +34,7 @@ use Aws\Retry\ConfigurationProvider as RetryConfigProvider;
use Aws\Signature\SignatureProvider; use Aws\Signature\SignatureProvider;
use Aws\Token\Token; use Aws\Token\Token;
use Aws\Token\TokenInterface; use Aws\Token\TokenInterface;
use Aws\Token\BedrockTokenProvider;
use Aws\Token\TokenProvider; use Aws\Token\TokenProvider;
use GuzzleHttp\Promise\PromiseInterface; use GuzzleHttp\Promise\PromiseInterface;
use InvalidArgumentException as IAE; use InvalidArgumentException as IAE;
@ -206,6 +207,13 @@ class ClientResolver
'fn' => [__CLASS__, '_apply_credentials'], 'fn' => [__CLASS__, '_apply_credentials'],
'default' => [__CLASS__, '_default_credential_provider'], 'default' => [__CLASS__, '_default_credential_provider'],
], ],
'auth_scheme_preference' => [
'type' => 'value',
'valid' => ['string', 'array'],
'doc' => 'Comma-separated list of authentication scheme preferences in priority order. Configure via environment variable `AWS_AUTH_SCHEME_PREFERENCE`, INI config file `auth_scheme_preference`, or client constructor parameter `auth_scheme_preference` (string or array).\nExample: `AWS_AUTH_SCHEME_PREFERENCE=aws.auth#sigv4a,aws.auth#sigv4,smithy.api#httpBearerAuth`',
'default' => self::DEFAULT_FROM_ENV_INI,
'fn' => [__CLASS__, '_apply_auth_scheme_preference'],
],
'token' => [ 'token' => [
'type' => 'value', 'type' => 'value',
'valid' => [TokenInterface::class, CacheInterface::class, 'array', 'bool', 'callable'], 'valid' => [TokenInterface::class, CacheInterface::class, 'array', 'bool', 'callable'],
@ -704,8 +712,17 @@ class ClientResolver
} }
} }
public static function _default_token_provider(array $args) public static function _default_token_provider(array &$args)
{ {
if (($args['config']['signing_name'] ?? '') === 'bedrock') {
// Checks for env value, if present, sets auth_scheme_preference
// to bearer auth and returns a provider
$provider = BedrockTokenProvider::createIfAvailable($args);
if (!is_null($provider)) {
return $provider;
}
}
return TokenProvider::defaultProvider($args); return TokenProvider::defaultProvider($args);
} }
@ -1122,6 +1139,32 @@ class ClientResolver
return new AuthSchemeResolver($args['credentials'], $args['token']); return new AuthSchemeResolver($args['credentials'], $args['token']);
} }
public static function _apply_auth_scheme_preference(
string|array|null &$value,
array &$args
): void
{
// Not provided user's preference auth scheme list
if (empty($value)) {
$value = null;
$args['config']['auth_scheme_preference'] = $value;
return;
}
// Normalize it as an array
if (is_string($value)) {
$value = explode(',', $value);
}
// Let`s trim each value to remove break lines, spaces and/or tabs
foreach ($value as &$val) {
$val = trim($val);
}
// Assign user's preferred auth scheme list
$args['auth_scheme_preference'] = $value;
}
public static function _default_signature_version(array &$args) public static function _default_signature_version(array &$args)
{ {
if (isset($args['config']['signature_version'])) { if (isset($args['config']['signature_version'])) {

View file

@ -68,7 +68,7 @@ class ConfigurationResolver
* *
* @return null | mixed * @return null | mixed
*/ */
public static function env($key, $expectedType) public static function env($key, $expectedType = 'string')
{ {
// Use config from environment variables, if available // Use config from environment variables, if available
$envValue = getenv(self::$envPrefix . strtoupper($key)); $envValue = getenv(self::$envPrefix . strtoupper($key));
@ -203,6 +203,7 @@ class ConfigurationResolver
) { ) {
$value = intVal($value); $value = intVal($value);
} }
return $value; return $value;
} }

View file

@ -52,6 +52,7 @@ class CredentialProvider
const ENV_SESSION = 'AWS_SESSION_TOKEN'; const ENV_SESSION = 'AWS_SESSION_TOKEN';
const ENV_TOKEN_FILE = 'AWS_WEB_IDENTITY_TOKEN_FILE'; const ENV_TOKEN_FILE = 'AWS_WEB_IDENTITY_TOKEN_FILE';
const ENV_SHARED_CREDENTIALS_FILE = 'AWS_SHARED_CREDENTIALS_FILE'; const ENV_SHARED_CREDENTIALS_FILE = 'AWS_SHARED_CREDENTIALS_FILE';
public const REFRESH_WINDOW = 60;
/** /**
* Create a default credential provider that * Create a default credential provider that
@ -224,10 +225,14 @@ class CredentialProvider
return $creds; return $creds;
} }
// Refresh expired credentials. // Check if credentials are expired or will expire in 1 minute
if (!$creds->isExpired()) { $needsRefresh = $creds->getExpiration() - time() <= self::REFRESH_WINDOW;
// Refresh if expired or expiring soon
if (!$needsRefresh && !$creds->isExpired()) {
return $creds; return $creds;
} }
// Refresh the result and forward the promise. // Refresh the result and forward the promise.
return $result = $provider($creds); return $result = $provider($creds);
}) })

View file

@ -142,8 +142,12 @@ final class Middleware
* *
* @return callable * @return callable
*/ */
public static function signer(callable $credProvider, callable $signatureFunction, $tokenProvider = null, $config = []) public static function signer(
{ callable $credProvider,
callable $signatureFunction,
$tokenProvider = null,
$config = []
) {
return function (callable $handler) use ($signatureFunction, $credProvider, $tokenProvider, $config) { return function (callable $handler) use ($signatureFunction, $credProvider, $tokenProvider, $config) {
return function ( return function (
CommandInterface $command, CommandInterface $command,

View file

@ -51,9 +51,7 @@ class RequestCompressionMiddleware
} }
$nextHandler = $this->nextHandler; $nextHandler = $this->nextHandler;
$operation = $this->api->getOperation($command->getName()); $operation = $this->api->getOperation($command->getName());
$compressionInfo = isset($operation['requestcompression']) $compressionInfo = $operation['requestcompression'] ?? null;
? $operation['requestcompression']
: null;
if (!$this->shouldCompressRequestBody( if (!$this->shouldCompressRequestBody(
$compressionInfo, $compressionInfo,
@ -87,8 +85,12 @@ class RequestCompressionMiddleware
$body = $request->getBody()->getContents(); $body = $request->getBody()->getContents();
$compressedBody = $fn($body); $compressedBody = $fn($body);
return $request->withBody(Psr7\Utils::streamFor($compressedBody)) $request = $request->withBody(Psr7\Utils::streamFor($compressedBody));
->withHeader('content-encoding', $this->encoding); if ($request->hasHeader('Content-Encoding')) {
return $request->withAddedHeader('Content-Encoding', $this->encoding);
}
return $request->withHeader('Content-Encoding', $this->encoding);
} }
private function determineEncoding() private function determineEncoding()

View file

@ -2,6 +2,7 @@
namespace Aws\S3; namespace Aws\S3;
use Aws\CommandInterface; use Aws\CommandInterface;
use GuzzleHttp\Psr7\Uri;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\RequestInterface;
/** /**
@ -16,22 +17,36 @@ class BucketEndpointMiddleware
{ {
private static $exclusions = ['GetBucketLocation' => true]; private static $exclusions = ['GetBucketLocation' => true];
private $nextHandler; private $nextHandler;
private bool $useEndpointV2;
private ?string $endpoint;
/** /**
* Create a middleware wrapper function. * Create a middleware wrapper function.
* *
* @param bool $useEndpointV2
* @param string|null $endpoint
*
* @return callable * @return callable
*/ */
public static function wrap() public static function wrap(
bool $useEndpointV2 = false,
?string $endpoint = null
): callable
{ {
return function (callable $handler) { return function (callable $handler) use ($useEndpointV2, $endpoint) {
return new self($handler); return new self($handler, $useEndpointV2, $endpoint);
}; };
} }
public function __construct(callable $nextHandler) public function __construct(
callable $nextHandler,
bool $useEndpointV2,
?string $endpoint = null
)
{ {
$this->nextHandler = $nextHandler; $this->nextHandler = $nextHandler;
$this->useEndpointV2 = $useEndpointV2;
$this->endpoint = $endpoint;
} }
public function __invoke(CommandInterface $command, RequestInterface $request) public function __invoke(CommandInterface $command, RequestInterface $request)
@ -47,74 +62,50 @@ class BucketEndpointMiddleware
} }
/** /**
* Performs a one-time removal of Bucket from path, then if * @param string $path
* the bucket name is duplicated in the path, performs additional * @param string $bucket
* removal which is dependent on the number of occurrences of the bucket
* name in a path-like format in the key name.
* *
* @return string * @return string
*/ */
private function removeBucketFromPath($path, $bucket, $key) private function removeBucketFromPath(string $path, string $bucket): string
{ {
$occurrencesInKey = $this->getBucketNameOccurrencesInKey($key, $bucket);
do {
$len = strlen($bucket) + 1; $len = strlen($bucket) + 1;
if (substr($path, 0, $len) === "/{$bucket}") { if (str_starts_with($path, "/{$bucket}")) {
$path = substr($path, $len); $path = substr($path, $len);
} }
} while (substr_count($path, "/{$bucket}") > $occurrencesInKey + 1);
return $path ?: '/'; return $path ?: '/';
} }
private function removeDuplicateBucketFromHost($host, $bucket) /**
{ * @param RequestInterface $request
if (substr_count($host, $bucket) > 1) { * @param CommandInterface $command
while (strpos($host, "{$bucket}.{$bucket}") === 0) { *
$hostArr = explode('.', $host); * @return RequestInterface
array_shift($hostArr); */
$host = implode('.', $hostArr);
}
}
return $host;
}
private function getBucketNameOccurrencesInKey($key, $bucket)
{
$occurrences = 0;
if (empty($key)) {
return $occurrences;
}
$segments = explode('/', $key);
foreach($segments as $segment) {
if (strpos($segment, $bucket) === 0) {
$occurrences++;
}
}
return $occurrences;
}
private function modifyRequest( private function modifyRequest(
RequestInterface $request, RequestInterface $request,
CommandInterface $command CommandInterface $command
) { ): RequestInterface
$key = isset($command['Key']) ? $command['Key'] : null; {
$uri = $request->getUri(); $uri = $request->getUri();
$path = $uri->getPath(); $path = $uri->getPath();
$host = $uri->getHost(); $host = $uri->getHost();
$bucket = $command['Bucket']; $bucket = $command['Bucket'];
$path = $this->removeBucketFromPath($path, $bucket, $key);
$host = $this->removeDuplicateBucketFromHost($host, $bucket); if ($this->useEndpointV2 && !empty($this->endpoint)) {
// V2 provider adds bucket name to host by default
// preserve original host
$host = (new Uri($this->endpoint))->getHost();
}
$path = $this->removeBucketFromPath($path, $bucket);
// Modify the Key to make sure the key is encoded, but slashes are not. // Modify the Key to make sure the key is encoded, but slashes are not.
if ($key) { if ($command['Key']) {
$path = S3Client::encodeKey(rawurldecode($path)); $path = S3Client::encodeKey(rawurldecode($path));
} }
return $request->withUri( return $request->withUri($uri->withPath($path)->withHost($host));
$uri->withHost($host)
->withPath($path)
);
} }
} }

View file

@ -41,6 +41,8 @@ use Psr\Http\Message\RequestInterface;
* @method \GuzzleHttp\Promise\Promise copyObjectAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise copyObjectAsync(array $args = [])
* @method \Aws\Result createBucket(array $args = []) * @method \Aws\Result createBucket(array $args = [])
* @method \GuzzleHttp\Promise\Promise createBucketAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise createBucketAsync(array $args = [])
* @method \Aws\Result createBucketMetadataConfiguration(array $args = [])
* @method \GuzzleHttp\Promise\Promise createBucketMetadataConfigurationAsync(array $args = [])
* @method \Aws\Result createBucketMetadataTableConfiguration(array $args = []) * @method \Aws\Result createBucketMetadataTableConfiguration(array $args = [])
* @method \GuzzleHttp\Promise\Promise createBucketMetadataTableConfigurationAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise createBucketMetadataTableConfigurationAsync(array $args = [])
* @method \Aws\Result createMultipartUpload(array $args = []) * @method \Aws\Result createMultipartUpload(array $args = [])
@ -61,6 +63,8 @@ use Psr\Http\Message\RequestInterface;
* @method \GuzzleHttp\Promise\Promise deleteBucketInventoryConfigurationAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise deleteBucketInventoryConfigurationAsync(array $args = [])
* @method \Aws\Result deleteBucketLifecycle(array $args = []) * @method \Aws\Result deleteBucketLifecycle(array $args = [])
* @method \GuzzleHttp\Promise\Promise deleteBucketLifecycleAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise deleteBucketLifecycleAsync(array $args = [])
* @method \Aws\Result deleteBucketMetadataConfiguration(array $args = [])
* @method \GuzzleHttp\Promise\Promise deleteBucketMetadataConfigurationAsync(array $args = [])
* @method \Aws\Result deleteBucketMetadataTableConfiguration(array $args = []) * @method \Aws\Result deleteBucketMetadataTableConfiguration(array $args = [])
* @method \GuzzleHttp\Promise\Promise deleteBucketMetadataTableConfigurationAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise deleteBucketMetadataTableConfigurationAsync(array $args = [])
* @method \Aws\Result deleteBucketMetricsConfiguration(array $args = []) * @method \Aws\Result deleteBucketMetricsConfiguration(array $args = [])
@ -105,6 +109,8 @@ use Psr\Http\Message\RequestInterface;
* @method \GuzzleHttp\Promise\Promise getBucketLocationAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise getBucketLocationAsync(array $args = [])
* @method \Aws\Result getBucketLogging(array $args = []) * @method \Aws\Result getBucketLogging(array $args = [])
* @method \GuzzleHttp\Promise\Promise getBucketLoggingAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise getBucketLoggingAsync(array $args = [])
* @method \Aws\Result getBucketMetadataConfiguration(array $args = [])
* @method \GuzzleHttp\Promise\Promise getBucketMetadataConfigurationAsync(array $args = [])
* @method \Aws\Result getBucketMetadataTableConfiguration(array $args = []) * @method \Aws\Result getBucketMetadataTableConfiguration(array $args = [])
* @method \GuzzleHttp\Promise\Promise getBucketMetadataTableConfigurationAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise getBucketMetadataTableConfigurationAsync(array $args = [])
* @method \Aws\Result getBucketMetricsConfiguration(array $args = []) * @method \Aws\Result getBucketMetricsConfiguration(array $args = [])
@ -233,6 +239,10 @@ use Psr\Http\Message\RequestInterface;
* @method \GuzzleHttp\Promise\Promise restoreObjectAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise restoreObjectAsync(array $args = [])
* @method \Aws\Result selectObjectContent(array $args = []) * @method \Aws\Result selectObjectContent(array $args = [])
* @method \GuzzleHttp\Promise\Promise selectObjectContentAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise selectObjectContentAsync(array $args = [])
* @method \Aws\Result updateBucketMetadataInventoryTableConfiguration(array $args = [])
* @method \GuzzleHttp\Promise\Promise updateBucketMetadataInventoryTableConfigurationAsync(array $args = [])
* @method \Aws\Result updateBucketMetadataJournalTableConfiguration(array $args = [])
* @method \GuzzleHttp\Promise\Promise updateBucketMetadataJournalTableConfigurationAsync(array $args = [])
* @method \Aws\Result uploadPart(array $args = []) * @method \Aws\Result uploadPart(array $args = [])
* @method \GuzzleHttp\Promise\Promise uploadPartAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise uploadPartAsync(array $args = [])
* @method \Aws\Result uploadPartCopy(array $args = []) * @method \Aws\Result uploadPartCopy(array $args = [])
@ -422,6 +432,7 @@ class S3Client extends AwsClient implements S3ClientInterface
$this->addBuiltIns($args); $this->addBuiltIns($args);
parent::__construct($args); parent::__construct($args);
$stack = $this->getHandlerList(); $stack = $this->getHandlerList();
$config = $this->getConfig();
$stack->appendInit(SSECMiddleware::wrap($this->getEndpoint()->getScheme()), 's3.ssec'); $stack->appendInit(SSECMiddleware::wrap($this->getEndpoint()->getScheme()), 's3.ssec');
$stack->appendBuild( $stack->appendBuild(
ApplyChecksumMiddleware::wrap($this->getApi(), $this->getConfig()), ApplyChecksumMiddleware::wrap($this->getApi(), $this->getConfig()),
@ -433,7 +444,9 @@ class S3Client extends AwsClient implements S3ClientInterface
); );
if ($this->getConfig('bucket_endpoint')) { if ($this->getConfig('bucket_endpoint')) {
$stack->appendBuild(BucketEndpointMiddleware::wrap(), 's3.bucket_endpoint'); $stack->appendBuild(BucketEndpointMiddleware::wrap(
$this->isUseEndpointV2(), $args['endpoint'] ?? null), 's3.bucket_endpoint'
);
} elseif (!$this->isUseEndpointV2()) { } elseif (!$this->isUseEndpointV2()) {
$stack->appendBuild( $stack->appendBuild(
S3EndpointMiddleware::wrap( S3EndpointMiddleware::wrap(
@ -916,6 +929,10 @@ class S3Client extends AwsClient implements S3ClientInterface
$requestUri = str_replace('/{Bucket}', '/', $requestUri); $requestUri = str_replace('/{Bucket}', '/', $requestUri);
} else { } else {
$requestUri = str_replace('/{Bucket}', '', $requestUri); $requestUri = str_replace('/{Bucket}', '', $requestUri);
// If we're left with just a query string, prepend '/'
if (str_starts_with($requestUri, '?')) {
$requestUri = '/' . $requestUri;
}
} }
$operation['http']['requestUri'] = $requestUri; $operation['http']['requestUri'] = $requestUri;
} }
@ -924,7 +941,7 @@ class S3Client extends AwsClient implements S3ClientInterface
foreach ($definition['shapes'] as $key => &$value) { foreach ($definition['shapes'] as $key => &$value) {
$suffix = 'Output'; $suffix = 'Output';
if (substr($key, -strlen($suffix)) === $suffix) { if (str_ends_with($key, $suffix)) {
if (isset($value['members']['Expires'])) { if (isset($value['members']['Expires'])) {
$value['members']['Expires']['deprecated'] = true; $value['members']['Expires']['deprecated'] = true;
$value['members']['ExpiresString'] = [ $value['members']['ExpiresString'] = [

View file

@ -20,6 +20,8 @@ use GuzzleHttp\Promise;
* @method \GuzzleHttp\Promise\Promise copyObjectAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise copyObjectAsync(array $args = [])
* @method \Aws\Result createBucket(array $args = []) * @method \Aws\Result createBucket(array $args = [])
* @method \GuzzleHttp\Promise\Promise createBucketAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise createBucketAsync(array $args = [])
* @method \Aws\Result createBucketMetadataConfiguration(array $args = [])
* @method \GuzzleHttp\Promise\Promise createBucketMetadataConfigurationAsync(array $args = [])
* @method \Aws\Result createBucketMetadataTableConfiguration(array $args = []) * @method \Aws\Result createBucketMetadataTableConfiguration(array $args = [])
* @method \GuzzleHttp\Promise\Promise createBucketMetadataTableConfigurationAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise createBucketMetadataTableConfigurationAsync(array $args = [])
* @method \Aws\Result createMultipartUpload(array $args = []) * @method \Aws\Result createMultipartUpload(array $args = [])
@ -40,6 +42,8 @@ use GuzzleHttp\Promise;
* @method \GuzzleHttp\Promise\Promise deleteBucketInventoryConfigurationAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise deleteBucketInventoryConfigurationAsync(array $args = [])
* @method \Aws\Result deleteBucketLifecycle(array $args = []) * @method \Aws\Result deleteBucketLifecycle(array $args = [])
* @method \GuzzleHttp\Promise\Promise deleteBucketLifecycleAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise deleteBucketLifecycleAsync(array $args = [])
* @method \Aws\Result deleteBucketMetadataConfiguration(array $args = [])
* @method \GuzzleHttp\Promise\Promise deleteBucketMetadataConfigurationAsync(array $args = [])
* @method \Aws\Result deleteBucketMetadataTableConfiguration(array $args = []) * @method \Aws\Result deleteBucketMetadataTableConfiguration(array $args = [])
* @method \GuzzleHttp\Promise\Promise deleteBucketMetadataTableConfigurationAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise deleteBucketMetadataTableConfigurationAsync(array $args = [])
* @method \Aws\Result deleteBucketMetricsConfiguration(array $args = []) * @method \Aws\Result deleteBucketMetricsConfiguration(array $args = [])
@ -84,6 +88,8 @@ use GuzzleHttp\Promise;
* @method \GuzzleHttp\Promise\Promise getBucketLocationAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise getBucketLocationAsync(array $args = [])
* @method \Aws\Result getBucketLogging(array $args = []) * @method \Aws\Result getBucketLogging(array $args = [])
* @method \GuzzleHttp\Promise\Promise getBucketLoggingAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise getBucketLoggingAsync(array $args = [])
* @method \Aws\Result getBucketMetadataConfiguration(array $args = [])
* @method \GuzzleHttp\Promise\Promise getBucketMetadataConfigurationAsync(array $args = [])
* @method \Aws\Result getBucketMetadataTableConfiguration(array $args = []) * @method \Aws\Result getBucketMetadataTableConfiguration(array $args = [])
* @method \GuzzleHttp\Promise\Promise getBucketMetadataTableConfigurationAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise getBucketMetadataTableConfigurationAsync(array $args = [])
* @method \Aws\Result getBucketMetricsConfiguration(array $args = []) * @method \Aws\Result getBucketMetricsConfiguration(array $args = [])
@ -212,6 +218,10 @@ use GuzzleHttp\Promise;
* @method \GuzzleHttp\Promise\Promise restoreObjectAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise restoreObjectAsync(array $args = [])
* @method \Aws\Result selectObjectContent(array $args = []) * @method \Aws\Result selectObjectContent(array $args = [])
* @method \GuzzleHttp\Promise\Promise selectObjectContentAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise selectObjectContentAsync(array $args = [])
* @method \Aws\Result updateBucketMetadataInventoryTableConfiguration(array $args = [])
* @method \GuzzleHttp\Promise\Promise updateBucketMetadataInventoryTableConfigurationAsync(array $args = [])
* @method \Aws\Result updateBucketMetadataJournalTableConfiguration(array $args = [])
* @method \GuzzleHttp\Promise\Promise updateBucketMetadataJournalTableConfigurationAsync(array $args = [])
* @method \Aws\Result uploadPart(array $args = []) * @method \Aws\Result uploadPart(array $args = [])
* @method \GuzzleHttp\Promise\Promise uploadPartAsync(array $args = []) * @method \GuzzleHttp\Promise\Promise uploadPartAsync(array $args = [])
* @method \Aws\Result uploadPartCopy(array $args = []) * @method \Aws\Result uploadPartCopy(array $args = [])

View file

@ -259,7 +259,7 @@ class StreamWrapper
$this->initProtocol($path); $this->initProtocol($path);
// Some paths come through as S3:// for some reason. // Some paths come through as S3:// for some reason.
$split = explode('://', $path); $split = explode('://', $path, 2);
$path = strtolower($split[0]) . '://' . $split[1]; $path = strtolower($split[0]) . '://' . $split[1];
// Check if this path is in the url_stat cache // Check if this path is in the url_stat cache
@ -703,7 +703,7 @@ class StreamWrapper
private function getBucketKey($path) private function getBucketKey($path)
{ {
// Remove the protocol // Remove the protocol
$parts = explode('://', $path); $parts = explode('://', $path, 2);
// Get the bucket, key // Get the bucket, key
$parts = explode('/', $parts[1], 2); $parts = explode('/', $parts[1], 2);

View file

@ -8,6 +8,8 @@ namespace Aws;
* @method \Aws\MultiRegionClient createMultiRegionACMPCA(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionACMPCA(array $args = [])
* @method \Aws\AIOps\AIOpsClient createAIOps(array $args = []) * @method \Aws\AIOps\AIOpsClient createAIOps(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionAIOps(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionAIOps(array $args = [])
* @method \Aws\ARCRegionSwitch\ARCRegionSwitchClient createARCRegionSwitch(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionARCRegionSwitch(array $args = [])
* @method \Aws\ARCZonalShift\ARCZonalShiftClient createARCZonalShift(array $args = []) * @method \Aws\ARCZonalShift\ARCZonalShiftClient createARCZonalShift(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionARCZonalShift(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionARCZonalShift(array $args = [])
* @method \Aws\AccessAnalyzer\AccessAnalyzerClient createAccessAnalyzer(array $args = []) * @method \Aws\AccessAnalyzer\AccessAnalyzerClient createAccessAnalyzer(array $args = [])
@ -74,10 +76,14 @@ namespace Aws;
* @method \Aws\MultiRegionClient createMultiRegionAutoScalingPlans(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionAutoScalingPlans(array $args = [])
* @method \Aws\B2bi\B2biClient createB2bi(array $args = []) * @method \Aws\B2bi\B2biClient createB2bi(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionB2bi(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionB2bi(array $args = [])
* @method \Aws\BCMDashboards\BCMDashboardsClient createBCMDashboards(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionBCMDashboards(array $args = [])
* @method \Aws\BCMDataExports\BCMDataExportsClient createBCMDataExports(array $args = []) * @method \Aws\BCMDataExports\BCMDataExportsClient createBCMDataExports(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionBCMDataExports(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionBCMDataExports(array $args = [])
* @method \Aws\BCMPricingCalculator\BCMPricingCalculatorClient createBCMPricingCalculator(array $args = []) * @method \Aws\BCMPricingCalculator\BCMPricingCalculatorClient createBCMPricingCalculator(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionBCMPricingCalculator(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionBCMPricingCalculator(array $args = [])
* @method \Aws\BCMRecommendedActions\BCMRecommendedActionsClient createBCMRecommendedActions(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionBCMRecommendedActions(array $args = [])
* @method \Aws\Backup\BackupClient createBackup(array $args = []) * @method \Aws\Backup\BackupClient createBackup(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionBackup(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionBackup(array $args = [])
* @method \Aws\BackupGateway\BackupGatewayClient createBackupGateway(array $args = []) * @method \Aws\BackupGateway\BackupGatewayClient createBackupGateway(array $args = [])
@ -90,6 +96,10 @@ namespace Aws;
* @method \Aws\MultiRegionClient createMultiRegionBedrock(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionBedrock(array $args = [])
* @method \Aws\BedrockAgent\BedrockAgentClient createBedrockAgent(array $args = []) * @method \Aws\BedrockAgent\BedrockAgentClient createBedrockAgent(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionBedrockAgent(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionBedrockAgent(array $args = [])
* @method \Aws\BedrockAgentCore\BedrockAgentCoreClient createBedrockAgentCore(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionBedrockAgentCore(array $args = [])
* @method \Aws\BedrockAgentCoreControl\BedrockAgentCoreControlClient createBedrockAgentCoreControl(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionBedrockAgentCoreControl(array $args = [])
* @method \Aws\BedrockAgentRuntime\BedrockAgentRuntimeClient createBedrockAgentRuntime(array $args = []) * @method \Aws\BedrockAgentRuntime\BedrockAgentRuntimeClient createBedrockAgentRuntime(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionBedrockAgentRuntime(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionBedrockAgentRuntime(array $args = [])
* @method \Aws\BedrockDataAutomation\BedrockDataAutomationClient createBedrockDataAutomation(array $args = []) * @method \Aws\BedrockDataAutomation\BedrockDataAutomationClient createBedrockDataAutomation(array $args = [])
@ -408,6 +418,8 @@ namespace Aws;
* @method \Aws\MultiRegionClient createMultiRegionKendraRanking(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionKendraRanking(array $args = [])
* @method \Aws\Keyspaces\KeyspacesClient createKeyspaces(array $args = []) * @method \Aws\Keyspaces\KeyspacesClient createKeyspaces(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionKeyspaces(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionKeyspaces(array $args = [])
* @method \Aws\KeyspacesStreams\KeyspacesStreamsClient createKeyspacesStreams(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionKeyspacesStreams(array $args = [])
* @method \Aws\Kinesis\KinesisClient createKinesis(array $args = []) * @method \Aws\Kinesis\KinesisClient createKinesis(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionKinesis(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionKinesis(array $args = [])
* @method \Aws\KinesisAnalytics\KinesisAnalyticsClient createKinesisAnalytics(array $args = []) * @method \Aws\KinesisAnalytics\KinesisAnalyticsClient createKinesisAnalytics(array $args = [])
@ -548,16 +560,14 @@ namespace Aws;
* @method \Aws\MultiRegionClient createMultiRegionOSIS(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionOSIS(array $args = [])
* @method \Aws\ObservabilityAdmin\ObservabilityAdminClient createObservabilityAdmin(array $args = []) * @method \Aws\ObservabilityAdmin\ObservabilityAdminClient createObservabilityAdmin(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionObservabilityAdmin(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionObservabilityAdmin(array $args = [])
* @method \Aws\Odb\OdbClient createOdb(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionOdb(array $args = [])
* @method \Aws\Omics\OmicsClient createOmics(array $args = []) * @method \Aws\Omics\OmicsClient createOmics(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionOmics(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionOmics(array $args = [])
* @method \Aws\OpenSearchServerless\OpenSearchServerlessClient createOpenSearchServerless(array $args = []) * @method \Aws\OpenSearchServerless\OpenSearchServerlessClient createOpenSearchServerless(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionOpenSearchServerless(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionOpenSearchServerless(array $args = [])
* @method \Aws\OpenSearchService\OpenSearchServiceClient createOpenSearchService(array $args = []) * @method \Aws\OpenSearchService\OpenSearchServiceClient createOpenSearchService(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionOpenSearchService(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionOpenSearchService(array $args = [])
* @method \Aws\OpsWorks\OpsWorksClient createOpsWorks(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionOpsWorks(array $args = [])
* @method \Aws\OpsWorksCM\OpsWorksCMClient createOpsWorksCM(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionOpsWorksCM(array $args = [])
* @method \Aws\Organizations\OrganizationsClient createOrganizations(array $args = []) * @method \Aws\Organizations\OrganizationsClient createOrganizations(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionOrganizations(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionOrganizations(array $args = [])
* @method \Aws\Outposts\OutpostsClient createOutposts(array $args = []) * @method \Aws\Outposts\OutpostsClient createOutposts(array $args = [])
@ -666,6 +676,8 @@ namespace Aws;
* @method \Aws\MultiRegionClient createMultiRegionS3Outposts(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionS3Outposts(array $args = [])
* @method \Aws\S3Tables\S3TablesClient createS3Tables(array $args = []) * @method \Aws\S3Tables\S3TablesClient createS3Tables(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionS3Tables(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionS3Tables(array $args = [])
* @method \Aws\S3Vectors\S3VectorsClient createS3Vectors(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionS3Vectors(array $args = [])
* @method \Aws\SSMContacts\SSMContactsClient createSSMContacts(array $args = []) * @method \Aws\SSMContacts\SSMContactsClient createSSMContacts(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionSSMContacts(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionSSMContacts(array $args = [])
* @method \Aws\SSMGuiConnect\SSMGuiConnectClient createSSMGuiConnect(array $args = []) * @method \Aws\SSMGuiConnect\SSMGuiConnectClient createSSMGuiConnect(array $args = [])
@ -800,6 +812,8 @@ namespace Aws;
* @method \Aws\MultiRegionClient createMultiRegionWorkSpacesThinClient(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionWorkSpacesThinClient(array $args = [])
* @method \Aws\WorkSpacesWeb\WorkSpacesWebClient createWorkSpacesWeb(array $args = []) * @method \Aws\WorkSpacesWeb\WorkSpacesWebClient createWorkSpacesWeb(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionWorkSpacesWeb(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionWorkSpacesWeb(array $args = [])
* @method \Aws\WorkspacesInstances\WorkspacesInstancesClient createWorkspacesInstances(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionWorkspacesInstances(array $args = [])
* @method \Aws\XRay\XRayClient createXRay(array $args = []) * @method \Aws\XRay\XRayClient createXRay(array $args = [])
* @method \Aws\MultiRegionClient createMultiRegionXRay(array $args = []) * @method \Aws\MultiRegionClient createMultiRegionXRay(array $args = [])
* @method \Aws\drs\drsClient createdrs(array $args = []) * @method \Aws\drs\drsClient createdrs(array $args = [])
@ -819,7 +833,7 @@ namespace Aws;
*/ */
class Sdk class Sdk
{ {
const VERSION = '3.346.2'; const VERSION = '3.356.8';
/** @var array Arguments for creating clients */ /** @var array Arguments for creating clients */
private $args; private $args;

View file

@ -73,7 +73,7 @@ class StreamRequestPayloadMiddleware
. ' calculated.'); . ' calculated.');
} }
$request = $request->withHeader( $request = $request->withHeader(
'content-length', 'Content-Length',
$size $size
); );
} }

View file

@ -45,7 +45,7 @@ use GuzzleHttp\Promise\PromiseInterface;
class ConfigurationProvider extends AbstractConfigurationProvider class ConfigurationProvider extends AbstractConfigurationProvider
implements ConfigurationProviderInterface implements ConfigurationProviderInterface
{ {
const DEFAULT_ENDPOINTS_TYPE = 'legacy'; const DEFAULT_ENDPOINTS_TYPE = 'regional';
const ENV_ENDPOINTS_TYPE = 'AWS_STS_REGIONAL_ENDPOINTS'; const ENV_ENDPOINTS_TYPE = 'AWS_STS_REGIONAL_ENDPOINTS';
const ENV_PROFILE = 'AWS_PROFILE'; const ENV_PROFILE = 'AWS_PROFILE';
const INI_ENDPOINTS_TYPE = 'sts_regional_endpoints'; const INI_ENDPOINTS_TYPE = 'sts_regional_endpoints';

View file

@ -0,0 +1,102 @@
<?php
namespace Aws\Token;
use Aws\Configuration\ConfigurationResolver;
use Aws\Exception\TokenException;
use GuzzleHttp\Promise;
/**
* Token provider for Bedrock that sources bearer tokens from environment variables.
*/
class BedrockTokenProvider extends TokenProvider
{
/** @var string used to resolve the AWS_BEARER_TOKEN_BEDROCK env var */
public const TOKEN_ENV_KEY = 'bearer_token_bedrock';
public const BEARER_AUTH = 'smithy.api#httpBearerAuth';
/**
* Create a default Bedrock token provider that checks for a bearer token
* in the AWS_BEARER_TOKEN_BEDROCK environment variable.
*
* This provider is automatically wrapped in a memoize function that caches
* previously provided tokens.
*
* @param array $config Optional array of token provider options.
*
* @return callable
*/
public static function defaultProvider(array $config = []): callable
{
$defaultChain = ['env' => self::env(self::TOKEN_ENV_KEY)];
return self::memoize(
call_user_func_array(
[TokenProvider::class, 'chain'],
array_values($defaultChain)
)
);
}
/**
* Token provider that creates a token from an environment variable.
*
* @param string $configKey The configuration key that will be transformed
* to an environment variable name by ConfigurationResolver
*
* @return callable
*/
public static function env(string $configKey): callable
{
return static function () use ($configKey) {
$tokenValue = ConfigurationResolver::env($configKey);
if (empty($tokenValue)) {
return Promise\Create::rejectionFor(
new TokenException(
"No token found in environment variable " .
ConfigurationResolver::$envPrefix . strtoupper($configKey)
)
);
}
return Promise\Create::promiseFor(new Token($tokenValue));
};
}
/**
* Create a token provider from a raw token value string.
* Bedrock bearer tokens sourced from env do not have an expiration
*
* @param string $tokenValue The bearer token value
*
* @return callable
*/
public static function fromTokenValue(string $tokenValue): callable
{
$token = new Token($tokenValue);
return self::fromToken($token);
}
/**
* Create a Bedrock token provider if the service is 'bedrock' and a token is available.
* Sets auth scheme preference to `bearer` auth.
*
* @param array $args Configuration arguments containing 'config' array
*
* @return callable|null Returns a token provider if conditions are met, null otherwise
*/
public static function createIfAvailable(array &$args): ?callable
{
$tokenValue = ConfigurationResolver::env(self::TOKEN_ENV_KEY);
if ($tokenValue) {
$authSchemePreference = $args['config']['auth_scheme_preference'] ?? [];
array_unshift($authSchemePreference, self::BEARER_AUTH);
$args['config']['auth_scheme_preference'] = $authSchemePreference;
return self::fromTokenValue($tokenValue);
}
return null;
}
}

View file

@ -2,7 +2,6 @@
namespace Aws\Token; namespace Aws\Token;
use Aws\Identity\BearerTokenIdentity; use Aws\Identity\BearerTokenIdentity;
use Aws\Token\TokenInterface;
/** /**
* Basic implementation of the AWS Token interface that allows callers to * Basic implementation of the AWS Token interface that allows callers to

View file

@ -2,7 +2,6 @@
namespace Aws\Token; namespace Aws\Token;
use Aws; use Aws;
use Aws\Api\DateTimeResult;
use Aws\CacheInterface; use Aws\CacheInterface;
use Aws\Exception\TokenException; use Aws\Exception\TokenException;
use GuzzleHttp\Promise; use GuzzleHttp\Promise;
@ -28,9 +27,10 @@ use GuzzleHttp\Promise;
*/ */
class TokenProvider class TokenProvider
{ {
use ParsesIniTrait;
const ENV_PROFILE = 'AWS_PROFILE'; const ENV_PROFILE = 'AWS_PROFILE';
use ParsesIniTrait;
/** /**
* Create a default token provider tha checks for cached a SSO token from * Create a default token provider tha checks for cached a SSO token from
* the CLI * the CLI
@ -44,15 +44,13 @@ class TokenProvider
*/ */
public static function defaultProvider(array $config = []) public static function defaultProvider(array $config = [])
{ {
$cacheable = [ $cacheable = [
'sso', 'sso',
]; ];
$defaultChain = []; $defaultChain = [];
if ( if (!isset($config['use_aws_shared_config_files'])
!isset($config['use_aws_shared_config_files'])
|| $config['use_aws_shared_config_files'] !== false || $config['use_aws_shared_config_files'] !== false
) { ) {
$profileName = getenv(self::ENV_PROFILE) ?: 'default'; $profileName = getenv(self::ENV_PROFILE) ?: 'default';
@ -79,7 +77,7 @@ class TokenProvider
return self::memoize( return self::memoize(
call_user_func_array( call_user_func_array(
[TokenProvider::class, 'chain'], [__CLASS__, 'chain'],
array_values($defaultChain) array_values($defaultChain)
) )
); );
@ -96,7 +94,7 @@ class TokenProvider
{ {
$promise = Promise\Create::promiseFor($token); $promise = Promise\Create::promiseFor($token);
return function () use ($promise) { return static function () use ($promise) {
return $promise; return $promise;
}; };
} }
@ -113,12 +111,12 @@ class TokenProvider
$links = func_get_args(); $links = func_get_args();
//Common use case for when aws_shared_config_files is false //Common use case for when aws_shared_config_files is false
if (empty($links)) { if (empty($links)) {
return function () { return static function () {
return Promise\Create::promiseFor(false); return Promise\Create::promiseFor(false);
}; };
} }
return function () use ($links) { return static function () use ($links) {
/** @var callable $parent */ /** @var callable $parent */
$parent = array_shift($links); $parent = array_shift($links);
$promise = $parent(); $promise = $parent();
@ -138,7 +136,7 @@ class TokenProvider
*/ */
public static function memoize(callable $provider) public static function memoize(callable $provider)
{ {
return function () use ($provider) { return static function () use ($provider) {
static $result; static $result;
static $isConstant; static $isConstant;
@ -190,10 +188,10 @@ class TokenProvider
callable $provider, callable $provider,
CacheInterface $cache, CacheInterface $cache,
$cacheKey = null $cacheKey = null
) { ){
$cacheKey = $cacheKey ?: 'aws_cached_token'; $cacheKey = $cacheKey ?: 'aws_cached_token';
return function () use ($provider, $cache, $cacheKey) { return static function () use ($provider, $cache, $cacheKey) {
$found = $cache->get($cacheKey); $found = $cache->get($cacheKey);
if (is_array($found) && isset($found['token'])) { if (is_array($found) && isset($found['token'])) {
$foundToken = $found['token']; $foundToken = $found['token'];
@ -214,7 +212,7 @@ class TokenProvider
) { ) {
$cache->set( $cache->set(
$cacheKey, $cacheKey,
$token, ['token' => $token],
null === $token->getExpiration() ? null === $token->getExpiration() ?
0 : $token->getExpiration() - time() 0 : $token->getExpiration() - time()
); );
@ -227,7 +225,8 @@ class TokenProvider
/** /**
* Gets profiles from the ~/.aws/config ini file * Gets profiles from the ~/.aws/config ini file
*/ */
private static function loadDefaultProfiles() { private static function loadDefaultProfiles()
{
$profiles = []; $profiles = [];
$configFile = self::getHomeDir() . '/.aws/config'; $configFile = self::getHomeDir() . '/.aws/config';
@ -260,11 +259,13 @@ class TokenProvider
* @return SsoTokenProvider * @return SsoTokenProvider
* @see Aws\Token\SsoTokenProvider for $config details. * @see Aws\Token\SsoTokenProvider for $config details.
*/ */
public static function sso($profileName, $filename, $config = []) public static function sso(
{ $profileName,
$ssoClient = isset($config['ssoClient']) ? $config['ssoClient'] : null; $filename,
$config = []
){
$ssoClient = $config['ssoClient'] ?? null;
return new SsoTokenProvider($profileName, $filename, $ssoClient); return new SsoTokenProvider($profileName, $filename, $ssoClient);
} }
} }

File diff suppressed because one or more lines are too long

View file

@ -1,3 +1,3 @@
<?php <?php
// This file was auto-generated from sdk-root/src/data/grandfathered-services.json // This file was auto-generated from sdk-root/src/data/grandfathered-services.json
return [ 'grandfathered-services' => [ 'AccessAnalyzer', 'Account', 'ACMPCA', 'ACM', 'PrometheusService', 'Amplify', 'AmplifyBackend', 'AmplifyUIBuilder', 'APIGateway', 'ApiGatewayManagementApi', 'ApiGatewayV2', 'AppConfig', 'AppConfigData', 'Appflow', 'AppIntegrationsService', 'ApplicationAutoScaling', 'ApplicationInsights', 'ApplicationCostProfiler', 'AppMesh', 'AppRunner', 'AppStream', 'AppSync', 'Athena', 'AuditManager', 'AutoScalingPlans', 'AutoScaling', 'BackupGateway', 'Backup', 'Batch', 'BillingConductor', 'Braket', 'Budgets', 'CostExplorer', 'ChimeSDKIdentity', 'ChimeSDKMediaPipelines', 'ChimeSDKMeetings', 'ChimeSDKMessaging', 'Chime', 'Cloud9', 'CloudControlApi', 'CloudDirectory', 'CloudFormation', 'CloudFront', 'CloudHSM', 'CloudHSMV2', 'CloudSearch', 'CloudSearchDomain', 'CloudTrail', 'CodeArtifact', 'CodeBuild', 'CodeCommit', 'CodeDeploy', 'CodeGuruReviewer', 'CodeGuruProfiler', 'CodePipeline', 'CodeStarconnections', 'CodeStarNotifications', 'CodeStar', 'CognitoIdentity', 'CognitoIdentityProvider', 'CognitoSync', 'Comprehend', 'ComprehendMedical', 'ComputeOptimizer', 'ConfigService', 'ConnectContactLens', 'Connect', 'ConnectCampaignService', 'ConnectParticipant', 'CostandUsageReportService', 'CustomerProfiles', 'IoTDataPlane', 'GlueDataBrew', 'DataExchange', 'DataPipeline', 'DataSync', 'DAX', 'Detective', 'DeviceFarm', 'DevOpsGuru', 'DirectConnect', 'ApplicationDiscoveryService', 'DLM', 'DatabaseMigrationService', 'DocDB', 'drs', 'DirectoryService', 'DynamoDB', 'EBS', 'EC2InstanceConnect', 'EC2', 'ECRPublic', 'ECR', 'ECS', 'EKS', 'ElastiCache', 'ElasticBeanstalk', 'EFS', 'ElasticLoadBalancing', 'ElasticLoadBalancingv2', 'EMR', 'ElasticTranscoder', 'SES', 'EMRContainers', 'EMRServerless', 'MarketplaceEntitlementService', 'ElasticsearchService', 'EventBridge', 'CloudWatchEvents', 'CloudWatchEvidently', 'FinSpaceData', 'finspace', 'Firehose', 'FIS', 'FMS', 'ForecastService', 'ForecastQueryService', 'FraudDetector', 'FSx', 'GameLift', 'Glacier', 'GlobalAccelerator', 'Glue', 'ManagedGrafana', 'Greengrass', 'GreengrassV2', 'GroundStation', 'GuardDuty', 'Health', 'HealthLake', 'IAM', 'IdentityStore', 'imagebuilder', 'ImportExport', 'Inspector', 'Inspector2', 'IoTJobsDataPlane', 'IoT', 'IoTAnalytics', 'IoTDeviceAdvisor', 'IoTEventsData', 'IoTEvents', 'IoTFleetHub', 'IoTSecureTunneling', 'IoTSiteWise', 'IoTThingsGraph', 'IoTTwinMaker', 'IoTWireless', 'IVS', 'ivschat', 'Kafka', 'KafkaConnect', 'kendra', 'Keyspaces', 'KinesisVideoArchivedMedia', 'KinesisVideoMedia', 'KinesisVideoSignalingChannels', 'Kinesis', 'KinesisAnalytics', 'KinesisAnalyticsV2', 'KinesisVideo', 'KMS', 'LakeFormation', 'Lambda', 'LexModelBuildingService', 'LicenseManager', 'Lightsail', 'LocationService', 'CloudWatchLogs', 'LookoutEquipment', 'LookoutMetrics', 'LookoutforVision', 'MainframeModernization', 'MachineLearning', 'Macie2', 'ManagedBlockchain', 'MarketplaceCatalog', 'MarketplaceCommerceAnalytics', 'MediaConnect', 'MediaConvert', 'MediaLive', 'MediaPackageVod', 'MediaPackage', 'MediaStoreData', 'MediaStore', 'MediaTailor', 'MemoryDB', 'MarketplaceMetering', 'MigrationHub', 'mgn', 'MigrationHubRefactorSpaces', 'MigrationHubConfig', 'MigrationHubStrategyRecommendations', 'LexModelsV2', 'CloudWatch', 'MQ', 'MTurk', 'MWAA', 'Neptune', 'NetworkFirewall', 'NetworkManager', 'OpenSearchService', 'OpsWorks', 'OpsWorksCM', 'Organizations', 'Outposts', 'Panorama', 'PersonalizeEvents', 'PersonalizeRuntime', 'Personalize', 'PI', 'PinpointEmail', 'PinpointSMSVoiceV2', 'Pinpoint', 'Polly', 'Pricing', 'Proton', 'QLDBSession', 'QLDB', 'QuickSight', 'RAM', 'RecycleBin', 'RDSDataService', 'RDS', 'RedshiftDataAPIService', 'RedshiftServerless', 'Redshift', 'Rekognition', 'ResilienceHub', 'ResourceGroups', 'ResourceGroupsTaggingAPI', 'RoboMaker', 'Route53RecoveryCluster', 'Route53RecoveryControlConfig', 'Route53RecoveryReadiness', 'Route53', 'Route53Domains', 'Route53Resolver', 'CloudWatchRUM', 'LexRuntimeV2', 'LexRuntimeService', 'SageMakerRuntime', 'S3', 'S3Control', 'S3Outposts', 'AugmentedAIRuntime', 'SagemakerEdgeManager', 'SageMakerFeatureStoreRuntime', 'SageMaker', 'SavingsPlans', 'Schemas', 'SecretsManager', 'SecurityHub', 'ServerlessApplicationRepository', 'ServiceQuotas', 'AppRegistry', 'ServiceCatalog', 'ServiceDiscovery', 'SESV2', 'Shield', 'signer', 'PinpointSMSVoice', 'SMS', 'SnowDeviceManagement', 'Snowball', 'SNS', 'SQS', 'SSMContacts', 'SSMIncidents', 'SSM', 'SSOAdmin', 'SSOOIDC', 'SSO', 'SFN', 'StorageGateway', 'DynamoDBStreams', 'STS', 'Support', 'SWF', 'Synthetics', 'Textract', 'TimestreamQuery', 'TimestreamWrite', 'TranscribeService', 'Transfer', 'Translate', 'VoiceID', 'WAFRegional', 'WAF', 'WAFV2', 'WellArchitected', 'ConnectWisdomService', 'WorkDocs', 'WorkMail', 'WorkMailMessageFlow', 'WorkSpacesWeb', 'WorkSpaces', 'XRay', ],]; return [ 'grandfathered-services' => [ 'AccessAnalyzer', 'Account', 'ACMPCA', 'ACM', 'PrometheusService', 'Amplify', 'AmplifyBackend', 'AmplifyUIBuilder', 'APIGateway', 'ApiGatewayManagementApi', 'ApiGatewayV2', 'AppConfig', 'AppConfigData', 'Appflow', 'AppIntegrationsService', 'ApplicationAutoScaling', 'ApplicationInsights', 'ApplicationCostProfiler', 'AppMesh', 'AppRunner', 'AppStream', 'AppSync', 'Athena', 'AuditManager', 'AutoScalingPlans', 'AutoScaling', 'BackupGateway', 'Backup', 'Batch', 'BillingConductor', 'Braket', 'Budgets', 'CostExplorer', 'ChimeSDKIdentity', 'ChimeSDKMediaPipelines', 'ChimeSDKMeetings', 'ChimeSDKMessaging', 'Chime', 'Cloud9', 'CloudControlApi', 'CloudDirectory', 'CloudFormation', 'CloudFront', 'CloudHSM', 'CloudHSMV2', 'CloudSearch', 'CloudSearchDomain', 'CloudTrail', 'CodeArtifact', 'CodeBuild', 'CodeCommit', 'CodeDeploy', 'CodeGuruReviewer', 'CodeGuruProfiler', 'CodePipeline', 'CodeStarconnections', 'CodeStarNotifications', 'CodeStar', 'CognitoIdentity', 'CognitoIdentityProvider', 'CognitoSync', 'Comprehend', 'ComprehendMedical', 'ComputeOptimizer', 'ConfigService', 'ConnectContactLens', 'Connect', 'ConnectCampaignService', 'ConnectParticipant', 'CostandUsageReportService', 'CustomerProfiles', 'IoTDataPlane', 'GlueDataBrew', 'DataExchange', 'DataPipeline', 'DataSync', 'DAX', 'Detective', 'DeviceFarm', 'DevOpsGuru', 'DirectConnect', 'ApplicationDiscoveryService', 'DLM', 'DatabaseMigrationService', 'DocDB', 'drs', 'DirectoryService', 'DynamoDB', 'EBS', 'EC2InstanceConnect', 'EC2', 'ECRPublic', 'ECR', 'ECS', 'EKS', 'ElastiCache', 'ElasticBeanstalk', 'EFS', 'ElasticLoadBalancing', 'ElasticLoadBalancingv2', 'EMR', 'ElasticTranscoder', 'SES', 'EMRContainers', 'EMRServerless', 'MarketplaceEntitlementService', 'ElasticsearchService', 'EventBridge', 'CloudWatchEvents', 'CloudWatchEvidently', 'FinSpaceData', 'finspace', 'Firehose', 'FIS', 'FMS', 'ForecastService', 'ForecastQueryService', 'FraudDetector', 'FSx', 'GameLift', 'Glacier', 'GlobalAccelerator', 'Glue', 'ManagedGrafana', 'Greengrass', 'GreengrassV2', 'GroundStation', 'GuardDuty', 'Health', 'HealthLake', 'IAM', 'IdentityStore', 'imagebuilder', 'ImportExport', 'Inspector', 'Inspector2', 'IoTJobsDataPlane', 'IoT', 'IoTAnalytics', 'IoTDeviceAdvisor', 'IoTEventsData', 'IoTEvents', 'IoTFleetHub', 'IoTSecureTunneling', 'IoTSiteWise', 'IoTThingsGraph', 'IoTTwinMaker', 'IoTWireless', 'IVS', 'ivschat', 'Kafka', 'KafkaConnect', 'kendra', 'Keyspaces', 'KinesisVideoArchivedMedia', 'KinesisVideoMedia', 'KinesisVideoSignalingChannels', 'Kinesis', 'KinesisAnalytics', 'KinesisAnalyticsV2', 'KinesisVideo', 'KMS', 'LakeFormation', 'Lambda', 'LexModelBuildingService', 'LicenseManager', 'Lightsail', 'LocationService', 'CloudWatchLogs', 'LookoutEquipment', 'LookoutMetrics', 'LookoutforVision', 'MainframeModernization', 'MachineLearning', 'Macie2', 'ManagedBlockchain', 'MarketplaceCatalog', 'MarketplaceCommerceAnalytics', 'MediaConnect', 'MediaConvert', 'MediaLive', 'MediaPackageVod', 'MediaPackage', 'MediaStoreData', 'MediaStore', 'MediaTailor', 'MemoryDB', 'MarketplaceMetering', 'MigrationHub', 'mgn', 'MigrationHubRefactorSpaces', 'MigrationHubConfig', 'MigrationHubStrategyRecommendations', 'LexModelsV2', 'CloudWatch', 'MQ', 'MTurk', 'MWAA', 'Neptune', 'NetworkFirewall', 'NetworkManager', 'OpenSearchService', 'Organizations', 'Outposts', 'Panorama', 'PersonalizeEvents', 'PersonalizeRuntime', 'Personalize', 'PI', 'PinpointEmail', 'PinpointSMSVoiceV2', 'Pinpoint', 'Polly', 'Pricing', 'Proton', 'QLDBSession', 'QLDB', 'QuickSight', 'RAM', 'RecycleBin', 'RDSDataService', 'RDS', 'RedshiftDataAPIService', 'RedshiftServerless', 'Redshift', 'Rekognition', 'ResilienceHub', 'ResourceGroups', 'ResourceGroupsTaggingAPI', 'RoboMaker', 'Route53RecoveryCluster', 'Route53RecoveryControlConfig', 'Route53RecoveryReadiness', 'Route53', 'Route53Domains', 'Route53Resolver', 'CloudWatchRUM', 'LexRuntimeV2', 'LexRuntimeService', 'SageMakerRuntime', 'S3', 'S3Control', 'S3Outposts', 'AugmentedAIRuntime', 'SagemakerEdgeManager', 'SageMakerFeatureStoreRuntime', 'SageMaker', 'SavingsPlans', 'Schemas', 'SecretsManager', 'SecurityHub', 'ServerlessApplicationRepository', 'ServiceQuotas', 'AppRegistry', 'ServiceCatalog', 'ServiceDiscovery', 'SESV2', 'Shield', 'signer', 'PinpointSMSVoice', 'SMS', 'SnowDeviceManagement', 'Snowball', 'SNS', 'SQS', 'SSMContacts', 'SSMIncidents', 'SSM', 'SSOAdmin', 'SSOOIDC', 'SSO', 'SFN', 'StorageGateway', 'DynamoDBStreams', 'STS', 'Support', 'SWF', 'Synthetics', 'Textract', 'TimestreamQuery', 'TimestreamWrite', 'TranscribeService', 'Transfer', 'Translate', 'VoiceID', 'WAFRegional', 'WAF', 'WAFV2', 'WellArchitected', 'ConnectWisdomService', 'WorkDocs', 'WorkMail', 'WorkMailMessageFlow', 'WorkSpacesWeb', 'WorkSpaces', 'XRay', ],];

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,3 +1,3 @@
<?php <?php
// This file was auto-generated from sdk-root/src/data/sso/2019-06-10/endpoint-rule-set-1.json // This file was auto-generated from sdk-root/src/data/sso/2019-06-10/endpoint-rule-set-1.json
return [ 'version' => '1.0', 'parameters' => [ 'Region' => [ 'builtIn' => 'AWS::Region', 'required' => false, 'documentation' => 'The AWS region used to dispatch the request.', 'type' => 'String', ], 'UseDualStack' => [ 'builtIn' => 'AWS::UseDualStack', 'required' => true, 'default' => false, 'documentation' => 'When true, use the dual-stack endpoint. If the configured endpoint does not support dual-stack, dispatching the request MAY return an error.', 'type' => 'Boolean', ], 'UseFIPS' => [ 'builtIn' => 'AWS::UseFIPS', 'required' => true, 'default' => false, 'documentation' => 'When true, send this request to the FIPS-compliant regional endpoint. If the configured endpoint does not have a FIPS compliant endpoint, dispatching the request will return an error.', 'type' => 'Boolean', ], 'Endpoint' => [ 'builtIn' => 'SDK::Endpoint', 'required' => false, 'documentation' => 'Override the endpoint used to send this request', 'type' => 'String', ], ], 'rules' => [ [ 'conditions' => [ [ 'fn' => 'isSet', 'argv' => [ [ 'ref' => 'Endpoint', ], ], ], ], 'type' => 'tree', 'rules' => [ [ 'conditions' => [ [ 'fn' => 'booleanEquals', 'argv' => [ [ 'ref' => 'UseFIPS', ], true, ], ], ], 'error' => 'Invalid Configuration: FIPS and custom endpoint are not supported', 'type' => 'error', ], [ 'conditions' => [ [ 'fn' => 'booleanEquals', 'argv' => [ [ 'ref' => 'UseDualStack', ], true, ], ], ], 'error' => 'Invalid Configuration: Dualstack and custom endpoint are not supported', 'type' => 'error', ], [ 'conditions' => [], 'endpoint' => [ 'url' => [ 'ref' => 'Endpoint', ], 'properties' => [], 'headers' => [], ], 'type' => 'endpoint', ], ], ], [ 'conditions' => [ [ 'fn' => 'isSet', 'argv' => [ [ 'ref' => 'Region', ], ], ], ], 'type' => 'tree', 'rules' => [ [ 'conditions' => [ [ 'fn' => 'aws.partition', 'argv' => [ [ 'ref' => 'Region', ], ], 'assign' => 'PartitionResult', ], ], 'type' => 'tree', 'rules' => [ [ 'conditions' => [ [ 'fn' => 'booleanEquals', 'argv' => [ [ 'ref' => 'UseFIPS', ], true, ], ], [ 'fn' => 'booleanEquals', 'argv' => [ [ 'ref' => 'UseDualStack', ], true, ], ], ], 'type' => 'tree', 'rules' => [ [ 'conditions' => [ [ 'fn' => 'booleanEquals', 'argv' => [ true, [ 'fn' => 'getAttr', 'argv' => [ [ 'ref' => 'PartitionResult', ], 'supportsFIPS', ], ], ], ], [ 'fn' => 'booleanEquals', 'argv' => [ true, [ 'fn' => 'getAttr', 'argv' => [ [ 'ref' => 'PartitionResult', ], 'supportsDualStack', ], ], ], ], ], 'type' => 'tree', 'rules' => [ [ 'conditions' => [], 'endpoint' => [ 'url' => 'https://portal.sso-fips.{Region}.{PartitionResult#dualStackDnsSuffix}', 'properties' => [], 'headers' => [], ], 'type' => 'endpoint', ], ], ], [ 'conditions' => [], 'error' => 'FIPS and DualStack are enabled, but this partition does not support one or both', 'type' => 'error', ], ], ], [ 'conditions' => [ [ 'fn' => 'booleanEquals', 'argv' => [ [ 'ref' => 'UseFIPS', ], true, ], ], ], 'type' => 'tree', 'rules' => [ [ 'conditions' => [ [ 'fn' => 'booleanEquals', 'argv' => [ true, [ 'fn' => 'getAttr', 'argv' => [ [ 'ref' => 'PartitionResult', ], 'supportsFIPS', ], ], ], ], ], 'type' => 'tree', 'rules' => [ [ 'conditions' => [ [ 'fn' => 'stringEquals', 'argv' => [ 'aws-us-gov', [ 'fn' => 'getAttr', 'argv' => [ [ 'ref' => 'PartitionResult', ], 'name', ], ], ], ], ], 'endpoint' => [ 'url' => 'https://portal.sso.{Region}.amazonaws.com', 'properties' => [], 'headers' => [], ], 'type' => 'endpoint', ], [ 'conditions' => [], 'endpoint' => [ 'url' => 'https://portal.sso-fips.{Region}.{PartitionResult#dnsSuffix}', 'properties' => [], 'headers' => [], ], 'type' => 'endpoint', ], ], ], [ 'conditions' => [], 'error' => 'FIPS is enabled but this partition does not support FIPS', 'type' => 'error', ], ], ], [ 'conditions' => [ [ 'fn' => 'booleanEquals', 'argv' => [ [ 'ref' => 'UseDualStack', ], true, ], ], ], 'type' => 'tree', 'rules' => [ [ 'conditions' => [ [ 'fn' => 'booleanEquals', 'argv' => [ true, [ 'fn' => 'getAttr', 'argv' => [ [ 'ref' => 'PartitionResult', ], 'supportsDualStack', ], ], ], ], ], 'type' => 'tree', 'rules' => [ [ 'conditions' => [], 'endpoint' => [ 'url' => 'https://portal.sso.{Region}.{PartitionResult#dualStackDnsSuffix}', 'properties' => [], 'headers' => [], ], 'type' => 'endpoint', ], ], ], [ 'conditions' => [], 'error' => 'DualStack is enabled but this partition does not support DualStack', 'type' => 'error', ], ], ], [ 'conditions' => [], 'endpoint' => [ 'url' => 'https://portal.sso.{Region}.{PartitionResult#dnsSuffix}', 'properties' => [], 'headers' => [], ], 'type' => 'endpoint', ], ], ], ], ], [ 'conditions' => [], 'error' => 'Invalid Configuration: Missing Region', 'type' => 'error', ], ],]; return [ 'version' => '1.0', 'parameters' => [ 'Region' => [ 'builtIn' => 'AWS::Region', 'required' => false, 'documentation' => 'The AWS region used to dispatch the request.', 'type' => 'String', ], 'UseDualStack' => [ 'builtIn' => 'AWS::UseDualStack', 'required' => true, 'default' => false, 'documentation' => 'When true, use the dual-stack endpoint. If the configured endpoint does not support dual-stack, dispatching the request MAY return an error.', 'type' => 'Boolean', ], 'UseFIPS' => [ 'builtIn' => 'AWS::UseFIPS', 'required' => true, 'default' => false, 'documentation' => 'When true, send this request to the FIPS-compliant regional endpoint. If the configured endpoint does not have a FIPS compliant endpoint, dispatching the request will return an error.', 'type' => 'Boolean', ], 'Endpoint' => [ 'builtIn' => 'SDK::Endpoint', 'required' => false, 'documentation' => 'Override the endpoint used to send this request', 'type' => 'String', ], ], 'rules' => [ [ 'conditions' => [ [ 'fn' => 'isSet', 'argv' => [ [ 'ref' => 'Endpoint', ], ], ], ], 'rules' => [ [ 'conditions' => [ [ 'fn' => 'booleanEquals', 'argv' => [ [ 'ref' => 'UseFIPS', ], true, ], ], ], 'error' => 'Invalid Configuration: FIPS and custom endpoint are not supported', 'type' => 'error', ], [ 'conditions' => [ [ 'fn' => 'booleanEquals', 'argv' => [ [ 'ref' => 'UseDualStack', ], true, ], ], ], 'error' => 'Invalid Configuration: Dualstack and custom endpoint are not supported', 'type' => 'error', ], [ 'conditions' => [], 'endpoint' => [ 'url' => [ 'ref' => 'Endpoint', ], 'properties' => [], 'headers' => [], ], 'type' => 'endpoint', ], ], 'type' => 'tree', ], [ 'conditions' => [ [ 'fn' => 'isSet', 'argv' => [ [ 'ref' => 'Region', ], ], ], ], 'rules' => [ [ 'conditions' => [ [ 'fn' => 'aws.partition', 'argv' => [ [ 'ref' => 'Region', ], ], 'assign' => 'PartitionResult', ], ], 'rules' => [ [ 'conditions' => [ [ 'fn' => 'booleanEquals', 'argv' => [ [ 'ref' => 'UseFIPS', ], true, ], ], [ 'fn' => 'booleanEquals', 'argv' => [ [ 'ref' => 'UseDualStack', ], true, ], ], ], 'rules' => [ [ 'conditions' => [ [ 'fn' => 'booleanEquals', 'argv' => [ true, [ 'fn' => 'getAttr', 'argv' => [ [ 'ref' => 'PartitionResult', ], 'supportsFIPS', ], ], ], ], [ 'fn' => 'booleanEquals', 'argv' => [ true, [ 'fn' => 'getAttr', 'argv' => [ [ 'ref' => 'PartitionResult', ], 'supportsDualStack', ], ], ], ], ], 'rules' => [ [ 'conditions' => [], 'endpoint' => [ 'url' => 'https://portal.sso-fips.{Region}.{PartitionResult#dualStackDnsSuffix}', 'properties' => [], 'headers' => [], ], 'type' => 'endpoint', ], ], 'type' => 'tree', ], [ 'conditions' => [], 'error' => 'FIPS and DualStack are enabled, but this partition does not support one or both', 'type' => 'error', ], ], 'type' => 'tree', ], [ 'conditions' => [ [ 'fn' => 'booleanEquals', 'argv' => [ [ 'ref' => 'UseFIPS', ], true, ], ], ], 'rules' => [ [ 'conditions' => [ [ 'fn' => 'booleanEquals', 'argv' => [ [ 'fn' => 'getAttr', 'argv' => [ [ 'ref' => 'PartitionResult', ], 'supportsFIPS', ], ], true, ], ], ], 'rules' => [ [ 'conditions' => [ [ 'fn' => 'stringEquals', 'argv' => [ [ 'fn' => 'getAttr', 'argv' => [ [ 'ref' => 'PartitionResult', ], 'name', ], ], 'aws-us-gov', ], ], ], 'endpoint' => [ 'url' => 'https://portal.sso.{Region}.amazonaws.com', 'properties' => [], 'headers' => [], ], 'type' => 'endpoint', ], [ 'conditions' => [], 'endpoint' => [ 'url' => 'https://portal.sso-fips.{Region}.{PartitionResult#dnsSuffix}', 'properties' => [], 'headers' => [], ], 'type' => 'endpoint', ], ], 'type' => 'tree', ], [ 'conditions' => [], 'error' => 'FIPS is enabled but this partition does not support FIPS', 'type' => 'error', ], ], 'type' => 'tree', ], [ 'conditions' => [ [ 'fn' => 'booleanEquals', 'argv' => [ [ 'ref' => 'UseDualStack', ], true, ], ], ], 'rules' => [ [ 'conditions' => [ [ 'fn' => 'booleanEquals', 'argv' => [ true, [ 'fn' => 'getAttr', 'argv' => [ [ 'ref' => 'PartitionResult', ], 'supportsDualStack', ], ], ], ], ], 'rules' => [ [ 'conditions' => [], 'endpoint' => [ 'url' => 'https://portal.sso.{Region}.{PartitionResult#dualStackDnsSuffix}', 'properties' => [], 'headers' => [], ], 'type' => 'endpoint', ], ], 'type' => 'tree', ], [ 'conditions' => [], 'error' => 'DualStack is enabled but this partition does not support DualStack', 'type' => 'error', ], ], 'type' => 'tree', ], [ 'conditions' => [], 'endpoint' => [ 'url' => 'https://portal.sso.{Region}.{PartitionResult#dnsSuffix}', 'properties' => [], 'headers' => [], ], 'type' => 'endpoint', ], ], 'type' => 'tree', ], ], 'type' => 'tree', ], [ 'conditions' => [], 'error' => 'Invalid Configuration: Missing Region', 'type' => 'error', ], ],];

View file

@ -8,12 +8,12 @@ $baseDir = dirname(dirname(dirname($vendorDir)));
return array( return array(
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'662a729f963d39afe703c9d9b7ab4a8c' => $vendorDir . '/symfony/polyfill-php83/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php', '8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php', 'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
'3109cb1a231dcd04bee1f9f620d46975' => $vendorDir . '/paragonie/sodium_compat/autoload.php', '3109cb1a231dcd04bee1f9f620d46975' => $vendorDir . '/paragonie/sodium_compat/autoload.php',
'662a729f963d39afe703c9d9b7ab4a8c' => $vendorDir . '/symfony/polyfill-php83/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
'5255c38a0faeba867671b61dfda6d864' => $vendorDir . '/paragonie/random_compat/lib/random.php', '5255c38a0faeba867671b61dfda6d864' => $vendorDir . '/paragonie/random_compat/lib/random.php',
'72579e7bd17821bb1321b87411366eae' => $vendorDir . '/illuminate/support/helpers.php', '72579e7bd17821bb1321b87411366eae' => $vendorDir . '/illuminate/support/helpers.php',

View file

@ -9,12 +9,12 @@ class ComposerStaticInited73ceb9c1bdec18b7c6d09764d1bce5
public static $files = array ( public static $files = array (
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'662a729f963d39afe703c9d9b7ab4a8c' => __DIR__ . '/..' . '/symfony/polyfill-php83/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php', '8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php', 'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
'3109cb1a231dcd04bee1f9f620d46975' => __DIR__ . '/..' . '/paragonie/sodium_compat/autoload.php', '3109cb1a231dcd04bee1f9f620d46975' => __DIR__ . '/..' . '/paragonie/sodium_compat/autoload.php',
'662a729f963d39afe703c9d9b7ab4a8c' => __DIR__ . '/..' . '/symfony/polyfill-php83/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
'5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php', '5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php',
'72579e7bd17821bb1321b87411366eae' => __DIR__ . '/..' . '/illuminate/support/helpers.php', '72579e7bd17821bb1321b87411366eae' => __DIR__ . '/..' . '/illuminate/support/helpers.php',

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
<?php return array( <?php return array(
'root' => array( 'root' => array(
'name' => 'ldap-account-manager/ldap-account-manager', 'name' => 'ldap-account-manager/ldap-account-manager',
'pretty_version' => '9.2', 'pretty_version' => '9.3',
'version' => '9.2.0.0', 'version' => '9.3.0.0',
'reference' => null, 'reference' => null,
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../../../../', 'install_path' => __DIR__ . '/../../../../',
@ -20,9 +20,9 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'aws/aws-sdk-php' => array( 'aws/aws-sdk-php' => array(
'pretty_version' => '3.346.2', 'pretty_version' => '3.356.8',
'version' => '3.346.2.0', 'version' => '3.356.8.0',
'reference' => 'd1403b5a39af7ab7af4fc538deb33013c19c8d33', 'reference' => '3efa8c62c11fedb17b90f60b2d3a9f815b406e63',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../aws/aws-sdk-php', 'install_path' => __DIR__ . '/../aws/aws-sdk-php',
'aliases' => array(), 'aliases' => array(),
@ -65,9 +65,9 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'duosecurity/duo_universal_php' => array( 'duosecurity/duo_universal_php' => array(
'pretty_version' => '1.1.0', 'pretty_version' => '1.1.1',
'version' => '1.1.0.0', 'version' => '1.1.1.0',
'reference' => 'a2852c46949a2de9ca6da908e4353a81c61b43a3', 'reference' => '4ee7253863d84653a60a8cad4b03aa3b66fcfd35',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../duosecurity/duo_universal_php', 'install_path' => __DIR__ . '/../duosecurity/duo_universal_php',
'aliases' => array(), 'aliases' => array(),
@ -101,27 +101,27 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'guzzlehttp/guzzle' => array( 'guzzlehttp/guzzle' => array(
'pretty_version' => '7.9.3', 'pretty_version' => '7.10.0',
'version' => '7.9.3.0', 'version' => '7.10.0.0',
'reference' => '7b2f29fe81dc4da0ca0ea7d42107a0845946ea77', 'reference' => 'b51ac707cfa420b7bfd4e4d5e510ba8008e822b4',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/guzzle', 'install_path' => __DIR__ . '/../guzzlehttp/guzzle',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'guzzlehttp/promises' => array( 'guzzlehttp/promises' => array(
'pretty_version' => '2.2.0', 'pretty_version' => '2.3.0',
'version' => '2.2.0.0', 'version' => '2.3.0.0',
'reference' => '7c69f28996b0a6920945dd20b3857e499d9ca96c', 'reference' => '481557b130ef3790cf82b713667b43030dc9c957',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/promises', 'install_path' => __DIR__ . '/../guzzlehttp/promises',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'guzzlehttp/psr7' => array( 'guzzlehttp/psr7' => array(
'pretty_version' => '2.7.1', 'pretty_version' => '2.8.0',
'version' => '2.7.1.0', 'version' => '2.8.0.0',
'reference' => 'c2270caaabe631b3b44c85f99e5a04bbb8060d16', 'reference' => '21dc724a0583619cd1652f673303492272778051',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/psr7', 'install_path' => __DIR__ . '/../guzzlehttp/psr7',
'aliases' => array(), 'aliases' => array(),
@ -173,8 +173,8 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'ldap-account-manager/ldap-account-manager' => array( 'ldap-account-manager/ldap-account-manager' => array(
'pretty_version' => '9.2', 'pretty_version' => '9.3',
'version' => '9.2.0.0', 'version' => '9.3.0.0',
'reference' => null, 'reference' => null,
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../../../../', 'install_path' => __DIR__ . '/../../../../',
@ -200,9 +200,9 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'nesbot/carbon' => array( 'nesbot/carbon' => array(
'pretty_version' => '3.9.1', 'pretty_version' => '3.10.2',
'version' => '3.9.1.0', 'version' => '3.10.2.0',
'reference' => 'ced71f79398ece168e24f7f7710462f462310d4d', 'reference' => '76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../nesbot/carbon', 'install_path' => __DIR__ . '/../nesbot/carbon',
'aliases' => array(), 'aliases' => array(),
@ -266,9 +266,9 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'phpseclib/phpseclib' => array( 'phpseclib/phpseclib' => array(
'pretty_version' => '3.0.43', 'pretty_version' => '3.0.46',
'version' => '3.0.43.0', 'version' => '3.0.46.0',
'reference' => '709ec107af3cb2f385b9617be72af8cf62441d02', 'reference' => '56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../phpseclib/phpseclib', 'install_path' => __DIR__ . '/../phpseclib/phpseclib',
'aliases' => array(), 'aliases' => array(),
@ -344,9 +344,9 @@
'psr/http-factory-implementation' => array( 'psr/http-factory-implementation' => array(
'dev_requirement' => false, 'dev_requirement' => false,
'provided' => array( 'provided' => array(
0 => '1.0', 0 => '^1.0',
1 => '^1.0', 1 => '*',
2 => '*', 2 => '1.0',
), ),
), ),
'psr/http-message' => array( 'psr/http-message' => array(
@ -361,8 +361,8 @@
'psr/http-message-implementation' => array( 'psr/http-message-implementation' => array(
'dev_requirement' => false, 'dev_requirement' => false,
'provided' => array( 'provided' => array(
0 => '1.0', 0 => '*',
1 => '*', 1 => '1.0',
), ),
), ),
'psr/http-server-handler' => array( 'psr/http-server-handler' => array(
@ -436,63 +436,63 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'spomky-labs/cbor-php' => array( 'spomky-labs/cbor-php' => array(
'pretty_version' => '3.1.0', 'pretty_version' => '3.1.1',
'version' => '3.1.0.0', 'version' => '3.1.1.0',
'reference' => '499d9bff0a6d59c4f1b813cc617fc3fd56d6dca4', 'reference' => '5404f3e21cbe72f5cf612aa23db2b922fd2f43bf',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../spomky-labs/cbor-php', 'install_path' => __DIR__ . '/../spomky-labs/cbor-php',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'spomky-labs/pki-framework' => array( 'spomky-labs/pki-framework' => array(
'pretty_version' => '1.2.3', 'pretty_version' => '1.3.0',
'version' => '1.2.3.0', 'version' => '1.3.0.0',
'reference' => '5ff1dcc21e961b60149a80e77f744fc047800b31', 'reference' => 'eced5b5ce70518b983ff2be486e902bbd15135ae',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../spomky-labs/pki-framework', 'install_path' => __DIR__ . '/../spomky-labs/pki-framework',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/clock' => array( 'symfony/clock' => array(
'pretty_version' => 'v6.4.13', 'pretty_version' => 'v6.4.24',
'version' => '6.4.13.0', 'version' => '6.4.24.0',
'reference' => 'b2bf55c4dd115003309eafa87ee7df9ed3dde81b', 'reference' => '5e15a9c9aeeb44a99f7cf24aa75aa9607795f6f8',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/clock', 'install_path' => __DIR__ . '/../symfony/clock',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/console' => array( 'symfony/console' => array(
'pretty_version' => 'v6.4.21', 'pretty_version' => 'v6.4.25',
'version' => '6.4.21.0', 'version' => '6.4.25.0',
'reference' => 'a3011c7b7adb58d89f6c0d822abb641d7a5f9719', 'reference' => '273fd29ff30ba0a88ca5fb83f7cf1ab69306adae',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/console', 'install_path' => __DIR__ . '/../symfony/console',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/deprecation-contracts' => array( 'symfony/deprecation-contracts' => array(
'pretty_version' => 'v3.5.1', 'pretty_version' => 'v3.6.0',
'version' => '3.5.1.0', 'version' => '3.6.0.0',
'reference' => '74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6', 'reference' => '63afe740e99a13ba87ec199bb07bbdee937a5b62',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/deprecation-contracts', 'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/http-client' => array( 'symfony/http-client' => array(
'pretty_version' => 'v6.4.19', 'pretty_version' => 'v6.4.25',
'version' => '6.4.19.0', 'version' => '6.4.25.0',
'reference' => '3294a433fc9d12ae58128174896b5b1822c28dad', 'reference' => 'b8e9dce2d8acba3c32af467bb58e0c3656886181',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/http-client', 'install_path' => __DIR__ . '/../symfony/http-client',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/http-client-contracts' => array( 'symfony/http-client-contracts' => array(
'pretty_version' => 'v3.5.2', 'pretty_version' => 'v3.6.0',
'version' => '3.5.2.0', 'version' => '3.6.0.0',
'reference' => 'ee8d807ab20fcb51267fdace50fbe3494c31e645', 'reference' => '75d7043853a42837e68111812f4d964b01e5101c',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/http-client-contracts', 'install_path' => __DIR__ . '/../symfony/http-client-contracts',
'aliases' => array(), 'aliases' => array(),
@ -505,17 +505,17 @@
), ),
), ),
'symfony/http-foundation' => array( 'symfony/http-foundation' => array(
'pretty_version' => 'v6.4.21', 'pretty_version' => 'v6.4.25',
'version' => '6.4.21.0', 'version' => '6.4.25.0',
'reference' => '3f0c7ea41db479383b81d436b836d37168fd5b99', 'reference' => '6bc974c0035b643aa497c58d46d9e25185e4b272',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/http-foundation', 'install_path' => __DIR__ . '/../symfony/http-foundation',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/polyfill-ctype' => array( 'symfony/polyfill-ctype' => array(
'pretty_version' => 'v1.32.0', 'pretty_version' => 'v1.33.0',
'version' => '1.32.0.0', 'version' => '1.33.0.0',
'reference' => 'a3cc8b044a6ea513310cbd48ef7333b384945638', 'reference' => 'a3cc8b044a6ea513310cbd48ef7333b384945638',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-ctype', 'install_path' => __DIR__ . '/../symfony/polyfill-ctype',
@ -523,17 +523,17 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/polyfill-intl-grapheme' => array( 'symfony/polyfill-intl-grapheme' => array(
'pretty_version' => 'v1.32.0', 'pretty_version' => 'v1.33.0',
'version' => '1.32.0.0', 'version' => '1.33.0.0',
'reference' => 'b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe', 'reference' => '380872130d3a5dd3ace2f4010d95125fde5d5c70',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/polyfill-intl-normalizer' => array( 'symfony/polyfill-intl-normalizer' => array(
'pretty_version' => 'v1.32.0', 'pretty_version' => 'v1.33.0',
'version' => '1.32.0.0', 'version' => '1.33.0.0',
'reference' => '3833d7255cc303546435cb650316bff708a1c75c', 'reference' => '3833d7255cc303546435cb650316bff708a1c75c',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer',
@ -541,8 +541,8 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/polyfill-mbstring' => array( 'symfony/polyfill-mbstring' => array(
'pretty_version' => 'v1.32.0', 'pretty_version' => 'v1.33.0',
'version' => '1.32.0.0', 'version' => '1.33.0.0',
'reference' => '6d857f4d76bd4b343eac26d6b539585d2bc56493', 'reference' => '6d857f4d76bd4b343eac26d6b539585d2bc56493',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
@ -550,17 +550,17 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/polyfill-php83' => array( 'symfony/polyfill-php83' => array(
'pretty_version' => 'v1.32.0', 'pretty_version' => 'v1.33.0',
'version' => '1.32.0.0', 'version' => '1.33.0.0',
'reference' => '2fb86d65e2d424369ad2905e83b236a8805ba491', 'reference' => '17f6f9a6b1735c0f163024d959f700cfbc5155e5',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php83', 'install_path' => __DIR__ . '/../symfony/polyfill-php83',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/polyfill-uuid' => array( 'symfony/polyfill-uuid' => array(
'pretty_version' => 'v1.32.0', 'pretty_version' => 'v1.33.0',
'version' => '1.32.0.0', 'version' => '1.33.0.0',
'reference' => '21533be36c24be3f4b1669c4725c7d1d2bab4ae2', 'reference' => '21533be36c24be3f4b1669c4725c7d1d2bab4ae2',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-uuid', 'install_path' => __DIR__ . '/../symfony/polyfill-uuid',
@ -568,45 +568,45 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/psr-http-message-bridge' => array( 'symfony/psr-http-message-bridge' => array(
'pretty_version' => 'v6.4.13', 'pretty_version' => 'v6.4.24',
'version' => '6.4.13.0', 'version' => '6.4.24.0',
'reference' => 'c9cf83326a1074f83a738fc5320945abf7fb7fec', 'reference' => '6954b4e8aef0e5d46f8558c90edcf27bb01b4724',
'type' => 'symfony-bridge', 'type' => 'symfony-bridge',
'install_path' => __DIR__ . '/../symfony/psr-http-message-bridge', 'install_path' => __DIR__ . '/../symfony/psr-http-message-bridge',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/service-contracts' => array( 'symfony/service-contracts' => array(
'pretty_version' => 'v3.5.1', 'pretty_version' => 'v3.6.0',
'version' => '3.5.1.0', 'version' => '3.6.0.0',
'reference' => 'e53260aabf78fb3d63f8d79d69ece59f80d5eda0', 'reference' => 'f021b05a130d35510bd6b25fe9053c2a8a15d5d4',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/service-contracts', 'install_path' => __DIR__ . '/../symfony/service-contracts',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/string' => array( 'symfony/string' => array(
'pretty_version' => 'v6.4.21', 'pretty_version' => 'v6.4.25',
'version' => '6.4.21.0', 'version' => '6.4.25.0',
'reference' => '73e2c6966a5aef1d4892873ed5322245295370c6', 'reference' => '7cdec7edfaf2cdd9c18901e35bcf9653d6209ff1',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/string', 'install_path' => __DIR__ . '/../symfony/string',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/translation' => array( 'symfony/translation' => array(
'pretty_version' => 'v6.4.21', 'pretty_version' => 'v6.4.24',
'version' => '6.4.21.0', 'version' => '6.4.24.0',
'reference' => 'bb92ea5588396b319ba43283a5a3087a034cb29c', 'reference' => '300b72643e89de0734d99a9e3f8494a3ef6936e1',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/translation', 'install_path' => __DIR__ . '/../symfony/translation',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/translation-contracts' => array( 'symfony/translation-contracts' => array(
'pretty_version' => 'v3.5.1', 'pretty_version' => 'v3.6.0',
'version' => '3.5.1.0', 'version' => '3.6.0.0',
'reference' => '4667ff3bd513750603a09c8dedbea942487fb07c', 'reference' => 'df210c7a2573f1913b2d17cc95f90f53a73d8f7d',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/translation-contracts', 'install_path' => __DIR__ . '/../symfony/translation-contracts',
'aliases' => array(), 'aliases' => array(),
@ -619,9 +619,9 @@
), ),
), ),
'symfony/uid' => array( 'symfony/uid' => array(
'pretty_version' => 'v6.4.13', 'pretty_version' => 'v6.4.24',
'version' => '6.4.13.0', 'version' => '6.4.24.0',
'reference' => '18eb207f0436a993fffbdd811b5b8fa35fa5e007', 'reference' => '17da16a750541a42cf2183935e0f6008316c23f7',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/uid', 'install_path' => __DIR__ . '/../symfony/uid',
'aliases' => array(), 'aliases' => array(),
@ -634,9 +634,9 @@
), ),
), ),
'web-auth/cose-lib' => array( 'web-auth/cose-lib' => array(
'pretty_version' => '4.4.0', 'pretty_version' => '4.4.2',
'version' => '4.4.0.0', 'version' => '4.4.2.0',
'reference' => '2166016e48e0214f4f63320a7758a9386d14c92a', 'reference' => 'a93b61c48fb587855f64a9ec11ad7b60e867cb15',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../web-auth/cose-lib', 'install_path' => __DIR__ . '/../web-auth/cose-lib',
'aliases' => array(), 'aliases' => array(),

View file

@ -38,7 +38,7 @@ class Client
const JWT_LEEWAY = 60; const JWT_LEEWAY = 60;
const SUCCESS_STATUS_CODE = 200; const SUCCESS_STATUS_CODE = 200;
const USER_AGENT = "duo_universal_php/1.1.0"; const USER_AGENT = "duo_universal_php/1.1.1";
const SIG_ALGORITHM = "HS512"; const SIG_ALGORITHM = "HS512";
const GRANT_TYPE = "authorization_code"; const GRANT_TYPE = "authorization_code";
const CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; const CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
@ -63,6 +63,7 @@ class Client
public $redirect_url; public $redirect_url;
public $use_duo_code_attribute; public $use_duo_code_attribute;
private $client_secret; private $client_secret;
private $user_agent_extension;
/** /**
* Retrieves exception message for DuoException from HTTPS result message. * Retrieves exception message for DuoException from HTTPS result message.
@ -190,6 +191,34 @@ class Client
$this->redirect_url = $redirect_url; $this->redirect_url = $redirect_url;
$this->use_duo_code_attribute = $use_duo_code_attribute; $this->use_duo_code_attribute = $use_duo_code_attribute;
$this->http_proxy = $http_proxy; $this->http_proxy = $http_proxy;
$this->user_agent_extension = null;
}
/**
* Append custom information to the user agent string.
*
* @param string $user_agent_extension Custom user agent information
*
* @return void
*/
public function appendToUserAgent(string $user_agent_extension): void
{
$this->user_agent_extension = trim($user_agent_extension);
}
/**
* Build the complete user agent string.
*
* @return string The complete user agent string
*/
private function buildUserAgent(): string
{
$base_user_agent = self::USER_AGENT . " php/" . phpversion() . " "
. php_uname();
if (!empty($this->user_agent_extension)) {
return $base_user_agent . " " . $this->user_agent_extension;
}
return $base_user_agent;
} }
/** /**
@ -282,7 +311,7 @@ class Client
public function exchangeAuthorizationCodeFor2FAResult(string $duoCode, string $username, ?string $nonce = null): array public function exchangeAuthorizationCodeFor2FAResult(string $duoCode, string $username, ?string $nonce = null): array
{ {
$token_endpoint = "https://" . $this->api_host . self::TOKEN_ENDPOINT; $token_endpoint = "https://" . $this->api_host . self::TOKEN_ENDPOINT;
$useragent = self::USER_AGENT . " php/" . phpversion() . " " . php_uname(); $useragent = $this->buildUserAgent();
$jwt = $this->createJwtPayload($token_endpoint); $jwt = $this->createJwtPayload($token_endpoint);
$request = ["grant_type" => self::GRANT_TYPE, $request = ["grant_type" => self::GRANT_TYPE,
"code" => $duoCode, "code" => $duoCode,

View file

@ -514,4 +514,144 @@ final class ClientTest extends TestCase
$this->assertStringContainsString("scope=openid", $duo_uri); $this->assertStringContainsString("scope=openid", $duo_uri);
$this->assertStringContainsString($expected_redir_uri, $duo_uri); $this->assertStringContainsString($expected_redir_uri, $duo_uri);
} }
/**
* Test that the user agent extension can be set and is included in requests.
*/
public function testAppendToUserAgent(): void
{
$custom_extension = "MyApp/1.0.0";
$id_token = $this->createIdToken();
$result = $this->createTokenResult($id_token);
// Mock the client to capture the user agent sent in HTTP requests
$client = $this->getMockBuilder(Client::class)
->setConstructorArgs([$this->client_id, $this->client_secret, $this->api_host, $this->redirect_url])
->setMethods(['makeHttpsCall'])
->getMock();
// Set up the mock to capture the user agent parameter
$captured_user_agent = null;
$client->method('makeHttpsCall')
->willReturnCallback(function ($endpoint, $request, $user_agent = null) use (&$captured_user_agent, $result) {
$captured_user_agent = $user_agent;
return $result;
});
// Append custom user agent extension
$client->appendToUserAgent($custom_extension);
// Make a call that uses the user agent
$client->exchangeAuthorizationCodeFor2FAResult($this->code, $this->username);
// Verify the user agent includes our custom extension
$this->assertNotNull($captured_user_agent);
$this->assertStringContainsString($custom_extension, $captured_user_agent);
$this->assertStringContainsString(Client::USER_AGENT, $captured_user_agent);
$this->assertStringContainsString("php/" . phpversion(), $captured_user_agent);
}
/**
* Test that user agent works correctly without any extension.
*/
public function testUserAgentWithoutExtension(): void
{
$id_token = $this->createIdToken();
$result = $this->createTokenResult($id_token);
// Mock the client to capture the user agent sent in HTTP requests
$client = $this->getMockBuilder(Client::class)
->setConstructorArgs([$this->client_id, $this->client_secret, $this->api_host, $this->redirect_url])
->setMethods(['makeHttpsCall'])
->getMock();
// Set up the mock to capture the user agent parameter
$captured_user_agent = null;
$client->method('makeHttpsCall')
->willReturnCallback(function ($endpoint, $request, $user_agent = null) use (&$captured_user_agent, $result) {
$captured_user_agent = $user_agent;
return $result;
});
// Make a call without setting any custom user agent extension
$client->exchangeAuthorizationCodeFor2FAResult($this->code, $this->username);
// Verify the user agent contains default information but no custom extension
$this->assertNotNull($captured_user_agent);
$this->assertStringContainsString(Client::USER_AGENT, $captured_user_agent);
$this->assertStringContainsString("php/" . phpversion(), $captured_user_agent);
$this->assertStringContainsString(php_uname(), $captured_user_agent);
}
/**
* Test that empty user agent extension is handled correctly.
*/
public function testAppendToUserAgentEmpty(): void
{
$id_token = $this->createIdToken();
$result = $this->createTokenResult($id_token);
// Mock the client to capture the user agent sent in HTTP requests
$client = $this->getMockBuilder(Client::class)
->setConstructorArgs([$this->client_id, $this->client_secret, $this->api_host, $this->redirect_url])
->setMethods(['makeHttpsCall'])
->getMock();
// Set up the mock to capture the user agent parameter
$captured_user_agent = null;
$client->method('makeHttpsCall')
->willReturnCallback(function ($endpoint, $request, $user_agent = null) use (&$captured_user_agent, $result) {
$captured_user_agent = $user_agent;
return $result;
});
// Append empty user agent extension
$client->appendToUserAgent("");
// Make a call
$client->exchangeAuthorizationCodeFor2FAResult($this->code, $this->username);
// Verify the user agent contains default information but no trailing space
$this->assertNotNull($captured_user_agent);
$this->assertStringContainsString(Client::USER_AGENT, $captured_user_agent);
$this->assertStringContainsString("php/" . phpversion(), $captured_user_agent);
$this->assertStringContainsString(php_uname(), $captured_user_agent);
// Ensure no trailing spaces from empty extension
$expected_base = Client::USER_AGENT . " php/" . phpversion() . " " . php_uname();
$this->assertEquals($expected_base, $captured_user_agent);
}
/**
* Test that whitespace-only user agent extension is handled correctly.
*/
public function testAppendToUserAgentWhitespace(): void
{
$id_token = $this->createIdToken();
$result = $this->createTokenResult($id_token);
// Mock the client to capture the user agent sent in HTTP requests
$client = $this->getMockBuilder(Client::class)
->setConstructorArgs([$this->client_id, $this->client_secret, $this->api_host, $this->redirect_url])
->setMethods(['makeHttpsCall'])
->getMock();
// Set up the mock to capture the user agent parameter
$captured_user_agent = null;
$client->method('makeHttpsCall')
->willReturnCallback(function ($endpoint, $request, $user_agent = null) use (&$captured_user_agent, $result) {
$captured_user_agent = $user_agent;
return $result;
});
// Append whitespace-only user agent extension
$client->appendToUserAgent(" ");
// Make a call
$client->exchangeAuthorizationCodeFor2FAResult($this->code, $this->username);
// Verify the user agent contains default information but no extra whitespace
$this->assertNotNull($captured_user_agent);
$expected_base = Client::USER_AGENT . " php/" . phpversion() . " " . php_uname();
$this->assertEquals($expected_base, $captured_user_agent);
}
} }

View file

@ -2,6 +2,17 @@
Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version. Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version.
## 7.10.0 - 2025-08-23
### Added
- Support for PHP 8.5
### Changed
- Adjusted `guzzlehttp/promises` version constraint to `^2.3`
- Adjusted `guzzlehttp/psr7` version constraint to `^2.8`
## 7.9.3 - 2025-03-27 ## 7.9.3 - 2025-03-27

View file

@ -81,8 +81,8 @@
"require": { "require": {
"php": "^7.2.5 || ^8.0", "php": "^7.2.5 || ^8.0",
"ext-json": "*", "ext-json": "*",
"guzzlehttp/promises": "^1.5.3 || ^2.0.3", "guzzlehttp/promises": "^2.3",
"guzzlehttp/psr7": "^2.7.0", "guzzlehttp/psr7": "^2.8",
"psr/http-client": "^1.0", "psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0" "symfony/deprecation-contracts": "^2.2 || ^3.0"
}, },

View file

@ -0,0 +1,6 @@
{
"name": "guzzle",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

View file

@ -125,7 +125,9 @@ class CurlFactory implements CurlFactoryInterface
unset($easy->handle); unset($easy->handle);
if (\count($this->handles) >= $this->maxHandles) { if (\count($this->handles) >= $this->maxHandles) {
if (PHP_VERSION_ID < 80000) {
\curl_close($resource); \curl_close($resource);
}
} else { } else {
// Remove all callback functions as they can hold onto references // Remove all callback functions as they can hold onto references
// and are not cleaned up by curl_reset. Using curl_setopt_array // and are not cleaned up by curl_reset. Using curl_setopt_array
@ -729,7 +731,10 @@ class CurlFactory implements CurlFactoryInterface
public function __destruct() public function __destruct()
{ {
foreach ($this->handles as $id => $handle) { foreach ($this->handles as $id => $handle) {
if (PHP_VERSION_ID < 80000) {
\curl_close($handle); \curl_close($handle);
}
unset($this->handles[$id]); unset($this->handles[$id]);
} }
} }

View file

@ -240,7 +240,10 @@ class CurlMultiHandler
$handle = $this->handles[$id]['easy']->handle; $handle = $this->handles[$id]['easy']->handle;
unset($this->delays[$id], $this->handles[$id]); unset($this->delays[$id], $this->handles[$id]);
\curl_multi_remove_handle($this->_mh, $handle); \curl_multi_remove_handle($this->_mh, $handle);
if (PHP_VERSION_ID < 80000) {
\curl_close($handle); \curl_close($handle);
}
return true; return true;
} }

View file

@ -333,8 +333,15 @@ class StreamHandler
); );
return $this->createResource( return $this->createResource(
function () use ($uri, &$http_response_header, $contextResource, $context, $options, $request) { function () use ($uri, $contextResource, $context, $options, $request) {
$resource = @\fopen((string) $uri, 'r', false, $contextResource); $resource = @\fopen((string) $uri, 'r', false, $contextResource);
// See https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_the_http_response_header_predefined_variable
if (function_exists('http_get_last_response_headers')) {
/** @var array|null */
$http_response_header = \http_get_last_response_headers();
}
$this->lastHeaders = $http_response_header ?? []; $this->lastHeaders = $http_response_header ?? [];
if (false === $resource) { if (false === $resource) {

View file

@ -187,12 +187,12 @@ final class Middleware
* Middleware that logs requests, responses, and errors using a message * Middleware that logs requests, responses, and errors using a message
* formatter. * formatter.
* *
* @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests.
*
* @param LoggerInterface $logger Logs messages. * @param LoggerInterface $logger Logs messages.
* @param MessageFormatterInterface|MessageFormatter $formatter Formatter used to create message strings. * @param MessageFormatterInterface|MessageFormatter $formatter Formatter used to create message strings.
* @param string $logLevel Level at which to log requests. * @param string $logLevel Level at which to log requests.
* *
* @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests.
*
* @return callable Returns a function that accepts the next handler. * @return callable Returns a function that accepts the next handler.
*/ */
public static function log(LoggerInterface $logger, $formatter, string $logLevel = 'info'): callable public static function log(LoggerInterface $logger, $formatter, string $logLevel = 'info'): callable

View file

@ -1,6 +1,13 @@
# CHANGELOG # CHANGELOG
## 2.3.0 - 2025-08-22
### Added
- PHP 8.5 support
## 2.2.0 - 2025-03-27 ## 2.2.0 - 2025-03-27
### Fixed ### Fixed

View file

@ -41,7 +41,7 @@ composer require guzzlehttp/promises
| Version | Status | PHP Version | | Version | Status | PHP Version |
|---------|---------------------|--------------| |---------|---------------------|--------------|
| 1.x | Security fixes only | >=5.5,<8.3 | | 1.x | Security fixes only | >=5.5,<8.3 |
| 2.x | Latest | >=7.2.5,<8.5 | | 2.x | Latest | >=7.2.5,<8.6 |
## Quick Start ## Quick Start

View file

@ -30,7 +30,7 @@
}, },
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2", "bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^8.5.39 || ^9.6.20" "phpunit/phpunit": "^8.5.44 || ^9.6.25"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

View file

@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 2.8.0 - 2025-08-23
### Added
- Allow empty lists as header values
### Changed
- PHP 8.5 support
## 2.7.1 - 2025-03-27 ## 2.7.1 - 2025-03-27
### Fixed ### Fixed

View file

@ -25,7 +25,7 @@ composer require guzzlehttp/psr7
| Version | Status | PHP Version | | Version | Status | PHP Version |
|---------|---------------------|--------------| |---------|---------------------|--------------|
| 1.x | EOL (2024-06-30) | >=5.4,<8.2 | | 1.x | EOL (2024-06-30) | >=5.4,<8.2 |
| 2.x | Latest | >=7.2.5,<8.5 | | 2.x | Latest | >=7.2.5,<8.6 |
## AppendStream ## AppendStream

View file

@ -62,7 +62,7 @@
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2", "bamarni/composer-bin-plugin": "^1.8.2",
"http-interop/http-factory-tests": "0.9.0", "http-interop/http-factory-tests": "0.9.0",
"phpunit/phpunit": "^8.5.39 || ^9.6.20" "phpunit/phpunit": "^8.5.44 || ^9.6.25"
}, },
"suggest": { "suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"

View file

@ -174,10 +174,6 @@ trait MessageTrait
return $this->trimAndValidateHeaderValues([$value]); return $this->trimAndValidateHeaderValues([$value]);
} }
if (count($value) === 0) {
throw new \InvalidArgumentException('Header value can not be an empty array.');
}
return $this->trimAndValidateHeaderValues($value); return $this->trimAndValidateHeaderValues($value);
} }

View file

@ -397,7 +397,7 @@ final class Utils
restore_error_handler(); restore_error_handler();
if ($ex) { if ($ex) {
/** @var $ex \RuntimeException */ /** @var \RuntimeException $ex */
throw $ex; throw $ex;
} }
@ -444,7 +444,7 @@ final class Utils
restore_error_handler(); restore_error_handler();
if ($ex) { if ($ex) {
/** @var $ex \RuntimeException */ /** @var \RuntimeException $ex */
throw $ex; throw $ex;
} }

View file

@ -44,21 +44,20 @@
"ext-json": "*", "ext-json": "*",
"carbonphp/carbon-doctrine-types": "<100.0", "carbonphp/carbon-doctrine-types": "<100.0",
"psr/clock": "^1.0", "psr/clock": "^1.0",
"symfony/clock": "^6.3 || ^7.0", "symfony/clock": "^6.3.12 || ^7.0",
"symfony/polyfill-mbstring": "^1.0", "symfony/polyfill-mbstring": "^1.0",
"symfony/translation": "^4.4.18 || ^5.2.1|| ^6.0 || ^7.0" "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0"
}, },
"require-dev": { "require-dev": {
"doctrine/dbal": "^3.6.3 || ^4.0", "doctrine/dbal": "^3.6.3 || ^4.0",
"doctrine/orm": "^2.15.2 || ^3.0", "doctrine/orm": "^2.15.2 || ^3.0",
"friendsofphp/php-cs-fixer": "^3.57.2", "friendsofphp/php-cs-fixer": "^3.75.0",
"kylekatarnls/multi-tester": "^2.5.3", "kylekatarnls/multi-tester": "^2.5.3",
"ondrejmirtes/better-reflection": "^6.25.0.4",
"phpmd/phpmd": "^2.15.0", "phpmd/phpmd": "^2.15.0",
"phpstan/extension-installer": "^1.3.1", "phpstan/extension-installer": "^1.4.3",
"phpstan/phpstan": "^1.11.2", "phpstan/phpstan": "^2.1.17",
"phpunit/phpunit": "^10.5.20", "phpunit/phpunit": "^10.5.46",
"squizlabs/php_codesniffer": "^3.9.0" "squizlabs/php_codesniffer": "^3.13.0"
}, },
"provide": { "provide": {
"psr/clock-implementation": "1.0" "psr/clock-implementation": "1.0"

View file

@ -127,53 +127,50 @@ This project exists thanks to all the people who contribute.
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. Support this project by becoming a sponsor. Your logo will show up here with a link to your website.
<!-- <open-collective-sponsors> --> <!-- <open-collective-sponsors> -->
<a title="Ставки на спорт, БК в Україні" href="https://betking.com.ua/sports-book/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Букмекер" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/065e61d2-f890-42db-b06c-8d40b39b2f0e/bk.jpg" width="96" height="96"></a> <a title="Porównanie kasyn online w Polsce. Darmowe automaty online." href="https://onlinekasyno-polis.pl/" target="_blank"><img alt="Online Kasyno Polis" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/12fe53d4-b2e4-4601-b9ea-7b652c414a38/274px%20274px-2.png" width="96" height="96"></a>
<a title="Онлайн казино 777 Україна" href="https://777.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Онлайн казино 777" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/7e572d50-1ce8-4d69-ae12-86cc80371373/ok-ua-777.png" width="96" height="96"></a> <a title="Онлайн казино 777 Україна" href="https://777.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Онлайн казино 777" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/7e572d50-1ce8-4d69-ae12-86cc80371373/ok-ua-777.png" width="96" height="96"></a>
<a title="Best non Gamstop sites in the UK" href="https://www.pieria.co.uk/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Best non Gamstop sites in the UK" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/34e340b8-e1de-4932-8a76-1b3ce2ec7ee8/logo_white%20bg%20(8).png" width="96" height="96"></a> <a title="Best non Gamstop sites in the UK" href="https://www.pieria.co.uk/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Best non Gamstop sites in the UK" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/34e340b8-e1de-4932-8a76-1b3ce2ec7ee8/logo_white%20bg%20(8).png" width="96" height="96"></a>
<a title="Real Money Pokies" href="https://onlinecasinoskiwi.co.nz/real-money-pokies/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Real Money Pokies" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/d0f7382e-32ea-4425-a8c4-3019f9ed501c/NZ_logo%20(6)%20(2).jpg" width="96" height="96"></a> <a title="Non GamStop Bookies UK" href="https://netto.co.uk/betting-sites-not-on-gamstop/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Non GamStop Bookies UK" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/51bfaa05-02b3-4cd9-b1a4-9d0d8f34cbae/%D0%97%D0%BD%D1%96%D0%BC%D0%BE%D0%BA%20%D0%B5%D0%BA%D1%80%D0%B0%D0%BD%D0%B0%202025-07-04%20%D0%BE%2015.21.16%20(1)%20(1)%20(1).jpg" width="126" height="96"></a>
<a title="Non GamStop Bookies UK" href="https://netto.co.uk/betting-sites-not-on-gamstop/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Non GamStop Bookies UK" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/43c5561c-8907-4ef7-a4ee-c6da054788b8/logo-site%20(3).jpg" width="96" height="96"></a>
<a title="#1 Guide To Online Gambling In Canada" href="https://casinohex.org/canada/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="CasinoHex Canada" src="https://opencollective-production.s3.us-west-1.amazonaws.com/79fdbcc0-a997-11eb-abbc-25e48b63c6dc.jpg" width="127.5" height="96"></a>
<a title="Trusted last mile route planning and route optimization" href="https://route4me.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Route4Me Route Planner" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/237386c3-48a2-47c6-97ac-5f888cdb4cda/Route4MeIconLogo.png" width="96" height="96"></a> <a title="Trusted last mile route planning and route optimization" href="https://route4me.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Route4Me Route Planner" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/237386c3-48a2-47c6-97ac-5f888cdb4cda/Route4MeIconLogo.png" width="96" height="96"></a>
<a title="Onlinecasinosgr.com" href="https://onlinecasinosgr.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Onlinecasinosgr.com" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/a9b971ee-db5f-4400-8c4b-76cf9bc35015/IMAGE%202024-06-14%2013%3A54%3A14.jpg" width="96" height="96"></a> <a title="gaia-wines.gr" href="https://www.gaia-wines.gr/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="gaia-wines.gr" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/a9b971ee-db5f-4400-8c4b-76cf9bc35015/IMAGE%202024-06-14%2013%3A54%3A14.jpg" width="96" height="96"></a>
<a title="Онлайн казино та БК (ставки на спорт) в Україні" href="https://betking.com.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Betking казино" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/08587758-582c-4136-aba5-2519230960d3/betking.jpg" width="96" height="96"></a> <a title="Ставки на спорт, БК в Україні" href="https://betking.com.ua/sports-book/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Букмекер" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/065e61d2-f890-42db-b06c-8d40b39b2f0e/bk.jpg" width="96" height="96"></a>
<a title="WestNews проект Александра Победы о гемблинге и онлайн-казино в Украине, предлагающий новости, обзоры, рейтинги и гиды по игорным заведениям." href="https://westnews.com.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="WestNews онлайн казино Украины" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/7fae83dd-0d53-42f7-b63c-d7062a86ccb1/3502ab17-a150-40e1-8f01-c26ff60c4cf8.png" width="96" height="96"></a>
<a title="Best Casinos not on Gamstop in the UK 2025" href="https://www.vso.org.uk/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="best non Gamstop casinos" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/3f48874e-f2f6-4062-a2a2-1500677ee3d9/125%D1%85125%20(1).jpg" width="96" height="96"></a> <a title="Best Casinos not on Gamstop in the UK 2025" href="https://www.vso.org.uk/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="best non Gamstop casinos" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/3f48874e-f2f6-4062-a2a2-1500677ee3d9/125%D1%85125%20(1).jpg" width="96" height="96"></a>
<a title="Проект с обзорами легальных онлайн казино Украины. Мы помогаем выбрать лучше казино онлайн игрокам." href="https://sportarena.com/casino/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Лучшие онлайн казино Украины на Sportarena" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/765475f7-3fea-4867-8f83-7b6f91b06128/sportarena%20(1).png" width="60" height="64"></a> <a title="#1 Guide To Online Gambling In Canada" href="https://casinohex.org/canada/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="CasinoHex Canada" src="https://opencollective-production.s3.us-west-1.amazonaws.com/79fdbcc0-a997-11eb-abbc-25e48b63c6dc.jpg" width="127.5" height="96"></a>
<a title="Проєкт з оглядами онлайн казино та їхніх бонусів. На сайті можна знайти актуальні промокоди та інші бонуси онлайн казино України." href="https://y-k.com.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Онлайн казино та їхні бонуси y-k.com.ua" src="https://logo.clearbit.com/y-k.com.ua" width="64" height="64"></a> <a title="Real Money Pokies" href="https://onlinecasinoskiwi.co.nz/real-money-pokies/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Real Money Pokies" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/d0f7382e-32ea-4425-a8c4-3019f9ed501c/NZ_logo%20(6)%20(2).jpg" width="96" height="96"></a>
<a title="Онлайн казино та БК (ставки на спорт) в Україні" href="https://betking.com.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Betking казино" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/08587758-582c-4136-aba5-2519230960d3/betking.jpg" width="64" height="64"></a>
<a title="WestNews проект Александра Победы о гемблинге и онлайн-казино в Украине, предлагающий новости, обзоры, рейтинги и гиды по игорным заведениям." href="https://westnews.com.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="WestNews онлайн казино Украины" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/7fae83dd-0d53-42f7-b63c-d7062a86ccb1/3502ab17-a150-40e1-8f01-c26ff60c4cf8.png" width="64" height="64"></a>
<a title="UK casinos not on GamStop" href="https://www.stjames-theatre.co.uk/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="UK casinos not on GamStop" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/34e5e82e-2121-4082-a321-050dca381d6c/%D0%97%D0%BD%D1%96%D0%BC%D0%BE%D0%BA%20%D0%B5%D0%BA%D1%80%D0%B0%D0%BD%D0%B0%202025-01-10%20%D0%BE%2015.29.42%20(1)%20(1).jpg" width="64" height="64"></a><details><summary>See more</summary>
<a title="OnlineCasinosSpelen" href="https://onlinecasinosspelen.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="OnlineCasinosSpelen" src="https://logo.clearbit.com/onlinecasinosspelen.com" width="64" height="64"></a> <a title="OnlineCasinosSpelen" href="https://onlinecasinosspelen.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="OnlineCasinosSpelen" src="https://logo.clearbit.com/onlinecasinosspelen.com" width="64" height="64"></a>
<a title="Betwinner is an online bookmaker offering sports betting, casino games, and more." href="https://guidebook.betwinner.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Guidebook.BetWinner" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/82cab29a-7002-4924-83bf-2eecb03d07c4/0x0.png" width="64" height="64"></a> <a title="Betwinner is an online bookmaker offering sports betting, casino games, and more." href="https://guidebook.betwinner.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Guidebook.BetWinner" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/82cab29a-7002-4924-83bf-2eecb03d07c4/0x0.png" width="64" height="64"></a>
<a title="Онлайн казино casino.ua" href="https://casino.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Онлайн казино casino.ua" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/32790ee6-245b-45bd-acf7-7a661fe2cf9f/logo.png" width="64" height="64"></a> <a title="Онлайн казино casino.ua" href="https://casino.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Онлайн казино casino.ua" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/32790ee6-245b-45bd-acf7-7a661fe2cf9f/logo.png" width="64" height="64"></a>
<a title="Best PayID Pokies in Australia" href="https://payid-gambler.net/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="PayIDGambler" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/b120ff74-a4cc-4e25-a96f-2b040d60de14/payidgambler.png" width="64" height="64"></a> <a title="Best PayID Pokies in Australia" href="https://payid-gambler.net/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="PayIDGambler" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/b120ff74-a4cc-4e25-a96f-2b040d60de14/payidgambler.png" width="64" height="64"></a>
<a title="Legal-casino.net незалежний інтернет-портал, присвячений ліцензійним онлайн казино України та азартним іграм в інтернеті. На якому не проводяться ігри на реальні чи віртуальні гроші." href="https://legal-casino.net/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Legal Casino" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/79978436-a1cb-42f1-8269-d495b232934a/legal-casino.jpg" width="64" height="64"></a> <a title="Legal-casino.net незалежний інтернет-портал, присвячений ліцензійним онлайн казино України та азартним іграм в інтернеті. На якому не проводяться ігри на реальні чи віртуальні гроші." href="https://legal-casino.net/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Legal Casino" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/79978436-a1cb-42f1-8269-d495b232934a/legal-casino.jpg" width="64" height="64"></a>
<a title="WildWinz online casino" href="https://wildwinz.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="WildWinz Casino" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/ccfcee7c-775c-4d43-ba23-3f0d2969497b/wildwinz.jpg" width="64" height="64"></a> <a title="Playfortune.net.br" href="https://playfortune.net.br/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Playfortune.net.br" src="https://logo.clearbit.com/playfortune.net.br" width="64" height="64"></a>
<a title="The Betwinner program allows individuals and businesses to earn commissions." href="https://betwinnerpartner.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Betwinner Partner" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/46a67975-2b70-4b91-9106-0e224c664b21/images%20(12).jpg" width="64" height="64"></a> <a title="https://play-fortune.pl/kasyno/z-minimalnym-depozytem/" href="https://play-fortune.pl/kasyno/z-minimalnym-depozytem/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="https://play-fortune.pl/kasyno/z-minimalnym-depozytem/" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/cbeea308-5148-4f6c-ac6e-dbfa029aadd1/PL.png" width="64" height="64"></a>
<a title="Top Casinos Canada" href="https://topcasino.net/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Top Casinos Canada" src="https://topcasino.net/img/topcasino-logo-cover.png" width="64" height="64"></a>
<a title="Best-betting.net is an Indian website where you can always find interesting, useful, and up-to-date information about cricket and other sports. Additionally, on our portal, you can explore predictions and betting opportunities for the most exciting sports" href="https://best-betting.net/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Best Betting" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/4b437e94-747c-4cf5-be67-d11bf8472d76/bestbetting-logo-cover.png" width="64" height="64"></a> <a title="Best-betting.net is an Indian website where you can always find interesting, useful, and up-to-date information about cricket and other sports. Additionally, on our portal, you can explore predictions and betting opportunities for the most exciting sports" href="https://best-betting.net/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Best Betting" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/4b437e94-747c-4cf5-be67-d11bf8472d76/bestbetting-logo-cover.png" width="64" height="64"></a>
<a title="inkedin" href="https://inkedin.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="inkedin" src="https://logo.clearbit.com/inkedin.com" width="64" height="64"></a> <a title="Slots not on GamStop" href="https://nogamstopcasinos.uk/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Slots not on GamStop" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/3b5fedc2-f3e5-41f5-84a9-869e2cbeb632/%D0%97%D0%BD%D1%96%D0%BC%D0%BE%D0%BA%20%D0%B5%D0%BA%D1%80%D0%B0%D0%BD%D0%B0%202025-05-01%20%D0%BE%2019.38.02%20(1)%20(1)%20(1).jpg" width="64" height="64"></a>
<a title="Актуальний та повносправний рейтинг онлайн казино України, ґрунтований на відгуках реальних гравців." href="https://uk.onlinecasino.in.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Онлайн казино України" src="https://opencollective-production.s3.us-west-1.amazonaws.com/c0b4b090-eef8-11ec-9cb7-0527a205b226.png" width="64" height="64"></a>
<a title="Buy TikTok Followers is a leading provider of social media growth solutions for TikTok.com." href="https://buytiktokfollowers.co/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="BuyTikTokFollowers.co" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/8626c295-9414-4e0c-b228-38ca2704cd68/btf-favicon.png" width="64" height="64"></a>
<a title="Offshore bookmakers review site." href="https://www.sportsbookreviewsonline.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Sportsbook Reviews Online" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/6d499f24-d669-4fc6-bb5f-b87184aa7963/sportsbookreviewsonline_com.png" width="64" height="64"></a> <a title="Offshore bookmakers review site." href="https://www.sportsbookreviewsonline.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Sportsbook Reviews Online" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/6d499f24-d669-4fc6-bb5f-b87184aa7963/sportsbookreviewsonline_com.png" width="64" height="64"></a>
<a title="Ставки на спорт Favbet" href="https://www.favbet.ua/uk/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Ставки на спорт Favbet" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/d86d313e-7b17-42fa-8b76-3f17fbf681a2/favbet-logo.jpg" width="64" height="64"></a> <a title="Ставки на спорт Favbet" href="https://www.favbet.ua/uk/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Ставки на спорт Favbet" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/d86d313e-7b17-42fa-8b76-3f17fbf681a2/favbet-logo.jpg" width="64" height="64"></a>
<a title="Znajdź najlepsze zakłady bukmacherskie w Polsce w 2023 roku. Probukmacher.pl to Twoje kompendium wiedzy na temat bukmacherów!" href="https://www.probukmacher.pl?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Probukmacher" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/caf50271-4560-4ffe-a434-ea15239168db/Screenshot_1.png" width="89" height="64"></a> <a title="inkedin" href="https://inkedin.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="inkedin" src="https://logo.clearbit.com/inkedin.com" width="42" height="42"></a>
<a title="Casino-portugal.pt" href="https://casino-portugal.pt/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Casino-portugal.pt" src="https://logo.clearbit.com/casino-portugal.pt" width="64" height="64"></a> <a title="Casino-portugal.pt" href="https://casino-portugal.pt/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Casino-portugal.pt" src="https://logo.clearbit.com/casino-portugal.pt" width="42" height="42"></a>
<a title="Znajdź najlepsze zakłady bukmacherskie w Polsce w 2023 roku. Probukmacher.pl to Twoje kompendium wiedzy na temat bukmacherów!" href="https://www.probukmacher.pl?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Probukmacher" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/caf50271-4560-4ffe-a434-ea15239168db/Screenshot_1.png" width="58" height="42"></a>
<a title="Get professional support for Carbon" href="https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&amp;utm_medium=referral&amp;utm_campaign=docs" target="_blank"><img alt="Tidelift" src="https://carbon.nesbot.com/docs/sponsors/tidelift-brand.png" width="84" height="42"></a> <a title="Get professional support for Carbon" href="https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&amp;utm_medium=referral&amp;utm_campaign=docs" target="_blank"><img alt="Tidelift" src="https://carbon.nesbot.com/docs/sponsors/tidelift-brand.png" width="84" height="42"></a>
<a title="Playfortune.net.br" href="https://playfortune.net.br/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Playfortune.net.br" src="https://logo.clearbit.com/playfortune.net.br" width="42" height="42"></a> <a title="Актуальний та повносправний рейтинг онлайн казино України, ґрунтований на відгуках реальних гравців." href="https://uk.onlinecasino.in.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Онлайн казино України" src="https://opencollective-production.s3.us-west-1.amazonaws.com/c0b4b090-eef8-11ec-9cb7-0527a205b226.png" width="42" height="42"></a>
<a title="https://play-fortune.pl/kasyno/z-minimalnym-depozytem/" href="https://play-fortune.pl/kasyno/z-minimalnym-depozytem/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="https://play-fortune.pl/kasyno/z-minimalnym-depozytem/" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/cbeea308-5148-4f6c-ac6e-dbfa029aadd1/PL.png" width="42" height="42"></a> <a title="Sites not on GamStop" href="https://casinonotongamstop.uk/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Sites not on GamStop" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/5c5977b8-1e94-43d6-b2d7-4af25bb85dbd/%D0%97%D0%BD%D1%96%D0%BC%D0%BE%D0%BA%20%D0%B5%D0%BA%D1%80%D0%B0%D0%BD%D0%B0%202025-05-01%20%D0%BE%2015.08.38%20(1)%20(2).jpg" width="68" height="42"></a>
<a title="UK casinos not on GamStop" href="https://www.stjamestheatre.co.uk/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="UK casinos not on GamStop" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/34e5e82e-2121-4082-a321-050dca381d6c/%D0%97%D0%BD%D1%96%D0%BC%D0%BE%D0%BA%20%D0%B5%D0%BA%D1%80%D0%B0%D0%BD%D0%B0%202025-01-10%20%D0%BE%2015.29.42%20(1)%20(1).jpg" width="42" height="42"></a> <a title="Проект с обзорами легальных онлайн казино Украины. Мы помогаем выбрать лучше казино онлайн игрокам." href="https://sportarena.com/casino/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Лучшие онлайн казино Украины на Sportarena" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/765475f7-3fea-4867-8f83-7b6f91b06128/sportarena%20(1).png" width="40" height="42"></a>
<a title="Проєкт з оглядами онлайн казино та їхніх бонусів. На сайті можна знайти актуальні промокоди та інші бонуси онлайн казино України." href="https://y-k.com.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Онлайн казино та їхні бонуси y-k.com.ua" src="https://logo.clearbit.com/y-k.com.ua" width="42" height="42"></a>
<a title="Slots City® ➢ Лучшее лицензионно казино онлайн и оффлайн на гривны в Украине. 【 Более1500 игровых автоматов и слотов】✅ Официально и Безопасно" href="https://slotscity.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Slots City" src="https://opencollective-production.s3.us-west-1.amazonaws.com/d7e298c0-7abe-11ed-8553-230872f5e54d.png" width="59" height="42"></a> <a title="Slots City® ➢ Лучшее лицензионно казино онлайн и оффлайн на гривны в Украине. 【 Более1500 игровых автоматов и слотов】✅ Официально и Безопасно" href="https://slotscity.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Slots City" src="https://opencollective-production.s3.us-west-1.amazonaws.com/d7e298c0-7abe-11ed-8553-230872f5e54d.png" width="59" height="42"></a>
<a title="Entertainment" href="https://www.nongamstopbets.com/casinos-not-on-gamstop/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Non-GamStop Bets UK" src="https://logo.clearbit.com/nongamstopbets.com" width="42" height="42"></a> <a title="WildWinz online casino" href="https://wildwinz.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="WildWinz Casino" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/ccfcee7c-775c-4d43-ba23-3f0d2969497b/wildwinz.jpg" width="42" height="42"></a>
<a title="ігрові автомати беткінг" href="https://betking.com.ua/games/all-slots/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Ігрові автомати" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/94601d07-3205-4c60-9c2d-9b8194dbefb7/skg-blue.png" width="42" height="42"></a> <a title="ігрові автомати беткінг" href="https://betking.com.ua/games/all-slots/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Ігрові автомати" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/94601d07-3205-4c60-9c2d-9b8194dbefb7/skg-blue.png" width="42" height="42"></a>
<a title="Casinos not on Gamstop" href="https://lgcnews.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Non Gamstop Casinos" src="https://lgcnews.com/wp-content/uploads/2018/01/LGC-logo-v8-temp.png" width="84" height="42"></a> <a title="Casinos not on Gamstop" href="https://lgcnews.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Non Gamstop Casinos" src="https://lgcnews.com/wp-content/uploads/2018/01/LGC-logo-v8-temp.png" width="84" height="42"></a>
<a title="Slotozilla website" href="https://www.slotozilla.com/nz/free-spins" target="_blank"><img alt="Slotozilla" src="https://carbon.nesbot.com/docs/sponsors/slotozilla.png" width="42" height="42"></a> <a title="Slotozilla website" href="https://www.slotozilla.com/nz/free-spins" target="_blank"><img alt="Slotozilla" src="https://carbon.nesbot.com/docs/sponsors/slotozilla.png" width="42" height="42"></a>
<a title="Per tutte le ultime notizie sul gioco d&#039;azzardo Non AAMS, le recensioni e i bonus di iscrizione." href="https://casinononaams.online?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="casino non aams" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/c60b92d1-590c-48a5-9527-fb0909431a86/casino%20non%20aams%20icon.jpg" width="42" height="42"></a> <a title="Per tutte le ultime notizie sul gioco d&#039;azzardo Non AAMS, le recensioni e i bonus di iscrizione." href="https://casinononaams.online?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="casino non aams" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/c60b92d1-590c-48a5-9527-fb0909431a86/casino%20non%20aams%20icon.jpg" width="42" height="42"></a>
<a title="Credit Zaim" href="https://creditzaim.com.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Credit Zaim" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/a856ed4e-651d-47c9-aa7a-98059423b3a6/creditzaim_logo.png" width="42" height="42"></a> <a title="Credit Zaim" href="https://creditzaim.com.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Credit Zaim" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/a856ed4e-651d-47c9-aa7a-98059423b3a6/creditzaim_logo.png" width="42" height="42"></a>
<a title="Incognito" href="https://opencollective.com/user-7146c9f8?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Incognito" src="https://images.opencollective.com/user-7146c9f8/avatar/256.png" width="42" height="42"></a><!-- </open-collective-sponsors> --> <a title="ssddanbrown" href="https://github.com/ssddanbrown" target="_blank"><img alt="ssddanbrown" src="https://avatars.githubusercontent.com/u/8343178?s=128&v=4" width="42" height="42"></a></details><!-- </open-collective-sponsors> -->
[[See all](https://carbon.nesbot.com/#sponsors)] [[See all](https://carbon.nesbot.com/#sponsors)]
[[Become a sponsor via OpenCollective*](https://opencollective.com/Carbon#sponsor)]
<a href="https://github.com/ssddanbrown" target="_blank"><img src="https://avatars.githubusercontent.com/u/8343178?s=128&v=4" width="42" height="42"></a> [[Become a sponsor via OpenCollective*](https://opencollective.com/Carbon#sponsor)]
<a href="https://github.com/BallymaloeCookerySchool" target="_blank"><img src="https://avatars.githubusercontent.com/u/123261043?s=128&v=4" width="42" height="42"></a>
[[Become a sponsor via GitHub*](https://github.com/sponsors/kylekatarnls)] [[Become a sponsor via GitHub*](https://github.com/sponsors/kylekatarnls)]

View file

@ -158,7 +158,10 @@ function getOpenCollectiveSponsors(): string
$status = null; $status = null;
$rank = 0; $rank = 0;
if ($monthlyContribution > 29 || $yearlyContribution > 700) { if ($monthlyContribution > 50 || $yearlyContribution > 900) {
$status = 'sponsor';
$rank = 5;
} elseif ($monthlyContribution > 29 || $yearlyContribution > 700) {
$status = 'sponsor'; $status = 'sponsor';
$rank = 4; $rank = 4;
} elseif ($monthlyContribution > 14.5 || $yearlyContribution > 500) { } elseif ($monthlyContribution > 14.5 || $yearlyContribution > 500) {
@ -190,6 +193,7 @@ function getOpenCollectiveSponsors(): string
$membersByUrl = []; $membersByUrl = [];
$output = ''; $output = '';
$extra = '';
foreach ($list as $member) { foreach ($list as $member) {
$url = $member['website'] ?? $member['profile']; $url = $member['website'] ?? $member['profile'];
@ -229,12 +233,30 @@ function getOpenCollectiveSponsors(): string
$height *= 1.5; $height *= 1.5;
} }
$output .= "\n".'<a title="'.$title.'" href="'.$href.'" target="_blank"'.$rel.'>'. $link = "\n".'<a title="'.$title.'" href="'.$href.'" target="_blank"'.$rel.'>'.
'<img alt="'.$alt.'" src="'.$src.'" width="'.$width.'" height="'.$height.'">'. '<img alt="'.$alt.'" src="'.$src.'" width="'.$width.'" height="'.$height.'">'.
'</a>'; '</a>';
if ($member['rank'] >= 5) {
$output .= $link;
continue;
}
$extra .= $link;
}
$github = [
8343178 => 'ssddanbrown',
];
foreach ($github as $avatar => $user) {
$extra .= "\n".'<a title="'.$user.'" href="https://github.com/'.$user.'" target="_blank">'.
'<img alt="'.$user.'" src="https://avatars.githubusercontent.com/u/'.$avatar.'?s=128&v=4" width="42" height="42">'.
'</a>';
} }
return $output; return $output.'<details><summary>See more</summary>'.$extra.'</details>';
} }
file_put_contents('readme.md', preg_replace_callback( file_put_contents('readme.md', preg_replace_callback(

View file

@ -533,7 +533,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface
($totalDays - $this->d).' days '. ($totalDays - $this->d).' days '.
($hours - $this->h).' hours '. ($hours - $this->h).' hours '.
($minutes - $this->i).' minutes '. ($minutes - $this->i).' minutes '.
($intervalSeconds - $this->s).' seconds '. number_format($intervalSeconds - $this->s, 6, '.', '').' seconds '.
($microseconds - $intervalMicroseconds).' microseconds ', ($microseconds - $intervalMicroseconds).' microseconds ',
)); ));
} }
@ -1082,7 +1082,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface
default: default:
throw new InvalidIntervalException( throw new InvalidIntervalException(
\sprintf('Invalid part %s in definition %s', $part, $intervalDefinition), "Invalid part $part in definition $intervalDefinition",
); );
} }
} }
@ -3088,16 +3088,50 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface
// PHP <= 8.1 // PHP <= 8.1
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
foreach ($properties as $property => $value) { $properties = array_combine(
$name = preg_replace('/^\0.+\0/', '', $property); array_map(
static fn (string $property) => preg_replace('/^\0.+\0/', '', $property),
array_keys($data),
),
$data,
);
$localStrictMode = $this->localStrictModeEnabled; $localStrictMode = $this->localStrictModeEnabled;
$this->localStrictModeEnabled = false; $this->localStrictModeEnabled = false;
$this->$name = $value; $days = $properties['days'] ?? false;
$this->days = $days === false ? false : (int) $days;
$this->y = (int) ($properties['y'] ?? 0);
$this->m = (int) ($properties['m'] ?? 0);
$this->d = (int) ($properties['d'] ?? 0);
$this->h = (int) ($properties['h'] ?? 0);
$this->i = (int) ($properties['i'] ?? 0);
$this->s = (int) ($properties['s'] ?? 0);
$this->f = (float) ($properties['f'] ?? 0.0);
// @phpstan-ignore-next-line
$this->weekday = (int) ($properties['weekday'] ?? 0);
// @phpstan-ignore-next-line
$this->weekday_behavior = (int) ($properties['weekday_behavior'] ?? 0);
// @phpstan-ignore-next-line
$this->first_last_day_of = (int) ($properties['first_last_day_of'] ?? 0);
$this->invert = (int) ($properties['invert'] ?? 0);
// @phpstan-ignore-next-line
$this->special_type = (int) ($properties['special_type'] ?? 0);
// @phpstan-ignore-next-line
$this->special_amount = (int) ($properties['special_amount'] ?? 0);
// @phpstan-ignore-next-line
$this->have_weekday_relative = (int) ($properties['have_weekday_relative'] ?? 0);
// @phpstan-ignore-next-line
$this->have_special_relative = (int) ($properties['have_special_relative'] ?? 0);
parent::__construct(self::getDateIntervalSpec($this));
if ($name !== 'localStrictModeEnabled') { foreach ($properties as $property => $value) {
$this->localStrictModeEnabled = $localStrictMode; if ($property === 'localStrictModeEnabled') {
continue;
} }
$this->$property = $value;
} }
$this->localStrictModeEnabled = $properties['localStrictModeEnabled'] ?? $localStrictMode;
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
} }
@ -3156,7 +3190,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface
} }
$microseconds = $interval->f; $microseconds = $interval->f;
$instance = new $className(static::getDateIntervalSpec($interval, false, $skip)); $instance = self::buildInstance($interval, $className, $skip);
if ($instance instanceof self) { if ($instance instanceof self) {
$instance->originalInput = $interval; $instance->originalInput = $interval;
@ -3175,6 +3209,83 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface
return self::withOriginal($instance, $interval); return self::withOriginal($instance, $interval);
} }
/**
* @template T of DateInterval
*
* @param DateInterval $interval
*
* @psalm-param class-string<T> $className
*
* @return T
*/
private static function buildInstance(
DateInterval $interval,
string $className,
array $skip = [],
): object {
$serialization = self::buildSerializationString($interval, $className, $skip);
return match ($serialization) {
null => new $className(static::getDateIntervalSpec($interval, false, $skip)),
default => unserialize($serialization),
};
}
/**
* As demonstrated by rlanvin (https://github.com/rlanvin) in
* https://github.com/briannesbitt/Carbon/issues/3018#issuecomment-2888538438
*
* Modifying the output of serialize() to change the class name and unserializing
* the tweaked string allows creating new interval instances where the ->days
* property can be set. It's not possible neither with `new` nto with `__set_state`.
*
* It has a non-negligible performance cost, so we'll use this method only if
* $interval->days !== false.
*/
private static function buildSerializationString(
DateInterval $interval,
string $className,
array $skip = [],
): ?string {
if ($interval->days === false || PHP_VERSION_ID < 8_02_00 || $skip !== []) {
return null;
}
// De-enhance CarbonInterval objects to be serializable back to DateInterval
if ($interval instanceof self && !is_a($className, self::class, true)) {
$interval = clone $interval;
unset($interval->timezoneSetting);
unset($interval->originalInput);
unset($interval->startDate);
unset($interval->endDate);
unset($interval->rawInterval);
unset($interval->absolute);
unset($interval->initialValues);
unset($interval->clock);
unset($interval->step);
unset($interval->localMonthsOverflow);
unset($interval->localYearsOverflow);
unset($interval->localStrictModeEnabled);
unset($interval->localHumanDiffOptions);
unset($interval->localToStringFormat);
unset($interval->localSerializer);
unset($interval->localMacros);
unset($interval->localGenericMacros);
unset($interval->localFormatFunction);
unset($interval->localTranslator);
}
$serialization = serialize($interval);
$inputClass = $interval::class;
$expectedStart = 'O:'.\strlen($inputClass).':"'.$inputClass.'":';
if (!str_starts_with($serialization, $expectedStart)) {
return null; // @codeCoverageIgnore
}
return 'O:'.\strlen($className).':"'.$className.'":'.substr($serialization, \strlen($expectedStart));
}
private static function copyStep(self $from, self $to): void private static function copyStep(self $from, self $to): void
{ {
$to->setStep($from->getStep()); $to->setStep($from->getStep());
@ -3408,7 +3519,8 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface
return; return;
} }
if (PHP_VERSION_ID !== 80320) { // @codeCoverageIgnoreStart
if (PHP_VERSION_ID !== 8_03_20) {
$instance->$unit += $value; $instance->$unit += $value;
return; return;
@ -3416,8 +3528,10 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface
// Cannot use +=, nor set to a negative value directly as it segfaults in PHP 8.3.20 // Cannot use +=, nor set to a negative value directly as it segfaults in PHP 8.3.20
self::setIntervalUnit($instance, $unit, ($instance->$unit ?? 0) + $value); self::setIntervalUnit($instance, $unit, ($instance->$unit ?? 0) + $value);
// @codeCoverageIgnoreEnd
} }
/** @codeCoverageIgnore */
private static function setIntervalUnit(DateInterval $instance, string $unit, mixed $value): void private static function setIntervalUnit(DateInterval $instance, string $unit, mixed $value): void
{ {
switch ($unit) { switch ($unit) {

View file

@ -176,9 +176,9 @@ require PHP_VERSION < 8.2
* *
* @mixin DeprecatedPeriodProperties * @mixin DeprecatedPeriodProperties
* *
* @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(TooManyFields)
* @SuppressWarnings(PHPMD.CamelCasePropertyName) * @SuppressWarnings(CamelCasePropertyName)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(CouplingBetweenObjects)
*/ */
class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
{ {
@ -414,16 +414,19 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
$instance = static::createFromArray($params); $instance = static::createFromArray($params);
if ($options !== null) { $instance->options = ($instance instanceof CarbonPeriodImmutable ? static::IMMUTABLE : 0) | $options;
$instance->options = $options;
$instance->handleChangedParameters(); $instance->handleChangedParameters();
}
return $instance; return $instance;
} }
public static function createFromISO8601String(string $iso, ?int $options = null): static
{
return self::createFromIso($iso, $options);
}
/** /**
* Return whether given interval contains non zero value of any time unit. * Return whether the given interval contains non-zero value of any time unit.
*/ */
protected static function intervalHasTime(DateInterval $interval): bool protected static function intervalHasTime(DateInterval $interval): bool
{ {
@ -453,7 +456,7 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
/** /**
* Parse given ISO 8601 string into an array of arguments. * Parse given ISO 8601 string into an array of arguments.
* *
* @SuppressWarnings(PHPMD.ElseExpression) * @SuppressWarnings(ElseExpression)
*/ */
protected static function parseIso8601(string $iso): array protected static function parseIso8601(string $iso): array
{ {
@ -597,7 +600,7 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
/** /**
* CarbonPeriod constructor. * CarbonPeriod constructor.
* *
* @SuppressWarnings(PHPMD.ElseExpression) * @SuppressWarnings(ElseExpression)
* *
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
@ -725,9 +728,10 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
parent::__construct( parent::__construct(
$this->startDate, $this->startDate,
$this->dateInterval, $this->dateInterval,
$this->endDate ?? $this->recurrences ?? 1, $this->endDate ?? max(1, min(2147483639, $this->recurrences ?? 1)),
$this->options, $this->options,
); );
$this->constructed = true; $this->constructed = true;
} }
@ -1115,7 +1119,7 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
/** /**
* Add a filter to the stack. * Add a filter to the stack.
* *
* @SuppressWarnings(PHPMD.UnusedFormalParameter) * @SuppressWarnings(UnusedFormalParameter)
*/ */
public function addFilter(callable|string $callback, ?string $name = null): static public function addFilter(callable|string $callback, ?string $name = null): static
{ {
@ -1132,7 +1136,7 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
/** /**
* Prepend a filter to the stack. * Prepend a filter to the stack.
* *
* @SuppressWarnings(PHPMD.UnusedFormalParameter) * @SuppressWarnings(UnusedFormalParameter)
*/ */
public function prependFilter(callable|string $callback, ?string $name = null): static public function prependFilter(callable|string $callback, ?string $name = null): static
{ {
@ -2245,11 +2249,100 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
public function __debugInfo(): array public function __debugInfo(): array
{ {
$info = $this->baseDebugInfo(); $info = $this->baseDebugInfo();
unset($info['start'], $info['end'], $info['interval'], $info['include_start_date'], $info['include_end_date']); unset(
$info['start'],
$info['end'],
$info['interval'],
$info['include_start_date'],
$info['include_end_date'],
$info['constructed'],
$info["\0*\0constructed"],
);
return $info; return $info;
} }
public function __unserialize(array $data): void
{
try {
$values = array_combine(
array_map(
static fn (string $key): string => preg_replace('/^\0\*\0/', '', $key),
array_keys($data),
),
$data,
);
$this->initializeSerialization($values);
foreach ($values as $key => $value) {
if ($value === null) {
continue;
}
$property = match ($key) {
'tzName' => $this->setTimezone(...),
'options' => $this->setOptions(...),
'recurrences' => $this->setRecurrences(...),
'current' => function (mixed $current): void {
if (!($current instanceof CarbonInterface)) {
$current = $this->resolveCarbon($current);
}
$this->carbonCurrent = $current;
},
'start' => 'startDate',
'interval' => $this->setDateInterval(...),
'end' => 'endDate',
'key' => null,
'include_start_date' => function (bool $included): void {
$this->excludeStartDate(!$included);
},
'include_end_date' => function (bool $included): void {
$this->excludeEndDate(!$included);
},
default => $key,
};
if ($property === null) {
continue;
}
if (\is_callable($property)) {
$property($value);
continue;
}
if ($value instanceof DateTimeInterface && !($value instanceof CarbonInterface)) {
$value = ($value instanceof DateTime)
? Carbon::instance($value)
: CarbonImmutable::instance($value);
}
try {
$this->$property = $value;
} catch (Throwable) {
// Must be ignored for backward-compatibility
}
}
if (\array_key_exists('carbonRecurrences', $values)) {
$this->carbonRecurrences = $values['carbonRecurrences'];
} elseif (((int) ($values['recurrences'] ?? 0)) <= 1 && $this->endDate !== null) {
$this->carbonRecurrences = null;
}
} catch (Throwable $e) {
// @codeCoverageIgnoreStart
if (!method_exists(parent::class, '__unserialize')) {
throw $e;
}
parent::__unserialize($data);
// @codeCoverageIgnoreEnd
}
}
/** /**
* Update properties after removing built-in filters. * Update properties after removing built-in filters.
*/ */
@ -2293,7 +2386,7 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
/** /**
* Recurrences filter callback (limits number of recurrences). * Recurrences filter callback (limits number of recurrences).
* *
* @SuppressWarnings(PHPMD.UnusedFormalParameter) * @SuppressWarnings(UnusedFormalParameter)
*/ */
protected function filterRecurrences(CarbonInterface $current, int $key): bool|callable protected function filterRecurrences(CarbonInterface $current, int $key): bool|callable
{ {
@ -2578,4 +2671,47 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
return $sortedArguments; return $sortedArguments;
} }
private function initializeSerialization(array $values): void
{
$serializationBase = [
'start' => $values['start'] ?? $values['startDate'] ?? null,
'current' => $values['current'] ?? $values['carbonCurrent'] ?? null,
'end' => $values['end'] ?? $values['endDate'] ?? null,
'interval' => $values['interval'] ?? $values['dateInterval'] ?? null,
'recurrences' => max(1, (int) ($values['recurrences'] ?? $values['carbonRecurrences'] ?? 1)),
'include_start_date' => $values['include_start_date'] ?? true,
'include_end_date' => $values['include_end_date'] ?? false,
];
foreach (['start', 'current', 'end'] as $dateProperty) {
if ($serializationBase[$dateProperty] instanceof Carbon) {
$serializationBase[$dateProperty] = $serializationBase[$dateProperty]->toDateTime();
} elseif ($serializationBase[$dateProperty] instanceof CarbonInterface) {
$serializationBase[$dateProperty] = $serializationBase[$dateProperty]->toDateTimeImmutable();
}
}
if ($serializationBase['interval'] instanceof CarbonInterval) {
$serializationBase['interval'] = $serializationBase['interval']->toDateInterval();
}
// @codeCoverageIgnoreStart
if (method_exists(parent::class, '__unserialize')) {
parent::__unserialize($serializationBase);
return;
}
$excludeStart = !($values['include_start_date'] ?? true);
$includeEnd = $values['include_end_date'] ?? true;
parent::__construct(
$serializationBase['start'],
$serializationBase['interval'],
$serializationBase['end'] ?? $serializationBase['recurrences'],
($excludeStart ? self::EXCLUDE_START_DATE : 0) | ($includeEnd && \defined('DatePeriod::INCLUDE_END_DATE') ? self::INCLUDE_END_DATE : 0),
);
// @codeCoverageIgnoreEnd
}
} }

View file

@ -16,6 +16,7 @@ namespace Carbon;
use Carbon\Exceptions\InvalidCastException; use Carbon\Exceptions\InvalidCastException;
use Carbon\Exceptions\InvalidTimeZoneException; use Carbon\Exceptions\InvalidTimeZoneException;
use Carbon\Traits\LocalFactory; use Carbon\Traits\LocalFactory;
use DateTimeImmutable;
use DateTimeInterface; use DateTimeInterface;
use DateTimeZone; use DateTimeZone;
use Exception; use Exception;
@ -129,10 +130,23 @@ class CarbonTimeZone extends DateTimeZone
{ {
$name = $this->getName(); $name = $this->getName();
foreach ($this->listAbbreviations() as $abbreviation => $zones) { $date = new DateTimeImmutable($dst ? 'July 1' : 'January 1', $this);
$timezone = $date->format('T');
$abbreviations = $this->listAbbreviations();
$matchingZones = array_merge($abbreviations[$timezone] ?? [], $abbreviations[strtolower($timezone)] ?? []);
if ($matchingZones !== []) {
foreach ($matchingZones as $zone) {
if ($zone['timezone_id'] === $name && $zone['dst'] == $dst) {
return $timezone;
}
}
}
foreach ($abbreviations as $abbreviation => $zones) {
foreach ($zones as $zone) { foreach ($zones as $zone) {
if ($zone['timezone_id'] === $name && $zone['dst'] == $dst) { if ($zone['timezone_id'] === $name && $zone['dst'] == $dst) {
return $abbreviation; return strtoupper($abbreviation);
} }
} }
} }

View file

@ -22,25 +22,25 @@
*/ */
return [ return [
'year' => ':count il', 'year' => ':count il',
'a_year' => '{1}bir il|]1,Inf[:count il', 'a_year' => '{1}bir il|[-Inf,Inf]:count il',
'y' => ':count il', 'y' => ':count il',
'month' => ':count ay', 'month' => ':count ay',
'a_month' => '{1}bir ay|]1,Inf[:count ay', 'a_month' => '{1}bir ay|[-Inf,Inf]:count ay',
'm' => ':count ay', 'm' => ':count ay',
'week' => ':count həftə', 'week' => ':count həftə',
'a_week' => '{1}bir həftə|]1,Inf[:count həftə', 'a_week' => '{1}bir həftə|[-Inf,Inf]:count həftə',
'w' => ':count h.', 'w' => ':count h.',
'day' => ':count gün', 'day' => ':count gün',
'a_day' => '{1}bir gün|]1,Inf[:count gün', 'a_day' => '{1}bir gün|[-Inf,Inf]:count gün',
'd' => ':count g.', 'd' => ':count g.',
'hour' => ':count saat', 'hour' => ':count saat',
'a_hour' => '{1}bir saat|]1,Inf[:count saat', 'a_hour' => '{1}bir saat|[-Inf,Inf]:count saat',
'h' => ':count s.', 'h' => ':count s.',
'minute' => ':count dəqiqə', 'minute' => ':count dəqiqə',
'a_minute' => '{1}bir dəqiqə|]1,Inf[:count dəqiqə', 'a_minute' => '{1}bir dəqiqə|[-Inf,Inf]:count dəqiqə',
'min' => ':count d.', 'min' => ':count d.',
'second' => ':count saniyə', 'second' => ':count saniyə',
'a_second' => '{1}birneçə saniyə|]1,Inf[:count saniyə', 'a_second' => '{1}birneçə saniyə|[-Inf,Inf]:count saniyə',
's' => ':count san.', 's' => ':count san.',
'ago' => ':time əvvəl', 'ago' => ':time əvvəl',
'from_now' => ':time sonra', 'from_now' => ':time sonra',

View file

@ -15,13 +15,20 @@
* - JD Isaacks * - JD Isaacks
*/ */
return [ return [
'year' => '{1}ལོ་གཅིག|]1,Inf[:count ལོ', 'year' => 'ལོ:count',
'month' => '{1}ཟླ་བ་གཅིག|]1,Inf[:count ཟླ་བ', 'a_year' => '{1}ལོ་གཅིག|[-Inf,Inf]ལོ:count',
'week' => ':count བདུན་ཕྲག', 'month' => 'ཟླ་བ:count',
'day' => '{1}ཉིན་གཅིག|]1,Inf[:count ཉིན་', 'a_month' => '{1}ཟླ་བ་གཅིག|[-Inf,Inf]ཟླ་བ:count',
'hour' => '{1}ཆུ་ཚོད་གཅིག|]1,Inf[:count ཆུ་ཚོད', 'week' => 'གཟའ་འཁོར་:count',
'minute' => '{1}སྐར་མ་གཅིག|]1,Inf[:count སྐར་མ', 'a_week' => 'གཟའ་འཁོར་གཅིག',
'second' => '{1}ལམ་སང|]1,Inf[:count སྐར་ཆ།', 'day' => 'ཉིན:count་',
'a_day' => '{1}ཉིན་གཅིག|[-Inf,Inf]ཉིན:count',
'hour' => 'ཆུ་ཚོད:count',
'a_hour' => '{1}ཆུ་ཚོད་གཅིག|[-Inf,Inf]ཆུ་ཚོད:count',
'minute' => 'སྐར་མ་:count',
'a_minute' => '{1}སྐར་མ་གཅིག|[-Inf,Inf]སྐར་མ་:count',
'second' => 'སྐར་ཆ:count',
'a_second' => '{01}ལམ་སང|[-Inf,Inf]སྐར་ཆ:count',
'ago' => ':time སྔན་ལ', 'ago' => ':time སྔན་ལ',
'from_now' => ':time ལ་', 'from_now' => ':time ལ་',
'diff_yesterday' => 'ཁ་སང', 'diff_yesterday' => 'ཁ་སང',

View file

@ -16,19 +16,26 @@
* - Daniel Monaghan * - Daniel Monaghan
*/ */
return [ return [
'year' => '{1}blwyddyn|]1,Inf[:count flynedd', 'year' => '{1}:count flwyddyn|[-Inf,Inf]:count flynedd',
'a_year' => '{1}blwyddyn|[-Inf,Inf]:count flynedd',
'y' => ':countbl', 'y' => ':countbl',
'month' => '{1}mis|]1,Inf[:count mis', 'month' => ':count mis',
'a_month' => '{1}mis|[-Inf,Inf]:count mis',
'm' => ':countmi', 'm' => ':countmi',
'week' => ':count wythnos', 'week' => ':count wythnos',
'a_week' => '{1}wythnos|[-Inf,Inf]:count wythnos',
'w' => ':countw', 'w' => ':countw',
'day' => '{1}diwrnod|]1,Inf[:count diwrnod', 'day' => ':count diwrnod',
'a_day' => '{1}diwrnod|[-Inf,Inf]:count diwrnod',
'd' => ':countd', 'd' => ':countd',
'hour' => '{1}awr|]1,Inf[:count awr', 'hour' => ':count awr',
'a_hour' => '{1}awr|[-Inf,Inf]:count awr',
'h' => ':counth', 'h' => ':counth',
'minute' => '{1}munud|]1,Inf[:count munud', 'minute' => ':count munud',
'a_minute' => '{1}munud|[-Inf,Inf]:count munud',
'min' => ':countm', 'min' => ':countm',
'second' => '{1}ychydig eiliadau|]1,Inf[:count eiliad', 'second' => ':count eiliad',
'a_second' => '{0,1}ychydig eiliadau|[-Inf,Inf]:count eiliad',
's' => ':counts', 's' => ':counts',
'ago' => ':time yn ôl', 'ago' => ':time yn ôl',
'from_now' => 'mewn :time', 'from_now' => 'mewn :time',

View file

@ -10,18 +10,18 @@
*/ */
$months = [ $months = [
ެނުއަރީ', ަނަވަރީ',
'ފެބްރުއަރީ', 'ފެބުރުވަރީ',
'މާރިޗު', 'މާރިޗު',
ޭޕްރީލު', ެޕްރީލް',
'މޭ', 'މޭ',
'ޖޫން', 'ޖޫން',
'ޖުލައި', 'ޖުލައި',
ޯގަސްޓު', ޮގަސްޓު',
'ސެޕްޓެމްބަރު', 'ސެޕްޓެންބަރު',
'އޮކްޓޯބަރު', 'އޮކްޓޫބަރު',
'ނޮވެމްބަރު', 'ނޮވެންބަރު',
'ޑިސެމްބަރު', 'ޑިސެންބަރު',
]; ];
$weekdays = [ $weekdays = [
@ -38,6 +38,7 @@ $weekdays = [
* Authors: * Authors:
* - Josh Soref * - Josh Soref
* - Jawish Hameed * - Jawish Hameed
* - Saiph Muhammad
*/ */
return [ return [
'year' => ':count '.'އަހަރު', 'year' => ':count '.'އަހަރު',

View file

@ -17,37 +17,37 @@
*/ */
return [ return [
/* /*
* {1}, {0} and ]1,Inf[ are not needed as it's the default for English pluralization. * {1}, {0} and [-Inf,Inf] are not needed as it's the default for English pluralization.
* But as some languages are using en.php as a fallback, it's better to specify it * But as some languages are using en.php as a fallback, it's better to specify it
* explicitly so those languages also fallback to English pluralization when a unit * explicitly so those languages also fallback to English pluralization when a unit
* is missing. * is missing.
*/ */
'year' => '{1}:count year|{0}:count years|]1,Inf[:count years', 'year' => '{1}:count year|{0}:count years|[-Inf,Inf]:count years',
'a_year' => '{1}a year|{0}:count years|]1,Inf[:count years', 'a_year' => '{1}a year|{0}:count years|[-Inf,Inf]:count years',
'y' => '{1}:countyr|{0}:countyrs|]1,Inf[:countyrs', 'y' => '{1}:countyr|{0}:countyrs|[-Inf,Inf]:countyrs',
'month' => '{1}:count month|{0}:count months|]1,Inf[:count months', 'month' => '{1}:count month|{0}:count months|[-Inf,Inf]:count months',
'a_month' => '{1}a month|{0}:count months|]1,Inf[:count months', 'a_month' => '{1}a month|{0}:count months|[-Inf,Inf]:count months',
'm' => '{1}:countmo|{0}:countmos|]1,Inf[:countmos', 'm' => '{1}:countmo|{0}:countmos|[-Inf,Inf]:countmos',
'week' => '{1}:count week|{0}:count weeks|]1,Inf[:count weeks', 'week' => '{1}:count week|{0}:count weeks|[-Inf,Inf]:count weeks',
'a_week' => '{1}a week|{0}:count weeks|]1,Inf[:count weeks', 'a_week' => '{1}a week|{0}:count weeks|[-Inf,Inf]:count weeks',
'w' => ':countw', 'w' => ':countw',
'day' => '{1}:count day|{0}:count days|]1,Inf[:count days', 'day' => '{1}:count day|{0}:count days|[-Inf,Inf]:count days',
'a_day' => '{1}a day|{0}:count days|]1,Inf[:count days', 'a_day' => '{1}a day|{0}:count days|[-Inf,Inf]:count days',
'd' => ':countd', 'd' => ':countd',
'hour' => '{1}:count hour|{0}:count hours|]1,Inf[:count hours', 'hour' => '{1}:count hour|{0}:count hours|[-Inf,Inf]:count hours',
'a_hour' => '{1}an hour|{0}:count hours|]1,Inf[:count hours', 'a_hour' => '{1}an hour|{0}:count hours|[-Inf,Inf]:count hours',
'h' => ':counth', 'h' => ':counth',
'minute' => '{1}:count minute|{0}:count minutes|]1,Inf[:count minutes', 'minute' => '{1}:count minute|{0}:count minutes|[-Inf,Inf]:count minutes',
'a_minute' => '{1}a minute|{0}:count minutes|]1,Inf[:count minutes', 'a_minute' => '{1}a minute|{0}:count minutes|[-Inf,Inf]:count minutes',
'min' => ':countm', 'min' => ':countm',
'second' => '{1}:count second|{0}:count seconds|]1,Inf[:count seconds', 'second' => '{1}:count second|{0}:count seconds|[-Inf,Inf]:count seconds',
'a_second' => '{1}a few seconds|{0}:count seconds|]1,Inf[:count seconds', 'a_second' => '{0,1}a few seconds|[-Inf,Inf]:count seconds',
's' => ':counts', 's' => ':counts',
'millisecond' => '{1}:count millisecond|{0}:count milliseconds|]1,Inf[:count milliseconds', 'millisecond' => '{1}:count millisecond|{0}:count milliseconds|[-Inf,Inf]:count milliseconds',
'a_millisecond' => '{1}a millisecond|{0}:count milliseconds|]1,Inf[:count milliseconds', 'a_millisecond' => '{1}a millisecond|{0}:count milliseconds|[-Inf,Inf]:count milliseconds',
'ms' => ':countms', 'ms' => ':countms',
'microsecond' => '{1}:count microsecond|{0}:count microseconds|]1,Inf[:count microseconds', 'microsecond' => '{1}:count microsecond|{0}:count microseconds|[-Inf,Inf]:count microseconds',
'a_microsecond' => '{1}a microsecond|{0}:count microseconds|]1,Inf[:count microseconds', 'a_microsecond' => '{1}a microsecond|{0}:count microseconds|[-Inf,Inf]:count microseconds',
'µs' => ':countµs', 'µs' => ':countµs',
'ago' => ':time ago', 'ago' => ':time ago',
'from_now' => ':time from now', 'from_now' => ':time from now',
@ -59,7 +59,7 @@ return [
'diff_tomorrow' => 'tomorrow', 'diff_tomorrow' => 'tomorrow',
'diff_before_yesterday' => 'before yesterday', 'diff_before_yesterday' => 'before yesterday',
'diff_after_tomorrow' => 'after tomorrow', 'diff_after_tomorrow' => 'after tomorrow',
'period_recurrences' => '{1}once|{0}:count times|]1,Inf[:count times', 'period_recurrences' => '{1}once|{0}:count times|[-Inf,Inf]:count times',
'period_interval' => 'every :interval', 'period_interval' => 'every :interval',
'period_start_date' => 'from :date', 'period_start_date' => 'from :date',
'period_end_date' => 'to :date', 'period_end_date' => 'to :date',

View file

@ -21,25 +21,25 @@
*/ */
return [ return [
'year' => ':count tahun', 'year' => ':count tahun',
'a_year' => '{1}setahun|]1,Inf[:count tahun', 'a_year' => '{1}setahun|[-Inf,Inf]:count tahun',
'y' => ':countthn', 'y' => ':countthn',
'month' => ':count bulan', 'month' => ':count bulan',
'a_month' => '{1}sebulan|]1,Inf[:count bulan', 'a_month' => '{1}sebulan|[-Inf,Inf]:count bulan',
'm' => ':countbln', 'm' => ':countbln',
'week' => ':count minggu', 'week' => ':count minggu',
'a_week' => '{1}seminggu|]1,Inf[:count minggu', 'a_week' => '{1}seminggu|[-Inf,Inf]:count minggu',
'w' => ':countmgg', 'w' => ':countmgg',
'day' => ':count hari', 'day' => ':count hari',
'a_day' => '{1}sehari|]1,Inf[:count hari', 'a_day' => '{1}sehari|[-Inf,Inf]:count hari',
'd' => ':counthr', 'd' => ':counthr',
'hour' => ':count jam', 'hour' => ':count jam',
'a_hour' => '{1}sejam|]1,Inf[:count jam', 'a_hour' => '{1}sejam|[-Inf,Inf]:count jam',
'h' => ':countj', 'h' => ':countj',
'minute' => ':count menit', 'minute' => ':count menit',
'a_minute' => '{1}semenit|]1,Inf[:count menit', 'a_minute' => '{1}semenit|[-Inf,Inf]:count menit',
'min' => ':countmnt', 'min' => ':countmnt',
'second' => ':count detik', 'second' => ':count detik',
'a_second' => '{1}beberapa detik|]1,Inf[:count detik', 'a_second' => '{1}beberapa detik|[-Inf,Inf]:count detik',
's' => ':countdt', 's' => ':countdt',
'ago' => ':time yang lalu', 'ago' => ':time yang lalu',
'from_now' => ':time dari sekarang', 'from_now' => ':time dari sekarang',

View file

@ -38,7 +38,7 @@ return [
'minute' => ':count分', 'minute' => ':count分',
'min' => ':count分', 'min' => ':count分',
'second' => ':count秒', 'second' => ':count秒',
'a_second' => '{1}数秒|]1,Inf[:count秒', 'a_second' => '{1}数秒|[-Inf,Inf]:count秒',
's' => ':count秒', 's' => ':count秒',
'ago' => ':time前', 'ago' => ':time前',
'from_now' => ':time後', 'from_now' => ':time後',

View file

@ -16,13 +16,20 @@
* - JD Isaacks * - JD Isaacks
*/ */
return [ return [
'year' => '{1}setaun|]1,Inf[:count taun', 'year' => ':count taun',
'month' => '{1}sewulan|]1,Inf[:count wulan', 'a_year' => '{1}setaun|[-Inf,Inf]:count taun',
'week' => '{1}sakminggu|]1,Inf[:count minggu', 'month' => ':count wulan',
'day' => '{1}sedinten|]1,Inf[:count dinten', 'a_month' => '{1}sewulan|[-Inf,Inf]:count wulan',
'hour' => '{1}setunggal jam|]1,Inf[:count jam', 'week' => ':count minggu',
'minute' => '{1}setunggal menit|]1,Inf[:count menit', 'a_week' => '{1}sakminggu|[-Inf,Inf]:count minggu',
'second' => '{1}sawetawis detik|]1,Inf[:count detik', 'day' => ':count dina',
'a_day' => '{1}sedina|[-Inf,Inf]:count dina',
'hour' => ':count jam',
'a_hour' => '{1}setunggal jam|[-Inf,Inf]:count jam',
'minute' => ':count menit',
'a_minute' => '{1}setunggal menit|[-Inf,Inf]:count menit',
'second' => ':count detik',
'a_second' => '{0,1}sawetawis detik|[-Inf,Inf]:count detik',
'ago' => ':time ingkang kepengker', 'ago' => ':time ingkang kepengker',
'from_now' => 'wonten ing :time', 'from_now' => 'wonten ing :time',
'diff_today' => 'Dinten', 'diff_today' => 'Dinten',

View file

@ -30,25 +30,25 @@ use Carbon\CarbonInterface;
return [ return [
'year' => ':count წელი', 'year' => ':count წელი',
'y' => ':count წელი', 'y' => ':count წელი',
'a_year' => '{1}წელი|]1,Inf[:count წელი', 'a_year' => '{1}წელი|[-Inf,Inf]:count წელი',
'month' => ':count თვე', 'month' => ':count თვე',
'm' => ':count თვე', 'm' => ':count თვე',
'a_month' => '{1}თვე|]1,Inf[:count თვე', 'a_month' => '{1}თვე|[-Inf,Inf]:count თვე',
'week' => ':count კვირა', 'week' => ':count კვირა',
'w' => ':count კვირა', 'w' => ':count კვირა',
'a_week' => '{1}კვირა|]1,Inf[:count კვირა', 'a_week' => '{1}კვირა|[-Inf,Inf]:count კვირა',
'day' => ':count დღე', 'day' => ':count დღე',
'd' => ':count დღე', 'd' => ':count დღე',
'a_day' => '{1}დღე|]1,Inf[:count დღე', 'a_day' => '{1}დღე|[-Inf,Inf]:count დღე',
'hour' => ':count საათი', 'hour' => ':count საათი',
'h' => ':count საათი', 'h' => ':count საათი',
'a_hour' => '{1}საათი|]1,Inf[:count საათი', 'a_hour' => '{1}საათი|[-Inf,Inf]:count საათი',
'minute' => ':count წუთი', 'minute' => ':count წუთი',
'min' => ':count წუთი', 'min' => ':count წუთი',
'a_minute' => '{1}წუთი|]1,Inf[:count წუთი', 'a_minute' => '{1}წუთი|[-Inf,Inf]:count წუთი',
'second' => ':count წამი', 'second' => ':count წამი',
's' => ':count წამი', 's' => ':count წამი',
'a_second' => '{1}რამდენიმე წამი|]1,Inf[:count წამი', 'a_second' => '{1}რამდენიმე წამი|[-Inf,Inf]:count წამი',
'ago' => static function ($time) { 'ago' => static function ($time) {
$replacements = [ $replacements = [
// year // year

View file

@ -31,32 +31,32 @@ return array_replace_recursive(require __DIR__.'/en.php', [
'first_day_of_week' => 1, 'first_day_of_week' => 1,
'day_of_first_week_of_year' => 1, 'day_of_first_week_of_year' => 1,
'year' => '{1}ukioq :count|{0}:count ukiut|]1,Inf[ukiut :count', 'year' => '{1}ukioq :count|{0}:count ukiut|[-Inf,Inf]ukiut :count',
'a_year' => '{1}ukioq|{0}:count ukiut|]1,Inf[ukiut :count', 'a_year' => '{1}ukioq|{0}:count ukiut|[-Inf,Inf]ukiut :count',
'y' => '{1}:countyr|{0}:countyrs|]1,Inf[:countyrs', 'y' => '{1}:countyr|{0}:countyrs|[-Inf,Inf]:countyrs',
'month' => '{1}qaammat :count|{0}:count qaammatit|]1,Inf[qaammatit :count', 'month' => '{1}qaammat :count|{0}:count qaammatit|[-Inf,Inf]qaammatit :count',
'a_month' => '{1}qaammat|{0}:count qaammatit|]1,Inf[qaammatit :count', 'a_month' => '{1}qaammat|{0}:count qaammatit|[-Inf,Inf]qaammatit :count',
'm' => '{1}:countmo|{0}:countmos|]1,Inf[:countmos', 'm' => '{1}:countmo|{0}:countmos|[-Inf,Inf]:countmos',
'week' => '{1}:count sap. ak.|{0}:count sap. ak.|]1,Inf[:count sap. ak.', 'week' => '{1}:count sap. ak.|{0}:count sap. ak.|[-Inf,Inf]:count sap. ak.',
'a_week' => '{1}a sap. ak.|{0}:count sap. ak.|]1,Inf[:count sap. ak.', 'a_week' => '{1}a sap. ak.|{0}:count sap. ak.|[-Inf,Inf]:count sap. ak.',
'w' => ':countw', 'w' => ':countw',
'day' => '{1}:count ulloq|{0}:count ullut|]1,Inf[:count ullut', 'day' => '{1}:count ulloq|{0}:count ullut|[-Inf,Inf]:count ullut',
'a_day' => '{1}a ulloq|{0}:count ullut|]1,Inf[:count ullut', 'a_day' => '{1}a ulloq|{0}:count ullut|[-Inf,Inf]:count ullut',
'd' => ':countd', 'd' => ':countd',
'hour' => '{1}:count tiimi|{0}:count tiimit|]1,Inf[:count tiimit', 'hour' => '{1}:count tiimi|{0}:count tiimit|[-Inf,Inf]:count tiimit',
'a_hour' => '{1}tiimi|{0}:count tiimit|]1,Inf[:count tiimit', 'a_hour' => '{1}tiimi|{0}:count tiimit|[-Inf,Inf]:count tiimit',
'h' => ':counth', 'h' => ':counth',
'minute' => '{1}:count minutsi|{0}:count minutsit|]1,Inf[:count minutsit', 'minute' => '{1}:count minutsi|{0}:count minutsit|[-Inf,Inf]:count minutsit',
'a_minute' => '{1}a minutsi|{0}:count minutsit|]1,Inf[:count minutsit', 'a_minute' => '{1}a minutsi|{0}:count minutsit|[-Inf,Inf]:count minutsit',
'min' => ':countm', 'min' => ':countm',
'second' => '{1}:count sikunti|{0}:count sikuntit|]1,Inf[:count sikuntit', 'second' => '{1}:count sikunti|{0}:count sikuntit|[-Inf,Inf]:count sikuntit',
'a_second' => '{1}sikunti|{0}:count sikuntit|]1,Inf[:count sikuntit', 'a_second' => '{1}sikunti|{0}:count sikuntit|[-Inf,Inf]:count sikuntit',
's' => ':counts', 's' => ':counts',
'ago' => ':time matuma siorna', 'ago' => ':time matuma siorna',

View file

@ -17,19 +17,25 @@
* - Sovichet Tep * - Sovichet Tep
*/ */
return [ return [
'year' => '{1}មួយឆ្នាំ|]1,Inf[:count ឆ្នាំ', 'year' => ':count ឆ្នាំ',
'a_year' => '{1}មួយឆ្នាំ|[-Inf,Inf]:count ឆ្នាំ',
'y' => ':count ឆ្នាំ', 'y' => ':count ឆ្នាំ',
'month' => '{1}មួយខែ|]1,Inf[:count ខែ', 'month' => ':count ខែ',
'a_month' => '{1}មួយខែ|[-Inf,Inf]:count ខែ',
'm' => ':count ខែ', 'm' => ':count ខែ',
'week' => ':count សប្ដាហ៍', 'week' => ':count សប្តាហ៍',
'w' => ':count សប្ដាហ៍', 'w' => ':count សប្តាហ៍',
'day' => '{1}មួយថ្ងៃ|]1,Inf[:count ថ្ងៃ', 'day' => ':count ថ្ងៃ',
'a_day' => '{1}មួយថ្ងៃ|[-Inf,Inf]:count ថ្ងៃ',
'd' => ':count ថ្ងៃ', 'd' => ':count ថ្ងៃ',
'hour' => '{1}មួយម៉ោង|]1,Inf[:count ម៉ោង', 'hour' => ':count ម៉ោង',
'a_hour' => '{1}មួយម៉ោង|[-Inf,Inf]:count ម៉ោង',
'h' => ':count ម៉ោង', 'h' => ':count ម៉ោង',
'minute' => '{1}មួយនាទី|]1,Inf[:count នាទី', 'minute' => ':count នាទី',
'a_minute' => '{1}មួយនាទី|[-Inf,Inf]:count នាទី',
'min' => ':count នាទី', 'min' => ':count នាទី',
'second' => '{1}ប៉ុន្មានវិនាទី|]1,Inf[:count វិនាទី', 'second' => ':count វិនាទី',
'a_second' => '{0,1}ប៉ុន្មានវិនាទី|[-Inf,Inf]:count វិនាទី',
's' => ':count វិនាទី', 's' => ':count វិនាទី',
'ago' => ':timeមុន', 'ago' => ':timeមុន',
'from_now' => ':timeទៀត', 'from_now' => ':timeទៀត',

View file

@ -17,13 +17,20 @@
* - rajeevnaikte * - rajeevnaikte
*/ */
return [ return [
'year' => '{1}ಒಂದು ವರ್ಷ|]1,Inf[:count ವರ್ಷ', 'year' => '{1}:count ವರ್ಷ|[-Inf,Inf]:count ವರ್ಷಗಳು',
'month' => '{1}ಒಂದು ತಿಂಗಳು|]1,Inf[:count ತಿಂಗಳು', 'a_year' => '{1}ಒಂದು ವರ್ಷ|[-Inf,Inf]:count ವರ್ಷಗಳು',
'week' => '{1}ಒಂದು ವಾರ|]1,Inf[:count ವಾರಗಳು', 'month' => ':count ತಿಂಗಳು',
'day' => '{1}ಒಂದು ದಿನ|]1,Inf[:count ದಿನ', 'a_month' => '{1}ಒಂದು ತಿಂಗಳು|[-Inf,Inf]:count ತಿಂಗಳು',
'hour' => '{1}ಒಂದು ಗಂಟೆ|]1,Inf[:count ಗಂಟೆ', 'week' => '{1}:count ವಾರ|[-Inf,Inf]:count ವಾರಗಳು',
'minute' => '{1}ಒಂದು ನಿಮಿಷ|]1,Inf[:count ನಿಮಿಷ', 'a_week' => '{1}ಒಂದು ವಾರ|[-Inf,Inf]:count ವಾರಗಳು',
'second' => '{1}ಕೆಲವು ಕ್ಷಣಗಳು|]1,Inf[:count ಸೆಕೆಂಡುಗಳು', 'day' => '{1}:count ದಿನ|[-Inf,Inf]:count ದಿನಗಳು',
'a_day' => '{1}ಒಂದು ದಿನ|[-Inf,Inf]:count ದಿನಗಳು',
'hour' => '{1}:count ಗಂಟೆ|[-Inf,Inf]:count ಗಂಟೆಗಳು',
'a_hour' => '{1}ಒಂದು ಗಂಟೆ|[-Inf,Inf]:count ಗಂಟೆಗಳು',
'minute' => '{1}:count ನಿಮಿಷ|[-Inf,Inf]:count ನಿಮಿಷಗಳು',
'a_minute' => '{1}ಒಂದು ನಿಮಿಷ|[-Inf,Inf]:count ನಿಮಿಷಗಳು',
'second' => '{0,1}:count ಸೆಕೆಂಡ್|[-Inf,Inf]:count ಸೆಕೆಂಡುಗಳು',
'a_second' => '{0,1}ಕೆಲವು ಕ್ಷಣಗಳು|[-Inf,Inf]:count ಸೆಕೆಂಡುಗಳು',
'ago' => ':time ಹಿಂದೆ', 'ago' => ':time ಹಿಂದೆ',
'from_now' => ':time ನಂತರ', 'from_now' => ':time ನಂತರ',
'diff_now' => 'ಈಗ', 'diff_now' => 'ಈಗ',

View file

@ -22,25 +22,25 @@
*/ */
return [ return [
'year' => ':count년', 'year' => ':count년',
'a_year' => '{1}일년|]1,Inf[:count년', 'a_year' => '{1}일년|[-Inf,Inf]:count년',
'y' => ':count년', 'y' => ':count년',
'month' => ':count개월', 'month' => ':count개월',
'a_month' => '{1}한달|]1,Inf[:count개월', 'a_month' => '{1}한달|[-Inf,Inf]:count개월',
'm' => ':count개월', 'm' => ':count개월',
'week' => ':count주', 'week' => ':count주',
'a_week' => '{1}일주일|]1,Inf[:count 주', 'a_week' => '{1}일주일|[-Inf,Inf]:count 주',
'w' => ':count주일', 'w' => ':count주일',
'day' => ':count일', 'day' => ':count일',
'a_day' => '{1}하루|]1,Inf[:count일', 'a_day' => '{1}하루|[-Inf,Inf]:count일',
'd' => ':count일', 'd' => ':count일',
'hour' => ':count시간', 'hour' => ':count시간',
'a_hour' => '{1}한시간|]1,Inf[:count시간', 'a_hour' => '{1}한시간|[-Inf,Inf]:count시간',
'h' => ':count시간', 'h' => ':count시간',
'minute' => ':count분', 'minute' => ':count분',
'a_minute' => '{1}일분|]1,Inf[:count분', 'a_minute' => '{1}일분|[-Inf,Inf]:count분',
'min' => ':count분', 'min' => ':count분',
'second' => ':count초', 'second' => ':count초',
'a_second' => '{1}몇초|]1,Inf[:count초', 'a_second' => '{1}몇초|[-Inf,Inf]:count초',
's' => ':count초', 's' => ':count초',
'ago' => ':time 전', 'ago' => ':time 전',
'from_now' => ':time 후', 'from_now' => ':time 후',

View file

@ -27,7 +27,8 @@ return [
'h' => ':count ຊມ. ', 'h' => ':count ຊມ. ',
'minute' => ':count ນາທີ', 'minute' => ':count ນາທີ',
'min' => ':count ນທ. ', 'min' => ':count ນທ. ',
'second' => '{1}ບໍ່ເທົ່າໃດວິນາທີ|]1,Inf[:count ວິນາທີ', 'second' => ':count ວິນາທີ',
'a_second' => '{0,1}ບໍ່ເທົ່າໃດວິນາທີ|[-Inf,Inf]:count ວິນາທີ',
's' => ':count ວິ. ', 's' => ':count ວິ. ',
'ago' => ':timeຜ່ານມາ', 'ago' => ':timeຜ່ານມາ',
'from_now' => 'ອີກ :time', 'from_now' => 'ອີກ :time',

View file

@ -21,29 +21,29 @@
*/ */
return [ return [
'year' => ':count tahun', 'year' => ':count tahun',
'a_year' => '{1}setahun|]1,Inf[:count tahun', 'a_year' => '{1}setahun|[-Inf,Inf]:count tahun',
'y' => ':count tahun', 'y' => ':count tahun',
'month' => ':count bulan', 'month' => ':count bulan',
'a_month' => '{1}sebulan|]1,Inf[:count bulan', 'a_month' => '{1}sebulan|[-Inf,Inf]:count bulan',
'm' => ':count bulan', 'm' => ':count bulan',
'week' => ':count minggu', 'week' => ':count minggu',
'a_week' => '{1}seminggu|]1,Inf[:count minggu', 'a_week' => '{1}seminggu|[-Inf,Inf]:count minggu',
'w' => ':count minggu', 'w' => ':count minggu',
'day' => ':count hari', 'day' => ':count hari',
'a_day' => '{1}sehari|]1,Inf[:count hari', 'a_day' => '{1}sehari|[-Inf,Inf]:count hari',
'd' => ':count hari', 'd' => ':count hari',
'hour' => ':count jam', 'hour' => ':count jam',
'a_hour' => '{1}sejam|]1,Inf[:count jam', 'a_hour' => '{1}sejam|[-Inf,Inf]:count jam',
'h' => ':count jam', 'h' => ':count jam',
'minute' => ':count minit', 'minute' => ':count minit',
'a_minute' => '{1}seminit|]1,Inf[:count minit', 'a_minute' => '{1}seminit|[-Inf,Inf]:count minit',
'min' => ':count minit', 'min' => ':count minit',
'second' => ':count saat', 'second' => ':count saat',
'a_second' => '{1}beberapa saat|]1,Inf[:count saat', 'a_second' => '{1}beberapa saat|[-Inf,Inf]:count saat',
'millisecond' => ':count milisaat', 'millisecond' => ':count milisaat',
'a_millisecond' => '{1}semilisaat|]1,Inf[:count milliseconds', 'a_millisecond' => '{1}semilisaat|[-Inf,Inf]:count milliseconds',
'microsecond' => ':count mikrodetik', 'microsecond' => ':count mikrodetik',
'a_microsecond' => '{1}semikrodetik|]1,Inf[:count mikrodetik', 'a_microsecond' => '{1}semikrodetik|[-Inf,Inf]:count mikrodetik',
's' => ':count saat', 's' => ':count saat',
'ago' => ':time yang lepas', 'ago' => ':time yang lepas',
'from_now' => ':time dari sekarang', 'from_now' => ':time dari sekarang',

View file

@ -16,19 +16,25 @@
* - Nay Lin Aung * - Nay Lin Aung
*/ */
return [ return [
'year' => '{1}တစ်နှစ်|]1,Inf[:count နှစ်', 'year' => ':count နှစ်',
'a_year' => '{1}တစ်နှစ်|[-Inf,Inf]:count နှစ်',
'y' => ':count နှစ်', 'y' => ':count နှစ်',
'month' => '{1}တစ်လ|]1,Inf[:count လ', 'month' => ':count လ',
'a_month' => '{1}တစ်လ|[-Inf,Inf]:count လ',
'm' => ':count လ', 'm' => ':count လ',
'week' => ':count ပတ်', 'week' => ':count ပတ်',
'w' => ':count ပတ်', 'w' => ':count ပတ်',
'day' => '{1}တစ်ရက်|]1,Inf[:count ရက်', 'day' => ':count ရက်',
'a_day' => '{1}တစ်ရက်|[-Inf,Inf]:count ရက်',
'd' => ':count ရက်', 'd' => ':count ရက်',
'hour' => '{1}တစ်နာရီ|]1,Inf[:count နာရီ', 'hour' => ':count နာရီ',
'a_hour' => '{1}တစ်နာရီ|[-Inf,Inf]:count နာရီ',
'h' => ':count နာရီ', 'h' => ':count နာရီ',
'minute' => '{1}တစ်မိနစ်|]1,Inf[:count မိနစ်', 'minute' => ':count မိနစ်',
'a_minute' => '{1}တစ်မိနစ်|[-Inf,Inf]:count မိနစ်',
'min' => ':count မိနစ်', 'min' => ':count မိနစ်',
'second' => '{1}စက္ကန်.အနည်းငယ်|]1,Inf[:count စက္ကန့်', 'second' => ':count စက္ကန့်',
'a_second' => '{0,1}စက္ကန်.အနည်းငယ်|[-Inf,Inf]:count စက္ကန့်',
's' => ':count စက္ကန့်', 's' => ':count စက္ကန့်',
'ago' => 'လွန်ခဲ့သော :time က', 'ago' => 'လွန်ခဲ့သော :time က',
'from_now' => 'လာမည့် :time မှာ', 'from_now' => 'လာမည့် :time မှာ',

View file

@ -38,16 +38,22 @@ $weekdays = [
* Authors: * Authors:
* - Narain Sagar * - Narain Sagar
* - Sawood Alam * - Sawood Alam
* - Narain Sagar
*/ */
return [ return [
'year' => '{1}'.'هڪ سال'.'|:count '.'سال', 'year' => ':count '.'سال',
'month' => '{1}'.'هڪ مهينو'.'|:count '.'مهينا', 'a_year' => '{1}'.'هڪ سال'.'|:count '.'سال',
'week' => '{1}'.'ھڪ ھفتو'.'|:count '.'هفتا', 'month' => ':count '.'مهينا',
'day' => '{1}'.'هڪ ڏينهن'.'|:count '.'ڏينهن', 'a_month' => '{1}'.'هڪ مهينو'.'|:count '.'مهينا',
'hour' => '{1}'.'هڪ ڪلاڪ'.'|:count '.'ڪلاڪ', 'week' => ':count '.'هفتا',
'minute' => '{1}'.'هڪ منٽ'.'|:count '.'منٽ', 'a_week' => '{1}'.'ھڪ ھفتو'.'|:count '.'هفتا',
'second' => '{1}'.'چند سيڪنڊ'.'|:count '.'سيڪنڊ', 'day' => ':count '.'ڏينهن',
'a_day' => '{1}'.'هڪ ڏينهن'.'|:count '.'ڏينهن',
'hour' => ':count '.'ڪلاڪ',
'a_hour' => '{1}'.'هڪ ڪلاڪ'.'|:count '.'ڪلاڪ',
'minute' => ':count '.'منٽ',
'a_minute' => '{1}'.'هڪ منٽ'.'|:count '.'منٽ',
'second' => ':count '.'سيڪنڊ',
'a_second' => '{1}'.'چند سيڪنڊ'.'|:count '.'سيڪنڊ',
'ago' => ':time اڳ', 'ago' => ':time اڳ',
'from_now' => ':time پوء', 'from_now' => ':time پوء',
'diff_yesterday' => 'ڪالهه', 'diff_yesterday' => 'ڪالهه',

View file

@ -16,7 +16,7 @@
return array_replace_recursive(require __DIR__.'/en.php', [ return array_replace_recursive(require __DIR__.'/en.php', [
'year' => ':count sanad|:count sanadood', 'year' => ':count sanad|:count sanadood',
'a_year' => 'sanad|:count sanadood', 'a_year' => 'sanad|:count sanadood',
'y' => '{1}:countsn|{0}:countsns|]1,Inf[:countsn', 'y' => '{1}:countsn|{0}:countsns|[-Inf,Inf]:countsn',
'month' => ':count bil|:count bilood', 'month' => ':count bil|:count bilood',
'a_month' => 'bil|:count bilood', 'a_month' => 'bil|:count bilood',
'm' => ':countbil', 'm' => ':countbil',

View file

@ -26,7 +26,7 @@ return [
'year' => ':count godina|:count godine|:count godina', 'year' => ':count godina|:count godine|:count godina',
'y' => ':count g.', 'y' => ':count g.',
'month' => ':count mesec|:count meseca|:count meseci', 'month' => ':count mesec|:count meseca|:count meseci',
'm' => ':count mj.', 'm' => ':count mes.',
'week' => ':count nedelja|:count nedelje|:count nedelja', 'week' => ':count nedelja|:count nedelje|:count nedelja',
'w' => ':count ned.', 'w' => ':count ned.',
'day' => ':count dan|:count dana|:count dana', 'day' => ':count dan|:count dana|:count dana',

View file

@ -34,7 +34,7 @@ return [
'minute' => ':count นาที', 'minute' => ':count นาที',
'min' => ':count นาที', 'min' => ':count นาที',
'second' => ':count วินาที', 'second' => ':count วินาที',
'a_second' => '{1}ไม่กี่วินาที|]1,Inf[:count วินาที', 'a_second' => '{1}ไม่กี่วินาที|[-Inf,Inf]:count วินาที',
's' => ':count วินาที', 's' => ':count วินาที',
'ago' => ':timeที่แล้ว', 'ago' => ':timeที่แล้ว',
'from_now' => 'อีก :time', 'from_now' => 'อีก :time',

View file

@ -23,25 +23,25 @@
*/ */
return [ return [
'year' => ':count yıl', 'year' => ':count yıl',
'a_year' => '{1}bir yıl|]1,Inf[:count yıl', 'a_year' => '{1}bir yıl|[-Inf,Inf]:count yıl',
'y' => ':county', 'y' => ':county',
'month' => ':count ay', 'month' => ':count ay',
'a_month' => '{1}bir ay|]1,Inf[:count ay', 'a_month' => '{1}bir ay|[-Inf,Inf]:count ay',
'm' => ':countay', 'm' => ':countay',
'week' => ':count hafta', 'week' => ':count hafta',
'a_week' => '{1}bir hafta|]1,Inf[:count hafta', 'a_week' => '{1}bir hafta|[-Inf,Inf]:count hafta',
'w' => ':counth', 'w' => ':counth',
'day' => ':count gün', 'day' => ':count gün',
'a_day' => '{1}bir gün|]1,Inf[:count gün', 'a_day' => '{1}bir gün|[-Inf,Inf]:count gün',
'd' => ':countg', 'd' => ':countg',
'hour' => ':count saat', 'hour' => ':count saat',
'a_hour' => '{1}bir saat|]1,Inf[:count saat', 'a_hour' => '{1}bir saat|[-Inf,Inf]:count saat',
'h' => ':countsa', 'h' => ':countsa',
'minute' => ':count dakika', 'minute' => ':count dakika',
'a_minute' => '{1}bir dakika|]1,Inf[:count dakika', 'a_minute' => '{1}bir dakika|[-Inf,Inf]:count dakika',
'min' => ':countdk', 'min' => ':countdk',
'second' => ':count saniye', 'second' => ':count saniye',
'a_second' => '{1}birkaç saniye|]1,Inf[:count saniye', 'a_second' => '{1}birkaç saniye|[-Inf,Inf]:count saniye',
's' => ':countsn', 's' => ':countsn',
'ago' => ':time önce', 'ago' => ':time önce',
'from_now' => ':time sonra', 'from_now' => ':time sonra',

View file

@ -46,15 +46,22 @@ $weekdays = [
* - hafezdivandari * - hafezdivandari
* - Hossein Jabbari * - Hossein Jabbari
* - nimamo * - nimamo
* - Usman Zahid
*/ */
return [ return [
'year' => 'ایک سال|:count سال', 'year' => ':count '.'سال',
'month' => 'ایک ماہ|:count ماہ', 'a_year' => 'ایک سال|:count سال',
'week' => ':count ہفتے', 'month' => ':count '.'ماہ',
'day' => 'ایک دن|:count دن', 'a_month' => 'ایک ماہ|:count ماہ',
'hour' => 'ایک گھنٹہ|:count گھنٹے', 'week' => ':count '.'ہفتے',
'minute' => 'ایک منٹ|:count منٹ', 'day' => ':count '.'دن',
'second' => 'چند سیکنڈ|:count سیکنڈ', 'a_day' => 'ایک دن|:count دن',
'hour' => ':count '.'گھنٹے',
'a_hour' => 'ایک گھنٹہ|:count گھنٹے',
'minute' => ':count '.'منٹ',
'a_minute' => 'ایک منٹ|:count منٹ',
'second' => ':count '.'سیکنڈ',
'a_second' => 'چند سیکنڈ|:count سیکنڈ',
'ago' => ':time قبل', 'ago' => ':time قبل',
'from_now' => ':time بعد', 'from_now' => ':time بعد',
'after' => ':time بعد', 'after' => ':time بعد',

View file

@ -37,7 +37,7 @@ return [
'minute' => ':count:optional-space分钟', 'minute' => ':count:optional-space分钟',
'min' => ':count:optional-space分钟', 'min' => ':count:optional-space分钟',
'second' => ':count:optional-space秒', 'second' => ':count:optional-space秒',
'a_second' => '{1}几秒|]1,Inf[:count:optional-space秒', 'a_second' => '{1}几秒|[-Inf,Inf]:count:optional-space秒',
's' => ':count:optional-space秒', 's' => ':count:optional-space秒',
'ago' => ':time前', 'ago' => ':time前',
'from_now' => ':time后', 'from_now' => ':time后',

View file

@ -39,7 +39,7 @@ return [
'minute' => ':count:optional-space分鐘', 'minute' => ':count:optional-space分鐘',
'min' => ':count:optional-space分鐘', 'min' => ':count:optional-space分鐘',
'second' => ':count:optional-space秒', 'second' => ':count:optional-space秒',
'a_second' => '{1}幾秒|]1,Inf[:count:optional-space秒', 'a_second' => '{1}幾秒|[-Inf,Inf]:count:optional-space秒',
's' => ':count:optional-space秒', 's' => ':count:optional-space秒',
'ago' => ':time前', 'ago' => ':time前',
'from_now' => ':time後', 'from_now' => ':time後',

View file

@ -319,7 +319,7 @@ trait Creator
* *
* @return static|null * @return static|null
*/ */
public static function create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $timezone = null): ?self public static function create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $timezone = null): ?static
{ {
$month = self::monthToInt($month); $month = self::monthToInt($month);
@ -405,7 +405,7 @@ trait Creator
* *
* @return static|null * @return static|null
*/ */
public static function createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $timezone = null): ?self public static function createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $timezone = null): ?static
{ {
$month = self::monthToInt($month); $month = self::monthToInt($month);
$fields = static::getRangesByUnit(); $fields = static::getRangesByUnit();
@ -563,7 +563,7 @@ trait Creator
* *
* @return static|null * @return static|null
*/ */
public static function rawCreateFromFormat(string $format, string $time, $timezone = null): ?self public static function rawCreateFromFormat(string $format, string $time, $timezone = null): ?static
{ {
// Work-around for https://bugs.php.net/bug.php?id=80141 // Work-around for https://bugs.php.net/bug.php?id=80141
$format = preg_replace('/(?<!\\\\)((?:\\\\{2})*)c/', '$1Y-m-d\TH:i:sP', $format); $format = preg_replace('/(?<!\\\\)((?:\\\\{2})*)c/', '$1Y-m-d\TH:i:sP', $format);
@ -640,7 +640,7 @@ trait Creator
* @return static|null * @return static|null
*/ */
#[ReturnTypeWillChange] #[ReturnTypeWillChange]
public static function createFromFormat($format, $time, $timezone = null): ?self public static function createFromFormat($format, $time, $timezone = null): ?static
{ {
$function = static::$createFromFormatFunction; $function = static::$createFromFormatFunction;
@ -685,9 +685,9 @@ trait Creator
string $format, string $format,
string $time, string $time,
$timezone = null, $timezone = null,
?string $locale = self::DEFAULT_LOCALE, ?string $locale = CarbonInterface::DEFAULT_LOCALE,
?TranslatorInterface $translator = null ?TranslatorInterface $translator = null
): ?self { ): ?static {
$format = preg_replace_callback('/(?<!\\\\)(\\\\{2})*(LTS|LT|[Ll]{1,4})/', function ($match) use ($locale, $translator) { $format = preg_replace_callback('/(?<!\\\\)(\\\\{2})*(LTS|LT|[Ll]{1,4})/', function ($match) use ($locale, $translator) {
[$code] = $match; [$code] = $match;
@ -825,7 +825,7 @@ trait Creator
* *
* @return static|null * @return static|null
*/ */
public static function createFromLocaleFormat(string $format, string $locale, string $time, $timezone = null): ?self public static function createFromLocaleFormat(string $format, string $locale, string $time, $timezone = null): ?static
{ {
$format = preg_replace_callback( $format = preg_replace_callback(
'/(?:\\\\[a-zA-Z]|[bfkqCEJKQRV]){2,}/', '/(?:\\\\[a-zA-Z]|[bfkqCEJKQRV]){2,}/',
@ -855,7 +855,7 @@ trait Creator
* *
* @return static|null * @return static|null
*/ */
public static function createFromLocaleIsoFormat(string $format, string $locale, string $time, $timezone = null): ?self public static function createFromLocaleIsoFormat(string $format, string $locale, string $time, $timezone = null): ?static
{ {
$time = static::translateTimeString($time, $locale, static::DEFAULT_LOCALE, CarbonInterface::TRANSLATE_MONTHS | CarbonInterface::TRANSLATE_DAYS | CarbonInterface::TRANSLATE_MERIDIEM); $time = static::translateTimeString($time, $locale, static::DEFAULT_LOCALE, CarbonInterface::TRANSLATE_MONTHS | CarbonInterface::TRANSLATE_DAYS | CarbonInterface::TRANSLATE_MERIDIEM);
@ -874,7 +874,7 @@ trait Creator
* *
* @return static|null * @return static|null
*/ */
public static function make($var, DateTimeZone|string|null $timezone = null): ?self public static function make($var, DateTimeZone|string|null $timezone = null): ?static
{ {
if ($var instanceof DateTimeInterface) { if ($var instanceof DateTimeInterface) {
return static::instance($var); return static::instance($var);

View file

@ -1836,6 +1836,8 @@ trait Date
/** /**
* Set the timezone or returns the timezone name if no arguments passed. * Set the timezone or returns the timezone name if no arguments passed.
*
* @return ($value is null ? string : static)
*/ */
public function tz(DateTimeZone|string|int|null $value = null): static|string public function tz(DateTimeZone|string|int|null $value = null): static|string
{ {

View file

@ -327,7 +327,7 @@ trait Localization
} }
/** /**
* Translate a time string from the current locale (`$date->locale()`) to an other. * Translate a time string from the current locale (`$date->locale()`) to another one.
* *
* @param string $timeString time string to translate * @param string $timeString time string to translate
* @param string|null $to output locale of the result returned ("en" by default) * @param string|null $to output locale of the result returned ("en" by default)
@ -659,7 +659,11 @@ trait Localization
{ {
$word = str_replace([':count', '%count', ':time'], '', $word); $word = str_replace([':count', '%count', ':time'], '', $word);
$word = strtr($word, ['' => "'"]); $word = strtr($word, ['' => "'"]);
$word = preg_replace('/({\d+(,(\d+|Inf))?}|[\[\]]\d+(,(\d+|Inf))?[\[\]])/', '', $word); $word = preg_replace(
'/\{(?:-?\d+(?:\.\d+)?|-?Inf)(?:,(?:-?\d+|-?Inf))?}|[\[\]](?:-?\d+(?:\.\d+)?|-?Inf)(?:,(?:-?\d+|-?Inf))?[\[\]]/',
'',
$word,
);
return trim($word); return trim($word);
} }
@ -688,7 +692,7 @@ trait Localization
return $key === 'to' return $key === 'to'
? self::cleanWordFromTranslationString(end($parts)) ? self::cleanWordFromTranslationString(end($parts))
: '(?:'.implode('|', array_map([static::class, 'cleanWordFromTranslationString'], $parts)).')'; : '(?:'.implode('|', array_map(static::cleanWordFromTranslationString(...), $parts)).')';
}, $keys); }, $keys);
} }

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