PluginProbe
WP-Stateless – Google Cloud Storage / 2.2.0
WP-Stateless – Google Cloud Storage v2.2.0
4.4.3 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.3.0 2.3.1 2.3.2 3.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.2.0 3.2.1 3.2.2 All 62 releases
wp-stateless / lib / Google / src / Google / Http / MediaFileUpload.php

MediaFileUpload.php in WP-Stateless – Google Cloud Storage 2.2.0, at lib/Google/src/Google/Http/MediaFileUpload.php

353 lines 9.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Copyright 2012 Google Inc.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 namespace wpCloud\StatelessMedia\Google_Client;
18
19 use GuzzleHttp\Psr7;
20 use GuzzleHttp\Psr7\Request;
21 use GuzzleHttp\Psr7\Uri;
22 use Psr\Http\Message\RequestInterface;
23
24 /**
25 * Manage large file uploads, which may be media but can be any type
26 * of sizable data.
27 */
28 class Google_Http_MediaFileUpload
29 {
30 const UPLOAD_MEDIA_TYPE = 'media';
31 const UPLOAD_MULTIPART_TYPE = 'multipart';
32 const UPLOAD_RESUMABLE_TYPE = 'resumable';
33
34 /** @var string $mimeType */
35 private $mimeType;
36
37 /** @var string $data */
38 private $data;
39
40 /** @var bool $resumable */
41 private $resumable;
42
43 /** @var int $chunkSize */
44 private $chunkSize;
45
46 /** @var int $size */
47 private $size;
48
49 /** @var string $resumeUri */
50 private $resumeUri;
51
52 /** @var int $progress */
53 private $progress;
54
55 /** @var Google_Client */
56 private $client;
57
58 /** @var Psr\Http\Message\RequestInterface */
59 private $request;
60
61 /** @var string */
62 private $boundary;
63
64 /**
65 * Result code from last HTTP call
66 * @var int
67 */
68 private $httpResultCode;
69
70 /**
71 * @param $mimeType string
72 * @param $data string The bytes you want to upload.
73 * @param $resumable bool
74 * @param bool $chunkSize File will be uploaded in chunks of this many bytes.
75 * only used if resumable=True
76 */
77 public function __construct(
78 Google_Client $client,
79 RequestInterface $request,
80 $mimeType,
81 $data,
82 $resumable = false,
83 $chunkSize = false
84 ) {
85 $this->client = $client;
86 $this->request = $request;
87 $this->mimeType = $mimeType;
88 $this->data = $data;
89 $this->resumable = $resumable;
90 $this->chunkSize = $chunkSize;
91 $this->progress = 0;
92
93 $this->process();
94 }
95
96 /**
97 * Set the size of the file that is being uploaded.
98 * @param $size - int file size in bytes
99 */
100 public function setFileSize($size)
101 {
102 $this->size = $size;
103 }
104
105 /**
106 * Return the progress on the upload
107 * @return int progress in bytes uploaded.
108 */
109 public function getProgress()
110 {
111 return $this->progress;
112 }
113
114 /**
115 * Send the next part of the file to upload.
116 * @param [$chunk] the next set of bytes to send. If false will used $data passed
117 * at construct time.
118 */
119 public function nextChunk($chunk = false)
120 {
121 $resumeUri = $this->getResumeUri();
122
123 if (false == $chunk) {
124 $chunk = substr($this->data, $this->progress, $this->chunkSize);
125 }
126
127 $lastBytePos = $this->progress + strlen($chunk) - 1;
128 $headers = array(
129 'content-range' => "bytes $this->progress-$lastBytePos/$this->size",
130 'content-length' => strlen($chunk),
131 'expect' => '',
132 );
133
134 $request = new Request(
135 'PUT',
136 $resumeUri,
137 $headers,
138 Psr7\stream_for($chunk)
139 );
140
141 return $this->makePutRequest($request);
142 }
143
144 /**
145 * Return the HTTP result code from the last call made.
146 * @return int code
147 */
148 public function getHttpResultCode()
149 {
150 return $this->httpResultCode;
151 }
152
153 /**
154 * Sends a PUT-Request to google drive and parses the response,
155 * setting the appropiate variables from the response()
156 *
157 * @param Google_Http_Request $httpRequest the Reuqest which will be send
158 *
159 * @return false|mixed false when the upload is unfinished or the decoded http response
160 *
161 */
162 private function makePutRequest(RequestInterface $request)
163 {
164 $response = $this->client->execute($request);
165 $this->httpResultCode = $response->getStatusCode();
166
167 if (308 == $this->httpResultCode) {
168 // Track the amount uploaded.
169 $range = $response->getHeaderLine('range');
170 if ($range) {
171 $range_array = explode('-', $range);
172 $this->progress = $range_array[1] + 1;
173 }
174
175 // Allow for changing upload URLs.
176 $location = $response->getHeaderLine('location');
177 if ($location) {
178 $this->resumeUri = $location;
179 }
180
181 // No problems, but upload not complete.
182 return false;
183 }
184
185 return Google_Http_REST::decodeHttpResponse($response, $this->request);
186 }
187
188 /**
189 * Resume a previously unfinished upload
190 * @param $resumeUri the resume-URI of the unfinished, resumable upload.
191 */
192 public function resume($resumeUri)
193 {
194 $this->resumeUri = $resumeUri;
195 $headers = array(
196 'content-range' => "bytes */$this->size",
197 'content-length' => 0,
198 );
199 $httpRequest = new Request(
200 'PUT',
201 $this->resumeUri,
202 $headers
203 );
204
205 return $this->makePutRequest($httpRequest);
206 }
207
208 /**
209 * @return Psr\Http\Message\RequestInterface $request
210 * @visible for testing
211 */
212 private function process()
213 {
214 $this->transformToUploadUrl();
215 $request = $this->request;
216
217 $postBody = '';
218 $contentType = false;
219
220 $meta = (string) $request->getBody();
221 $meta = is_string($meta) ? json_decode($meta, true) : $meta;
222
223 $uploadType = $this->getUploadType($meta);
224 $request = $request->withUri(
225 Uri::withQueryValue($request->getUri(), 'uploadType', $uploadType)
226 );
227
228 $mimeType = $this->mimeType ?: $request->getHeaderLine('content-type');
229
230 if (self::UPLOAD_RESUMABLE_TYPE == $uploadType) {
231 $contentType = $mimeType;
232 $postBody = is_string($meta) ? $meta : json_encode($meta);
233 } else if (self::UPLOAD_MEDIA_TYPE == $uploadType) {
234 $contentType = $mimeType;
235 $postBody = $this->data;
236 } else if (self::UPLOAD_MULTIPART_TYPE == $uploadType) {
237 // This is a multipart/related upload.
238 $boundary = $this->boundary ?: mt_rand();
239 $boundary = str_replace('"', '', $boundary);
240 $contentType = 'multipart/related; boundary=' . $boundary;
241 $related = "--$boundary\r\n";
242 $related .= "Content-Type: application/json; charset=UTF-8\r\n";
243 $related .= "\r\n" . json_encode($meta) . "\r\n";
244 $related .= "--$boundary\r\n";
245 $related .= "Content-Type: $mimeType\r\n";
246 $related .= "Content-Transfer-Encoding: base64\r\n";
247 $related .= "\r\n" . base64_encode($this->data) . "\r\n";
248 $related .= "--$boundary--";
249 $postBody = $related;
250 }
251
252 $request = $request->withBody(Psr7\stream_for($postBody));
253
254 if (isset($contentType) && $contentType) {
255 $request = $request->withHeader('content-type', $contentType);
256 }
257
258 return $this->request = $request;
259 }
260
261 /**
262 * Valid upload types:
263 * - resumable (UPLOAD_RESUMABLE_TYPE)
264 * - media (UPLOAD_MEDIA_TYPE)
265 * - multipart (UPLOAD_MULTIPART_TYPE)
266 * @param $meta
267 * @return string
268 * @visible for testing
269 */
270 public function getUploadType($meta)
271 {
272 if ($this->resumable) {
273 return self::UPLOAD_RESUMABLE_TYPE;
274 }
275
276 if (false == $meta && $this->data) {
277 return self::UPLOAD_MEDIA_TYPE;
278 }
279
280 return self::UPLOAD_MULTIPART_TYPE;
281 }
282
283 public function getResumeUri()
284 {
285 if (null === $this->resumeUri) {
286 $this->resumeUri = $this->fetchResumeUri();
287 }
288
289 return $this->resumeUri;
290 }
291
292 private function fetchResumeUri()
293 {
294 $body = $this->request->getBody();
295 if ($body) {
296 $headers = array(
297 'content-type' => 'application/json; charset=UTF-8',
298 'content-length' => $body->getSize(),
299 'x-upload-content-type' => $this->mimeType,
300 'x-upload-content-length' => $this->size,
301 'expect' => '',
302 );
303 foreach ($headers as $key => $value) {
304 $this->request = $this->request->withHeader($key, $value);
305 }
306 }
307
308 $response = $this->client->execute($this->request, false);
309 $location = $response->getHeaderLine('location');
310 $code = $response->getStatusCode();
311
312 if (200 == $code && true == $location) {
313 return $location;
314 }
315
316 $message = $code;
317 $body = json_decode((string) $this->request->getBody(), true);
318 if (isset($body['error']['errors'])) {
319 $message .= ': ';
320 foreach ($body['error']['errors'] as $error) {
321 $message .= "{$error[domain]}, {$error[message]};";
322 }
323 $message = rtrim($message, ';');
324 }
325
326 $error = "Failed to start the resumable upload (HTTP {$message})";
327 $this->client->getLogger()->error($error);
328
329 throw new Google_Exception($error);
330 }
331
332 private function transformToUploadUrl()
333 {
334 $parts = parse_url((string) $this->request->getUri());
335 if (!isset($parts['path'])) {
336 $parts['path'] = '';
337 }
338 $parts['path'] = '/upload' . $parts['path'];
339 $uri = Uri::fromParts($parts);
340 $this->request = $this->request->withUri($uri);
341 }
342
343 public function setChunkSize($chunkSize)
344 {
345 $this->chunkSize = $chunkSize;
346 }
347
348 public function getRequest()
349 {
350 return $this->request;
351 }
352 }
353