# media-cloud-sync/1.4.2/includes/sdk/s3/Aws/Api/Serializer/RestSerializer.php

Media Cloud Sync, version 1.4.2. 386 lines.

- Page: https://pluginprobe.com/plugins/media-cloud-sync/1.4.2/code/includes/sdk/s3/Aws/Api/Serializer/RestSerializer.php
- Raw: https://pluginprobe.com/plugins/media-cloud-sync/1.4.2/raw/includes/sdk/s3/Aws/Api/Serializer/RestSerializer.php
- Modified: 2026-05-10T10:14:36+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/media-cloud-sync/1.4.2/code/includes/sdk/s3/Aws/Api/Serializer/RestSerializer.php#L10-L20`.

```php
<?php

namespace Dudlewebs\WPMCS\s3\Aws\Api\Serializer;

use Dudlewebs\WPMCS\s3\Aws\Api\ListShape;
use Dudlewebs\WPMCS\s3\Aws\Api\MapShape;
use Dudlewebs\WPMCS\s3\Aws\Api\Service;
use Dudlewebs\WPMCS\s3\Aws\Api\Operation;
use Dudlewebs\WPMCS\s3\Aws\Api\Shape;
use Dudlewebs\WPMCS\s3\Aws\Api\StructureShape;
use Dudlewebs\WPMCS\s3\Aws\Api\TimestampShape;
use Dudlewebs\WPMCS\s3\Aws\CommandInterface;
use Dudlewebs\WPMCS\s3\Aws\EndpointV2\EndpointV2SerializerTrait;
use Dudlewebs\WPMCS\s3\Aws\EndpointV2\Ruleset\RulesetEndpoint;
use DateTimeInterface;
use Dudlewebs\WPMCS\s3\GuzzleHttp\Psr7;
use Dudlewebs\WPMCS\s3\GuzzleHttp\Psr7\Request;
use Dudlewebs\WPMCS\s3\GuzzleHttp\Psr7\Uri;
use Dudlewebs\WPMCS\s3\GuzzleHttp\Psr7\UriResolver;
use Dudlewebs\WPMCS\s3\Psr\Http\Message\RequestInterface;
use Dudlewebs\WPMCS\s3\Psr\Http\Message\UriInterface;
/**
 * Serializes HTTP locations like header, uri, payload, etc...
 * @internal
 */
abstract class RestSerializer
{
    private const TEMPLATE_STRING_REGEX = '/\\{([^\\}]+)\\}/';
    private static array $excludeContentType = ['s3' => \true, 'glacier' => \true];
    /** @var Service */
    private Service $api;
    /** @var Uri */
    private $endpoint;
    /** @var bool */
    private $isUseEndpointV2;
    use EndpointV2SerializerTrait;
    /**
     * @param Service $api Service API description
     * @param string $endpoint Endpoint to connect to
     */
    public function __construct(Service $api, $endpoint)
    {
        $this->api = $api;
        $this->endpoint = Psr7\Utils::uriFor($endpoint);
    }
    /**
     * @param CommandInterface $command Command to serialize into a request.
     * @param mixed|null $endpoint
     * @return RequestInterface
     */
    public function __invoke(CommandInterface $command, mixed $endpoint = null)
    {
        $operation = $this->api->getOperation($command->getName());
        $commandArgs = $command->toArray();
        $opts = $this->serialize($operation, $commandArgs);
        $headers = $opts['headers'] ?? [];
        if ($endpoint instanceof RulesetEndpoint) {
            $this->isUseEndpointV2 = \true;
            $this->setEndpointV2RequestOptions($endpoint, $headers);
        }
        $uri = $this->buildEndpoint($operation, $commandArgs, $opts);
        return new Request($operation['http']['method'], $uri, $headers, $opts['body'] ?? null);
    }
    /**
     * Modifies a hash of request options for a payload body.
     *
     * @param StructureShape $member Member to serialize
     * @param array $value Value to serialize
     * @param array $opts Request options to modify.
     */
    protected abstract function payload(StructureShape $member, array $value, array &$opts);
    private function serialize(Operation $operation, array $args)
    {
        $opts = [];
        $input = $operation->getInput();
        // Apply the payload trait if present
        if ($payload = $input['payload']) {
            $this->applyPayload($input, $payload, $args, $opts);
        }
        foreach ($args as $name => $value) {
            if ($input->hasMember($name)) {
                $member = $input->getMember($name);
                $location = $member['location'];
                if (!$payload && !$location) {
                    $bodyMembers[$name] = $value;
                } elseif ($location === 'header') {
                    $this->applyHeader($name, $member, $value, $opts);
                } elseif ($location === 'querystring') {
                    $this->applyQuery($name, $member, $value, $opts);
                } elseif ($location === 'headers') {
                    $this->applyHeaderMap($name, $member, $value, $opts);
                }
            }
        }
        if (isset($bodyMembers)) {
            $this->payload($input, $bodyMembers, $opts);
        } else {
            if (!isset($opts['body']) && $this->hasPayloadParam($input, $payload)) {
                $this->payload($input, [], $opts);
            }
        }
        return $opts;
    }
    private function applyPayload(StructureShape $input, $name, array $args, array &$opts)
    {
        if (!isset($args[$name])) {
            return;
        }
        $m = $input->getMember($name);
        $type = $m->getType();
        if ($m['streaming'] || ($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
            // always just a stream of data.
            $opts['body'] = Psr7\Utils::streamFor($body);
            return;
        }
        $this->payload($m, $args[$name], $opts);
    }
    private function applyHeader($name, Shape $member, $value, array &$opts)
    {
        // Handle lists by recursively applying header logic to each element
        if ($member instanceof ListShape) {
            $listMember = $member->getMember();
            $headerValues = [];
            foreach ($value as $listValue) {
                $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']) {
            $value = \json_encode($value);
            if (empty($value) && \JSON_ERROR_NONE !== \json_last_error()) {
                throw new \InvalidArgumentException('Unable to encode the provided value' . ' with \'json_encode\'. ' . \json_last_error_msg());
            }
            $value = \base64_encode($value);
        }
        $opts['headers'][$member['locationName'] ?: $name] = $value;
    }
    /**
     * Note: This is currently only present in the Amazon S3 model.
     */
    private function applyHeaderMap($name, Shape $member, array $value, array &$opts)
    {
        $prefix = $member['locationName'];
        foreach ($value as $k => $v) {
            $opts['headers'][$prefix . $k] = $v;
        }
    }
    private function applyQuery($name, Shape $member, $value, array &$opts)
    {
        if ($member instanceof MapShape) {
            $opts['query'] = isset($opts['query']) && \is_array($opts['query']) ? $opts['query'] + $value : $value;
        } elseif ($member instanceof ListShape) {
            $listMember = $member->getMember();
            $paramName = $member['locationName'] ?: $name;
            foreach ($value as $listValue) {
                // Recursively call applyQuery for each list element
                $tempOpts = ['query' => []];
                $this->applyQuery('temp', $listMember, $listValue, $tempOpts);
                $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;
        }
    }
    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
    {
        $varDefinitions = $this->getVarDefinitions($operation, $args);
        return \preg_replace_callback(self::TEMPLATE_STRING_REGEX, static function (array $matches) use($varDefinitions) {
            $isGreedy = \str_ends_with($matches[1], '+');
            $varName = $isGreedy ? \substr($matches[1], 0, -1) : $matches[1];
            if (!isset($varDefinitions[$varName])) {
                return '';
            }
            $value = $varDefinitions[$varName];
            if ($isGreedy) {
                return \str_replace('%2F', '/', \rawurlencode($value));
            }
            return \rawurlencode($value);
        }, $operation['http']['requestUri']);
    }
    /**
     * Checks for path-like key names. If detected, traditional
     * URI resolution is bypassed.
     *
     * @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;
                }
            }
        }
        // Keys starting with slash
        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, '/?')) {
                $query = \substr($relativeUri, 2);
                // Remove '/?'
                return $this->endpoint->withQuery($query);
            }
            // 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);
            }
        }
        return UriResolver::resolve($this->endpoint, new Uri($relativeUri));
    }
    /**
     * @param StructureShape $input
     * @param $payload
     *
     * @return bool
     */
    private function hasPayloadParam(StructureShape $input, $payload)
    {
        if ($payload) {
            $potentiallyEmptyTypes = ['blob', 'string'];
            if ($this->api->getProtocol() === 'rest-xml') {
                $potentiallyEmptyTypes[] = 'structure';
            }
            $payloadMember = $input->getMember($payload);
            //unions may also be empty/unset
            if (!empty($payloadMember['union']) || \in_array($payloadMember['type'], $potentiallyEmptyTypes)) {
                return \false;
            }
        }
        foreach ($input->getMembers() as $member) {
            if (!isset($member['location'])) {
                return \true;
            }
        }
        return \false;
    }
    /**
     * @param $query
     * @param $relativeUri
     *
     * @return string
     */
    private function appendQuery($query, $relativeUri) : string
    {
        $append = Psr7\Query::build($query);
        return $relativeUri . (\str_contains($relativeUri, '?') ? "&{$append}" : "?{$append}");
    }
    /**
     * @param CommandInterface $command
     * @param array $args
     *
     * @return array
     */
    private function getVarDefinitions(Operation $operation, array $args) : array
    {
        $varDefinitions = [];
        foreach ($operation->getInput()->getMembers() as $name => $member) {
            if ($member['location'] === 'uri') {
                $value = $args[$name] ?? null;
                if (!\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;
                    }
                }
                $varDefinitions[$member['locationName'] ?: $name] = $value;
            }
        }
        return $varDefinitions;
    }
    /**
     * @param DateTimeInterface|string|int $value
     * @param string $timestampFormat
     *
     * @return string
     */
    private function formatTimestamp(DateTimeInterface|string|int $value, string $timestampFormat) : string
    {
        return TimestampShape::format($value, $timestampFormat);
    }
    /**
     * @param $value
     *
     * @return string
     */
    private function formatBoolean($value) : string
    {
        return $value ? 'true' : 'false';
    }
}

```
