PluginProbe
WP-Stateless – Google Cloud Storage / trunk
WP-Stateless – Google Cloud Storage vtrunk
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 trunk, at lib/classes/class-gs-client.php

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