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

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

494 lines 15.7 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('\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 \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 \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 $object_id = isset($args['metadata']['object-id']) ? $args['metadata']['object-id'] : (isset($args['metadata']['child-of']) ? $args['metadata']['child-of'] : "");
195 $object_size = isset($args['metadata']['size']) ? $args['metadata']['size'] : "";
196
197 $args['name'] = apply_filters('wp_stateless_file_name', $args['name'], $args['use_root'], $object_id, $object_size);
198 $args = apply_filters('wp_stateless_add_media_args', $args);
199 $name = $args['name'];
200
201 // If media exists we just return it
202 if (!$args['force'] && $media = $this->media_exists($name)) {
203 if ($media->getCacheControl() != $args['cacheControl']) {
204 $media->setCacheControl($args['cacheControl']);
205 $media = $this->service->objects->patch($this->bucket, $name, $media);
206 }
207 return get_object_vars($media);
208 }
209
210 $media = new \Google\Service\Storage\StorageObject();
211 $media->setName($name);
212 $media->setMetadata($args['metadata']);
213
214 if (isset($args['cacheControl'])) {
215 $media->setCacheControl($args['cacheControl']);
216 }
217
218 if (isset($args['contentEncoding'])) {
219 $media->setContentEncoding($args['contentEncoding']);
220 }
221
222 if (isset($args['contentDisposition'])) {
223 $media->getContentDisposition($args['contentDisposition']);
224 }
225
226 // If chunk size is defined, we assume user needs the file to be sent by chunks
227 // Otherwise, we send it directly
228 if (defined('WP_STATELESS_MEDIA_UPLOAD_CHUNK_SIZE') && is_int(WP_STATELESS_MEDIA_UPLOAD_CHUNK_SIZE)) {
229 $this->client->setDefer(true);
230
231 $file_size = filesize($args['absolutePath']);
232 $filetoupload = array('name' => $name, 'uploadType' => 'resumable');
233 $request = $this->service->objects->insert($this->bucket, $media, $filetoupload);
234 $uploader = new \Google\Google_Http_MediaFileUpload($this->client, $request, $args['mimeType'], null, true, WP_STATELESS_MEDIA_UPLOAD_CHUNK_SIZE);
235 $uploader->setFileSize($file_size);
236 $handle = fopen($args['absolutePath'], "rb");
237
238 $status = false;
239 while (!$status && !feof($handle)) {
240 $chunk = fread($handle, WP_STATELESS_MEDIA_UPLOAD_CHUNK_SIZE);
241 $status = $uploader->nextChunk($chunk);
242 }
243
244 $media = false;
245 if ($status != false) {
246 $media = $status;
247 }
248
249 fclose($handle);
250 // Reset to the client to execute requests immediately in the future.
251 $this->client->setDefer(false);
252 } else {
253 $media = $this->service->objects->insert($this->bucket, $media, array_filter(array(
254 'data' => file_get_contents($args['absolutePath']),
255 'uploadType' => 'media',
256 'mimeType' => $args['mimeType'],
257 'predefinedAcl' => 'bucketOwnerFullControl',
258 )));
259 }
260
261 $this->mediaInsertACL($name, $media, $args);
262 } catch (Exception $e) {
263 return new WP_Error('sm_error', $e->getMessage());
264 }
265 return get_object_vars($media);
266 }
267
268 /**
269 * Update Object ACL
270 */
271 public function mediaInsertACL($name, $media = array(), $agrs = array()) {
272 /* Make Media Public READ for all on success */
273 if (!empty($name)) {
274 $acl = new \Google\Service\Storage\ObjectAccessControl();
275 $acl->setEntity('allUsers');
276 $acl->setRole('READER');
277 $acl = apply_filters('wp_stateless_media_acl', $acl, $name, $media, $agrs);
278 $this->service->objectAccessControls->insert($this->bucket, $name, $acl);
279 }
280 }
281
282 /**
283 * Get or save media file
284 *
285 * @param $path
286 * @param bool $save
287 * @param bool $save_path
288 * @return bool|\Google_Service_Storage_StorageObject|int
289 */
290 public function get_media($path, $save = false, $save_path = false) {
291 try {
292 $media = $this->service->objects->get($this->bucket, $path);
293 } catch (\Exception $e) {
294 return false;
295 }
296
297 if (empty($media->id)) return false;
298
299 if ($save && $save_path) {
300 if (!file_exists($_dir = dirname($save_path))) {
301 wp_mkdir_p($_dir);
302 }
303 return $this->client->getHttpClient()->get($media->getMediaLink(), ['sink' => $save_path])->getStatusCode();
304 }
305
306 return $media;
307 }
308
309 /**
310 * get or save media file
311 * @param $path
312 * @param bool $save
313 * @param bool $save_path
314 * @return bool|\Google_Service_Storage_StorageObject|int
315 */
316 public function copy_media($path, $new_path) {
317 try {
318 $media = $this->service->objects->get($this->bucket, $path);
319 $media = $this->service->objects->copy($this->bucket, $path, $this->bucket, $new_path, $media);
320 $this->mediaInsertACL($new_path, $media);
321 } catch (\Exception $e) {
322 return false;
323 }
324
325 if (empty($media->id)) return false;
326
327 return $media;
328 }
329
330 /**
331 * get or save media file
332 * @param $path
333 * @param bool $save
334 * @param bool $save_path
335 * @return bool|\Google_Service_Storage_StorageObject|int
336 */
337 public function move_media($path, $new_path) {
338 try {
339 $media = $this->copy_media($path, $new_path);
340 $this->remove_media($path);
341 } catch (\Exception $e) {
342 return false;
343 }
344
345 if (empty($media->id)) return false;
346
347 return $media;
348 }
349
350 /**
351 * Check if media exists
352 * @param $path
353 * @return bool|object
354 */
355 public function media_exists($path) {
356 try {
357 $media = $this->service->objects->get($this->bucket, $path);
358 // Here we wanted to check if access allowed, but noticed it actually sets this ACL... Leaving it as is. @author korotkov@ud
359 $this->service->objectAccessControls->get($this->bucket, $path, 'allUsers');
360 } catch (\Exception $e) {
361 return false;
362 }
363
364 if (empty($media->id)) return false;
365 return $media;
366 }
367
368 /**
369 * Fired for every file remove action
370 *
371 * @author peshkov@UD
372 * @param string $name
373 * @param string $id
374 * @param boolean $use_root
375 * @param string $size
376 * @param boolean $is_webp
377 * @return bool
378 */
379 public function remove_media($name, $id = "", $use_root = true, $size = "", $is_webp = false) {
380 try {
381 $name = apply_filters('wp_stateless_file_name', $name, $use_root, $id, $size);
382 if ($is_webp && substr($name, -4) != "webp") $name .= ".webp";
383
384 $this->service->objects->delete($this->bucket, $name);
385 } catch (Exception $e) {
386 return new WP_Error('sm_error', $e->getMessage());
387 }
388 return true;
389 }
390
391 /**
392 * Tests connection to Google Storage
393 * by trying to get passed bucket's data.
394 *
395 * @author peshkov@UD
396 */
397 public function is_connected() {
398 try {
399 $this->service->buckets->get($this->bucket);
400 } catch (Exception $e) {
401 return $e;
402 }
403 return true;
404 }
405
406 /**
407 * Determine if instance already exists and Return Instance
408 *
409 * @param array $args
410 *
411 * $args
412 * @param string client_id
413 * @param string service_account_name
414 * @param string key_file_path
415 *
416 * @author peshkov@UD
417 * @return \wpCloud\StatelessMedia\GS_Client
418 */
419 public static function get_instance($args) {
420 if (null === self::$instance) {
421
422 try {
423
424 if (empty($args['bucket'])) {
425 throw new Exception(__('<b>Bucket</b> parameter must be provided.'));
426 }
427
428 $json = "{}";
429
430 if (!empty($args['key_json'])) {
431 $json = json_decode($args['key_json']);
432 }
433
434 if (!$json || !property_exists($json, 'private_key')) {
435 throw new Exception(__('<b>Service Account JSON</b> is invalid.'));
436 }
437
438 self::$instance = new self($args);
439 } catch (Exception $e) {
440 return new WP_Error('sm_error', $e->getMessage());
441 }
442 }
443 return self::$instance;
444 }
445
446 /**
447 * Set warning about potential conflict with Google SDK
448 *
449 * @since 2.0.1
450 */
451 private function _setWarning() {
452
453 $reflector = new \ReflectionClass('Google_Client');
454 $pluginBasename = wp_normalize_path(plugin_basename($reflector->getFileName()));
455
456 // Check if get_plugins() function exists. This is required on the front end of the
457 // site, since it is in a file that is normally only loaded in the admin.
458 if (!function_exists('get_plugins')) {
459 require_once ABSPATH . 'wp-admin/includes/plugin.php';
460 }
461
462 $pluginBasenameParts = explode('/', $pluginBasename);
463 $pluginName = __("UNDEFINED", ud_get_stateless_media()->domain);
464
465 foreach (get_plugins() as $path => $meta) {
466 if (strpos($path, trailingslashit($pluginBasenameParts[0])) === 0) {
467 $pluginName = $meta['Name'];
468 }
469 };
470
471 $error = sprintf(
472 __("%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),
473 "<b>" . 'WP-Stateless' . "</b>",
474 "<b>" . $pluginName . "</b>",
475 'WP-Stateless',
476 "<b>v2.0</b>",
477 $pluginName,
478 "<b>v" . \Google\Client::LIBVER . "</b>"
479 );
480
481 set_transient("wp_stateless_google_sdk_conflict", $error);
482 }
483
484 /**
485 * Removes Warning if it exists
486 *
487 */
488 private function _deleteWarning() {
489 delete_transient("wp_stateless_google_sdk_conflict");
490 }
491 }
492 }
493 }
494