PluginProbe
WP-Stateless – Google Cloud Storage / 3.0.4
WP-Stateless – Google Cloud Storage v3.0.4
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 / classes / class-gs-client.php

class-gs-client.php in WP-Stateless – Google Cloud Storage 3.0.4, at lib/classes/class-gs-client.php

501 lines 16.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * GS API Client
5 *
6 * @since 0.2.0
7 * @author peshkov@UD
8 */
9
10 namespace wpCloud\StatelessMedia {
11
12 use wpCloud\StatelessMedia\Google_Client;
13 use Google_Service_Storage;
14 use WP_Error;
15 use Exception;
16 use Google_Service_Storage_ObjectAccessControl;
17 use Google_Auth_AssertionCredentials;
18
19 if (!class_exists('wpCloud\StatelessMedia\GS_Client')) {
20
21 final class GS_Client {
22
23 /**
24 * Singleton object
25 *
26 * @var \wpCloud\StatelessMedia\GS_Client
27 */
28 private static $instance;
29
30 /**
31 * Google Client manager
32 *
33 * @var \wpCloud\StatelessMedia\Google_Client\Google_Client $client
34 */
35 public $client;
36
37 /**
38 * Google Storage Service manager
39 *
40 * @var \Google_Service_Storage $service
41 */
42 public $service;
43
44 /**
45 * Google Storage Bucket
46 *
47 * @var
48 */
49 private $bucket;
50
51 /**
52 * @var
53 */
54 private $temp_objects = array();
55
56 /**
57 * Constructor.
58 * Must not be called directly.
59 *
60 * @param $args
61 * @author peshkov@UD
62 */
63 protected function __construct($args) {
64 global $current_blog;
65 $this->bucket = $args['bucket'];
66 $this->key_json = json_decode($args['key_json'], 1);
67
68 // May be Loading Google SDK....
69 if (!class_exists('\wpCloud\StatelessMedia\Google_Client\Google_Client')) {
70 include_once(ud_get_stateless_media()->path('lib/Google/vendor/autoload.php', 'dir'));
71 }
72
73 /* Initialize our client */
74 $this->client = new \wpCloud\StatelessMedia\Google_Client\Google_Client();
75
76 // We're supporting Google SDK 1.X version since
77 // The plugins which also are using Google SDK may have its old version
78 // what may cause conflicts
79 //
80 if (version_compare($this->client->getLibraryVersion(), '2.0', '<')) {
81 // We should set the warning about potential issue
82 // If Google SDK has different version with already included
83 $this->_setWarning();
84
85 $wp_upload_dir = wp_upload_dir();
86 $dir = $wp_upload_dir['path'];
87 $filename = md5(wp_generate_password()) . '.tmp';
88 $path = wp_normalize_path($dir . '/' . $filename);
89 @file_put_contents($path, json_encode($this->key_json));
90 $cred = $this->client->loadServiceAccountJson($path, ['https://www.googleapis.com/auth/devstorage.full_control']);
91 $this->client->setAssertionCredentials($cred);
92 if ($this->client->getAuth()->isAccessTokenExpired()) {
93 $this->client->getAuth()->refreshTokenWithAssertion($cred);
94 }
95 @unlink($path);
96 } else {
97 // May be delete warning transient if it was set
98 $this->_deleteWarning();
99 $this->client->setAuthConfig($this->key_json);
100 }
101
102 if (isset($current_blog) && isset($current_blog->domain)) {
103 $this->client->setApplicationName($current_blog->domain);
104 } else {
105 $this->client->setApplicationName(urlencode(str_replace(array('http://', 'https://'), '', get_bloginfo('url'))));
106 }
107
108 $this->client->setScopes(['https://www.googleapis.com/auth/devstorage.full_control']);
109
110 // May be Loading Google SDK. Because some bad plugins may load their Google SDK with not included Google_Service_Storage.
111 if (!class_exists('Google_Service_Storage')) {
112 include_once(ud_get_stateless_media()->path('lib/Google/vendor/autoload.php', 'dir'));
113 }
114
115 /* Now, Initialize our Google Storage Service */
116 $this->service = new \wpCloud\StatelessMedia\Google_Client\Google_Service_Storage($this->client);
117 }
118
119 /**
120 * Wrapper for listObjects()
121 */
122 public function list_objects($options = array()) {
123
124 $options = wp_parse_args($options, array(
125 'delimiter' => '',
126 'maxResults' => 1000,
127 'pageToken' => '',
128 'prefix' => '',
129 'projection' => 'noAcl',
130 'versions' => false
131 ));
132
133 return $this->service->objects->listObjects($this->bucket, $options);
134 }
135
136 /**
137 * List all items page by page of maxResults
138 * @param $bucket
139 * @param array $options
140 * @return mixed
141 */
142 public function list_all_objects($options = array()) {
143
144 $options = wp_parse_args($options, array(
145 'delimiter' => '',
146 'maxResults' => 1000,
147 'pageToken' => '',
148 'prefix' => '',
149 'projection' => 'noAcl',
150 'versions' => false
151 ));
152
153 $response = $this->service->objects->listObjects($this->bucket, $options);
154
155 $this->temp_objects = array_merge($this->temp_objects, $response->getItems());
156
157 if (!empty($response->nextPageToken)) {
158 $options['pageToken'] = $response->nextPageToken;
159 return $this->list_all_objects($this->bucket, $options);
160 } else {
161 return $this->temp_objects;
162 }
163 }
164
165 /**
166 * Add/Update Media Object to Bucket
167 *
168 * https://stackoverflow.com/questions/26872851/resumable-uploading-to-google-cloud-storage-using-php-api
169 *
170 * @author peshkov@UD
171 * @param array $args
172 * @return bool
173 */
174 public function add_media($args = array()) {
175 try {
176
177 @set_time_limit(-1);
178
179 $args = wp_parse_args($args, array(
180 'use_root' => true,
181 'force' => false,
182 'name' => false,
183 'absolutePath' => false,
184 'mimeType' => 'image/jpeg',
185 'metadata' => array(),
186 'is_webp' => '',
187 ));
188
189 /* Be sure file exists. */
190 if (!file_exists($args['absolutePath'])) {
191 return new \WP_Error('sm_error', __('Unable to locate file on disk', ud_get_stateless_media()->domain));
192 }
193
194 $use_wildcards = Utility::is_use_wildcards();
195
196 /* Set default name if parameter was not passed. */
197 if (empty($name) || $use_wildcards) {
198 $name = basename($args['name']);
199 }
200
201 $object_id = isset($args['metadata']['object-id']) ? $args['metadata']['object-id'] : (isset($args['metadata']['child-of']) ? $args['metadata']['child-of'] : "");
202 $object_size = isset($args['metadata']['size']) ? $args['metadata']['size'] : "";
203
204 $args['name'] = apply_filters('wp_stateless_file_name', $args['name'], $args['use_root'], $object_id, $object_size, $use_wildcards);
205 $args = apply_filters('wp_stateless_add_media_args', $args);
206 $name = $args['name'];
207
208 // If media exists we just return it
209 if (!$args['force'] && $media = $this->media_exists($name)) {
210 if ($media->getCacheControl() != $args['cacheControl']) {
211 $media->setCacheControl($args['cacheControl']);
212 $media = $this->service->objects->patch($this->bucket, $name, $media);
213 }
214 return get_object_vars($media);
215 }
216
217 $media = new Google_Client\Google_Service_Storage_StorageObject();
218 $media->setName($name);
219 $media->setMetadata($args['metadata']);
220
221 if (isset($args['cacheControl'])) {
222 $media->setCacheControl($args['cacheControl']);
223 }
224
225 if (isset($args['contentEncoding'])) {
226 $media->setContentEncoding($args['contentEncoding']);
227 }
228
229 if (isset($args['contentDisposition'])) {
230 $media->getContentDisposition($args['contentDisposition']);
231 }
232
233 // If chunk size is defined, we assume user needs the file to be sent by chunks
234 // Otherwise, we send it directly
235 if (defined('WP_STATELESS_MEDIA_UPLOAD_CHUNK_SIZE') && is_int(WP_STATELESS_MEDIA_UPLOAD_CHUNK_SIZE)) {
236 $this->client->setDefer(true);
237
238 $file_size = filesize($args['absolutePath']);
239 $filetoupload = array('name' => $name, 'uploadType' => 'resumable');
240 $request = $this->service->objects->insert($this->bucket, $media, $filetoupload);
241 $uploader = new Google_Client\Google_Http_MediaFileUpload($this->client, $request, $args['mimeType'], null, true, WP_STATELESS_MEDIA_UPLOAD_CHUNK_SIZE);
242 $uploader->setFileSize($file_size);
243 $handle = fopen($args['absolutePath'], "rb");
244
245 $status = false;
246 while (!$status && !feof($handle)) {
247 $chunk = fread($handle, WP_STATELESS_MEDIA_UPLOAD_CHUNK_SIZE);
248 $status = $uploader->nextChunk($chunk);
249 }
250
251 $media = false;
252 if ($status != false) {
253 $media = $status;
254 }
255
256 fclose($handle);
257 // Reset to the client to execute requests immediately in the future.
258 $this->client->setDefer(false);
259 } else {
260 $media = $this->service->objects->insert($this->bucket, $media, array_filter(array(
261 'data' => file_get_contents($args['absolutePath']),
262 'uploadType' => 'media',
263 'mimeType' => $args['mimeType'],
264 'predefinedAcl' => 'bucketOwnerFullControl',
265 )));
266 }
267
268 $this->mediaInsertACL($name, $media, $args);
269 } catch (Exception $e) {
270 return new WP_Error('sm_error', $e->getMessage());
271 }
272 return get_object_vars($media);
273 }
274
275 /**
276 * Update Object ACL
277 */
278 public function mediaInsertACL($name, $media = array(), $agrs = array()) {
279 /* Make Media Public READ for all on success */
280 if (!empty($name)) {
281 $acl = new \wpCloud\StatelessMedia\Google_Client\Google_Service_Storage_ObjectAccessControl();
282 $acl->setEntity('allUsers');
283 $acl->setRole('READER');
284 $acl = apply_filters('wp_stateless_media_acl', $acl, $name, $media, $agrs);
285 $this->service->objectAccessControls->insert($this->bucket, $name, $acl);
286 }
287 }
288
289 /**
290 * Get or save media file
291 *
292 * @param $path
293 * @param bool $save
294 * @param bool $save_path
295 * @return bool|\Google_Service_Storage_StorageObject|int
296 */
297 public function get_media($path, $save = false, $save_path = false) {
298 try {
299 $media = $this->service->objects->get($this->bucket, $path);
300 } catch (\Exception $e) {
301 return false;
302 }
303
304 if (empty($media->id)) return false;
305
306 if ($save && $save_path) {
307 if (!file_exists($_dir = dirname($save_path))) {
308 wp_mkdir_p($_dir);
309 }
310 return $this->client->getHttpClient()->get($media->getMediaLink(), ['save_to' => $save_path])->getStatusCode();
311 }
312
313 return $media;
314 }
315
316 /**
317 * get or save media file
318 * @param $path
319 * @param bool $save
320 * @param bool $save_path
321 * @return bool|\Google_Service_Storage_StorageObject|int
322 */
323 public function copy_media($path, $new_path) {
324 try {
325 $media = $this->service->objects->get($this->bucket, $path);
326 $media = $this->service->objects->copy($this->bucket, $path, $this->bucket, $new_path, $media);
327 $this->mediaInsertACL($new_path, $media);
328 } catch (\Exception $e) {
329 return false;
330 }
331
332 if (empty($media->id)) return false;
333
334 return $media;
335 }
336
337 /**
338 * get or save media file
339 * @param $path
340 * @param bool $save
341 * @param bool $save_path
342 * @return bool|\Google_Service_Storage_StorageObject|int
343 */
344 public function move_media($path, $new_path) {
345 try {
346 $media = $this->copy_media($path, $new_path);
347 $this->remove_media($path);
348 } catch (\Exception $e) {
349 return false;
350 }
351
352 if (empty($media->id)) return false;
353
354 return $media;
355 }
356
357 /**
358 * Check if media exists
359 * @param $path
360 * @return bool
361 */
362 public function media_exists($path) {
363 try {
364 $media = $this->service->objects->get($this->bucket, $path);
365 // Here we wanted to check if access allowed, but noticed it actually sets this ACL... Leaving it as is. @author korotkov@ud
366 $this->service->objectAccessControls->get($this->bucket, $path, 'allUsers');
367 } catch (\Exception $e) {
368 return false;
369 }
370
371 if (empty($media->id)) return false;
372 return $media;
373 }
374
375 /**
376 * Fired for every file remove action
377 *
378 * @author peshkov@UD
379 * @param string $name
380 * @param string $id
381 * @param boolean $use_root
382 * @param string $size
383 * @param boolean $is_webp
384 * @return bool
385 */
386 public function remove_media($name, $id = "", $use_root = true, $size = "", $is_webp = false) {
387 try {
388 $name = apply_filters('wp_stateless_file_name', $name, $use_root, $id, $size, false);
389 if ($is_webp && substr($name, -4) != "webp") $name .= ".webp";
390
391 $this->service->objects->delete($this->bucket, $name);
392 } catch (Exception $e) {
393 return new WP_Error('sm_error', $e->getMessage());
394 }
395 return true;
396 }
397
398 /**
399 * Tests connection to Google Storage
400 * by trying to get passed bucket's data.
401 *
402 * @author peshkov@UD
403 */
404 public function is_connected() {
405 try {
406 $this->service->buckets->get($this->bucket);
407 } catch (Exception $e) {
408 return $e;
409 }
410 return true;
411 }
412
413 /**
414 * Determine if instance already exists and Return Instance
415 *
416 * @param array $args
417 *
418 * $args
419 * @param string client_id
420 * @param string service_account_name
421 * @param string key_file_path
422 *
423 * @author peshkov@UD
424 * @return \wpCloud\StatelessMedia\GS_Client
425 */
426 public static function get_instance($args) {
427 if (null === self::$instance) {
428
429 try {
430
431 if (empty($args['bucket'])) {
432 throw new Exception(__('<b>Bucket</b> parameter must be provided.'));
433 }
434
435 $json = "{}";
436
437 if (!empty($args['key_json'])) {
438 $json = json_decode($args['key_json']);
439 }
440
441 if (!$json || !property_exists($json, 'private_key')) {
442 throw new Exception(__('<b>Service Account JSON</b> is invalid.'));
443 }
444
445 self::$instance = new self($args);
446 } catch (Exception $e) {
447 return new WP_Error('sm_error', $e->getMessage());
448 }
449 }
450 return self::$instance;
451 }
452
453 /**
454 * Set warning about potential conflict with Google SDK
455 *
456 * @since 2.0.1
457 */
458 private function _setWarning() {
459
460 $reflector = new \ReflectionClass('Google_Client');
461 $pluginBasename = wp_normalize_path(plugin_basename($reflector->getFileName()));
462
463 // Check if get_plugins() function exists. This is required on the front end of the
464 // site, since it is in a file that is normally only loaded in the admin.
465 if (!function_exists('get_plugins')) {
466 require_once ABSPATH . 'wp-admin/includes/plugin.php';
467 }
468
469 $pluginBasenameParts = explode('/', $pluginBasename);
470 $pluginName = __("UNDEFINED", ud_get_stateless_media()->domain);
471
472 foreach (get_plugins() as $path => $meta) {
473 if (strpos($path, trailingslashit($pluginBasenameParts[0])) === 0) {
474 $pluginName = $meta['Name'];
475 }
476 };
477
478 $error = sprintf(
479 __("%s plugin may have potential Google SDK version conflicts with %s plugin. %s is using Google SDK %s, when %s loads old Google SDK version %s.", ud_get_stateless_media()->domain),
480 "<b>" . 'WP-Stateless' . "</b>",
481 "<b>" . $pluginName . "</b>",
482 'WP-Stateless',
483 "<b>v2.0</b>",
484 $pluginName,
485 "<b>v" . \wpCloud\StatelessMedia\Google_Client\Google_Client::LIBVER . "</b>"
486 );
487
488 set_transient("wp_stateless_google_sdk_conflict", $error);
489 }
490
491 /**
492 * Removes Warning if it exists
493 *
494 */
495 private function _deleteWarning() {
496 delete_transient("wp_stateless_google_sdk_conflict");
497 }
498 }
499 }
500 }
501