PluginProbe
Depicter — Popup & Slider Builder / trunk
Depicter — Popup & Slider Builder vtrunk
4.8.1 trunk 1.0.0 1.1.0 1.1.2 1.1.4 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.5 1.3.8 1.5.0 1.5.1 1.5.2 1.5.5 1.6.0 1.6.1 1.6.2 1.7.0 All 76 releases
depicter / modules / GuzzleHttp / Psr7 / MultipartStream.php

MultipartStream.php in Depicter — Popup & Slider Builder trunk, at modules/GuzzleHttp/Psr7/MultipartStream.php

166 lines 5.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Depicter\GuzzleHttp\Psr7;
6
7 use Depicter\Psr\Http\Message\StreamInterface;
8
9 /**
10 * Stream that when read returns bytes for a streaming multipart or
11 * multipart/form-data stream.
12 */
13 final class MultipartStream implements StreamInterface
14 {
15 use StreamDecoratorTrait;
16
17 /** @var string */
18 private $boundary;
19
20 /** @var StreamInterface */
21 private $stream;
22
23 /**
24 * @param array $elements Array of associative arrays, each containing a
25 * required "name" key mapping to the form field,
26 * name, a required "contents" key mapping to a
27 * StreamInterface/resource/string, an optional
28 * "headers" associative array of custom headers,
29 * and an optional "filename" key mapping to a
30 * string to send as the filename in the part.
31 * @param string $boundary You can optionally provide a specific boundary
32 *
33 * @throws \InvalidArgumentException
34 */
35 public function __construct(array $elements = [], string $boundary = null)
36 {
37 $this->boundary = $boundary ?: bin2hex(random_bytes(20));
38 $this->stream = $this->createStream($elements);
39 }
40
41 public function getBoundary(): string
42 {
43 return $this->boundary;
44 }
45
46 public function isWritable(): bool
47 {
48 return false;
49 }
50
51 /**
52 * Get the headers needed before transferring the content of a POST file
53 *
54 * @param string[] $headers
55 */
56 private function getHeaders(array $headers): string
57 {
58 $str = '';
59 foreach ($headers as $key => $value) {
60 $str .= "{$key}: {$value}\r\n";
61 }
62
63 return "--{$this->boundary}\r\n".trim($str)."\r\n\r\n";
64 }
65
66 /**
67 * Create the aggregate stream that will be used to upload the POST data
68 */
69 protected function createStream(array $elements = []): StreamInterface
70 {
71 $stream = new AppendStream();
72
73 foreach ($elements as $element) {
74 if (!is_array($element)) {
75 throw new \UnexpectedValueException('An array is expected');
76 }
77 $this->addElement($stream, $element);
78 }
79
80 // Add the trailing boundary with CRLF
81 $stream->addStream(Utils::streamFor("--{$this->boundary}--\r\n"));
82
83 return $stream;
84 }
85
86 private function addElement(AppendStream $stream, array $element): void
87 {
88 foreach (['contents', 'name'] as $key) {
89 if (!array_key_exists($key, $element)) {
90 throw new \InvalidArgumentException("A '{$key}' key is required");
91 }
92 }
93
94 $element['contents'] = Utils::streamFor($element['contents']);
95
96 if (empty($element['filename'])) {
97 $uri = $element['contents']->getMetadata('uri');
98 if ($uri && \is_string($uri) && \substr($uri, 0, 6) !== 'php://' && \substr($uri, 0, 7) !== 'data://') {
99 $element['filename'] = $uri;
100 }
101 }
102
103 [$body, $headers] = $this->createElement(
104 $element['name'],
105 $element['contents'],
106 $element['filename'] ?? null,
107 $element['headers'] ?? []
108 );
109
110 $stream->addStream(Utils::streamFor($this->getHeaders($headers)));
111 $stream->addStream($body);
112 $stream->addStream(Utils::streamFor("\r\n"));
113 }
114
115 /**
116 * @param string[] $headers
117 *
118 * @return array{0: StreamInterface, 1: string[]}
119 */
120 private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers): array
121 {
122 // Set a default content-disposition header if one was no provided
123 $disposition = self::getHeader($headers, 'content-disposition');
124 if (!$disposition) {
125 $headers['Content-Disposition'] = ($filename === '0' || $filename)
126 ? sprintf(
127 'form-data; name="%s"; filename="%s"',
128 $name,
129 basename($filename)
130 )
131 : "form-data; name=\"{$name}\"";
132 }
133
134 // Set a default content-length header if one was no provided
135 $length = self::getHeader($headers, 'content-length');
136 if (!$length) {
137 if ($length = $stream->getSize()) {
138 $headers['Content-Length'] = (string) $length;
139 }
140 }
141
142 // Set a default Content-Type if one was not supplied
143 $type = self::getHeader($headers, 'content-type');
144 if (!$type && ($filename === '0' || $filename)) {
145 $headers['Content-Type'] = MimeType::fromFilename($filename) ?? 'application/octet-stream';
146 }
147
148 return [$stream, $headers];
149 }
150
151 /**
152 * @param string[] $headers
153 */
154 private static function getHeader(array $headers, string $key): ?string
155 {
156 $lowercaseHeader = strtolower($key);
157 foreach ($headers as $k => $v) {
158 if (strtolower((string) $k) === $lowercaseHeader) {
159 return $v;
160 }
161 }
162
163 return null;
164 }
165 }
166