PluginProbe
Photonic Gallery & Lightbox for Flickr, SmugMug & Others / 3.33
Photonic Gallery & Lightbox for Flickr, SmugMug & Others v3.33
3.36 3.35 3.34 3.33 2.19 2.20 2.21 2.22 2.23 2.24 2.25 2.26 2.27 2.28 2.29 2.30 2.31 2.32 2.33 2.34 2.40 2.41 2.42 2.43 2.44 All 141 releases
photonic / Platforms / Google_Photos.php

Google_Photos.php in Photonic Gallery & Lightbox for Flickr, SmugMug & Others 3.33, at Platforms/Google_Photos.php

970 lines 33.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Photonic_Plugin\Platforms;
4
5 use Photonic_Plugin\Components\Album_List;
6 use Photonic_Plugin\Components\Error;
7 use Photonic_Plugin\Components\Pagination;
8 use Photonic_Plugin\Components\Photo_List;
9 use Photonic_Plugin\Core\Photonic;
10 use Photonic_Plugin\Components\Album;
11 use Photonic_Plugin\Components\Photo;
12
13 require_once 'OAuth2.php';
14 require_once 'Level_One_Module.php';
15 require_once 'Level_Two_Module.php';
16 require_once 'Pageable.php';
17
18 /**
19 * Fetches photos from a user's Google Photos account.
20 * Lacks support for dual title / description fields, doesn't provide download URLs, and video support is ambiguous.
21 */
22 class Google_Photos extends OAuth2 implements Level_One_Module, Level_Two_Module, Pageable {
23 public $error_date_format;
24 public $refresh_token_valid;
25 private static $instance = null;
26
27 private $eol_date;
28 private $current_date;
29
30 protected function __construct() {
31 parent::__construct();
32 global $photonic_google_client_id, $photonic_google_client_secret, $photonic_google_refresh_token;
33
34 $this->eol_date = gmdate("Ymd", gmmktime(0, 0, 0, 3, 30, 2025));
35 $this->current_date = gmdate("Ymd");
36
37 // if (!empty($photonic_google_use_own_keys) || (!empty($photonic_google_client_id) && !empty($photonic_google_client_secret))) {
38 if (!empty($photonic_google_client_id) && !empty($photonic_google_client_secret)) {
39 $this->client_id = trim($photonic_google_client_id);
40 $this->client_secret = trim($photonic_google_client_secret);
41 }
42
43 /*
44 *
45 elseif (empty($photonic_google_use_own_keys)) {
46 $this->client_id = '';
47 $this->client_secret = '';
48 }
49 */
50
51 $this->provider = 'google';
52 $this->oauth_version = '2.0';
53 $this->response_type = 'code';
54 $this->scope = 'https://www.googleapis.com/auth/photoslibrary.readonly';
55 $this->link_lightbox_title = false; // empty($photonic_google_disable_title_link);
56
57 // Documentation
58 $this->doc_links = [
59 'general' => 'https://aquoid.com/plugins/photonic/google-photos/',
60 'photos' => 'https://aquoid.com/plugins/photonic/google-photos/photos/',
61 'albums' => 'https://aquoid.com/plugins/photonic/google-photos/albums/',
62 ];
63
64 $this->error_date_format = esc_html__('Dates must be entered in the format Y/M/D where Y is from 0 to 9999, M is from 0 to 12 and D is from 0 to 31. You entered %s.', 'photonic');
65 $this->oauth_done = false;
66
67 if ($this->current_date < $this->eol_date) {
68 $this->authenticate($photonic_google_refresh_token);
69 }
70 }
71
72 /**
73 * Main function that fetches the images associated with the shortcode.
74 *
75 * @param array $attr
76 * @return array
77 */
78 public function get_gallery_images($attr = []): array {
79 global $photonic_google_refresh_token, $photonic_google_media, $photonic_google_title_caption;
80
81 $out = [];
82
83 if ($this->current_date < $this->eol_date) {
84 $this->push_to_stack('Get Gallery Images');
85 $attr = array_merge(
86 $this->common_parameters,
87 [
88 'caption' => $photonic_google_title_caption,
89 'thumb_size' => '150',
90 'main_size' => '1600',
91 'tile_size' => '1600',
92 'crop_thumb' => 'crop',
93
94 // Google ...
95 'count' => 100,
96 'media' => $photonic_google_media,
97 'video_size' => 'dv',
98 'date_filters' => '',
99 'content_filters' => '',
100 'access' => 'all',
101 ],
102 $attr
103 );
104 $attr = array_map('trim', $attr);
105 $attr['overlay_size'] = empty($attr['overlay_size']) ? $attr['thumb_size'] : $attr['overlay_size'];
106 $attr['overlay_video_size'] = empty($attr['overlay_video_size']) ? $attr['video_size'] : $attr['overlay_video_size'];
107 $attr['overlay_crop'] = empty($attr['overlay_crop']) ? $attr['crop_thumb'] : $attr['overlay_crop'];
108 if (empty($this->client_id)) {
109 $this->pop_from_stack();
110 return [new Error(esc_html__('Google Photos Client ID not defined.', 'photonic') . Photonic::doc_link($this->doc_links['general']))];
111 }
112 if (empty($this->client_secret)) {
113 $this->pop_from_stack();
114 return [new Error(esc_html__('Google Photos Client Secret not defined.', 'photonic') . Photonic::doc_link($this->doc_links['general']))];
115 }
116 if (empty($photonic_google_refresh_token)) {
117 $this->pop_from_stack();
118 return [new Error(sprintf(esc_html__('Google Photos Refresh Token not defined. Please authenticate from %s.', 'photonic'), '<em>Photonic &rarr; Authentication</em>') . Photonic::doc_link($this->doc_links['general']))];
119 }
120 if (!$this->refresh_token_valid) {
121 $this->pop_from_stack();
122 $error = sprintf(esc_html__('Google Photos Refresh Token invalid. Please authenticate from %s.', 'photonic'), '<em>Photonic &rarr; Authentication</em>');
123 if (!empty($this->auth_error)) {
124 $error .= '<br/>' . sprintf(esc_html__('Error encountered during authentication: %s', 'photonic'), '<br/><pre>' . $this->auth_error . '</pre>');
125 }
126 return [new Error($error . Photonic::doc_link($this->doc_links['general']))];
127 }
128 if (empty($attr['view'])) {
129 $this->pop_from_stack();
130 return [new Error(sprintf(esc_html__('The %s parameter is mandatory for the shortcode.', 'photonic'), '<code>view</code>'))];
131 }
132 $query_urls = [];
133 if ('albums' === $attr['view']) {
134 $additional = [];
135 if (!empty($attr['count'])) {
136 $additional['pageSize'] = intval($attr['count']) > 50 ? 50 : intval($attr['count']);
137 }
138
139 if (!empty($attr['next_token'])) {
140 $additional['pageToken'] = $attr['next_token'];
141 }
142
143 $access = $this->access_all_or_shared($attr);
144 if ($access['shared']) {
145 $query_urls['https://photoslibrary.googleapis.com/v1/sharedAlbums'] = ['GET' => $additional];
146 }
147 if ($access['self']) {
148 $query_urls['https://photoslibrary.googleapis.com/v1/albums'] = ['GET' => $additional];
149 }
150 }
151 elseif ('photos' === $attr['view'] || 'shared-photos' === $attr['view']) {
152 $additional = [];
153 if (!empty($attr['album_id'])) {
154 $additional['albumId'] = $attr['album_id'];
155 }
156 else {
157 $filters = [];
158
159 $date_parameter = [];
160 $range_parameter = [];
161 if (!empty($attr['date_filters'])) {
162 /*
163 * Structure of $attr['date_filters']: comma-separated list of dates or date ranges.
164 * Each date is represented by Y/M/D, where 0 <= Y <= 9999, 0 <= M <= 12, 0 <= D < 31
165 * Each range is represented as Y/M/D-Y/M/D
166 */
167 $date_filters = explode(',', trim($attr['date_filters']));
168 foreach ($date_filters as $date_filter) {
169 $dates = explode('-', trim($date_filter));
170 if (count($dates) > 2) {
171 $dates = array_slice($dates, 0, 2);
172 }
173 $range = [];
174 foreach ($dates as $idx => $date) {
175 $date_parts = explode('/', trim($date));
176 if (count($date_parts) !== 3) {
177 $this->pop_from_stack();
178 return [new Error(sprintf($this->error_date_format, $date))];
179 }
180
181 if (!is_numeric($date_parts[0]) || $date_parts[0] > 9999 || $date_parts[0] < 0 ||
182 !is_numeric($date_parts[1]) || $date_parts[1] > 12 || $date_parts[1] < 0 ||
183 !is_numeric($date_parts[2]) || $date_parts[2] > 31 || $date_parts[2] < 0) {
184 $this->pop_from_stack();
185 return [new Error(sprintf($this->error_date_format, $date))];
186 }
187
188 $date_object = [
189 'year' => intval($date_parts[0]),
190 'month' => intval($date_parts[1]),
191 'day' => intval($date_parts[2]),
192 ];
193
194 if (count($dates) === 1) {
195 $date_parameter[] = $date_object;
196 }
197 elseif (0 === $idx) {
198 $range['startDate'] = $date_object;
199 }
200 else {
201 $range['endDate'] = $date_object;
202 }
203 }
204 if (!empty($range)) {
205 $range_parameter[] = $range;
206 }
207 }
208
209 $date_filter_parameter = [];
210 if (!empty($date_parameter)) {
211 $date_filter_parameter['dates'] = $date_parameter;
212 }
213 if (!empty($range_parameter)) {
214 $date_filter_parameter['ranges'] = $range_parameter;
215 }
216 if (!empty($date_filter_parameter)) {
217 $filters['dateFilter'] = $date_filter_parameter;
218 }
219 }
220
221 if (!empty($attr['content_filters'])) {
222 $valid_filters = [
223 'NONE' => 'Default content category. This category is ignored if any other category is also listed.',
224 'LANDSCAPES' => 'Media items containing landscapes.',
225 'RECEIPTS' => 'Media items containing receipts.',
226 'CITYSCAPES' => 'Media items containing cityscapes.',
227 'LANDMARKS' => 'Media items containing landmarks.',
228 'SELFIES' => 'Media items that are selfies.',
229 'PEOPLE' => 'Media items containing people.',
230 'PETS' => 'Media items containing pets.',
231 'WEDDINGS' => 'Media items from weddings.',
232 'BIRTHDAYS' => 'Media items from birthdays.',
233 'DOCUMENTS' => 'Media items containing documents.',
234 'TRAVEL' => 'Media items taken during travel.',
235 'ANIMALS' => 'Media items containing animals.',
236 'FOOD' => 'Media items containing food.',
237 'SPORT' => 'Media items from sporting events.',
238 'NIGHT' => 'Media items taken at night.',
239 'PERFORMANCES' => 'Media items from performances.',
240 'WHITEBOARDS' => 'Media items containing whiteboards.',
241 'SCREENSHOTS' => 'Media items that are screenshots.',
242 'UTILITY' => 'Media items that are considered to be utility. These include, but are not limited to documents, screenshots, whiteboards etc.',
243 ];
244
245 /*
246 * Structure of content_filters: C1,C2,-C3,C4,-C5.
247 * The filters are specified as a comma-separated list.
248 * A "-" before the filter's name indicates that the filter should be excluded rather than included.
249 */
250 $content_filters = explode(',', $attr['content_filters']);
251 $include = $exclude = [];
252 foreach ($content_filters as $content_filter) {
253 $content_filter = strtoupper($content_filter);
254 if (stripos($content_filter, '-') === 0 && array_key_exists(substr($content_filter, 1), $valid_filters)) {
255 $exclude[] = substr($content_filter, 1);
256 }
257 elseif (array_key_exists($content_filter, $valid_filters)) {
258 $include[] = $content_filter;
259 }
260 }
261
262 $content_filter_parameter = [];
263 if (!empty($include)) {
264 $content_filter_parameter['includedContentCategories'] = $include;
265 }
266 if (!empty($exclude)) {
267 $content_filter_parameter['excludedContentCategories'] = $exclude;
268 }
269 if (!empty($content_filter_parameter)) {
270 $filters['contentFilter'] = $content_filter_parameter;
271 }
272 }
273
274 $media_filters = explode(',', $attr['media']);
275 $media_filter_parameter = [];
276 if (in_array('all', $media_filters, true)) {
277 $media_filter_parameter[] = 'ALL_MEDIA';
278 }
279 elseif (in_array('photos', $media_filters, true)) {
280 $media_filter_parameter[] = 'PHOTO';
281 }
282 elseif (in_array('videos', $media_filters, true)) {
283 $media_filter_parameter[] = 'VIDEO';
284 }
285
286 if (!empty($media_filter_parameter)) {
287 $filters['mediaTypeFilter'] = ['mediaTypes' => $media_filter_parameter];
288 }
289
290 if (!empty($filters)) {
291 $additional['filters'] = $filters;
292 }
293 }
294
295 if (!empty($attr['count']) || !empty($attr['photo_count'])) {
296 $additional['pageSize'] = !empty($attr['photo_count']) ? $attr['photo_count'] : $attr['count'];
297 $additional['pageSize'] = intval($additional['pageSize']) > 100 ? 100 : intval($additional['pageSize']);
298 }
299 if (!empty($attr['next_token'])) {
300 $additional['pageToken'] = $attr['next_token'];
301 }
302
303 $query_urls['https://photoslibrary.googleapis.com/v1/mediaItems:search'] = ['POST' => $additional];
304 }
305 $out = $this->make_call($query_urls, $attr);
306 $this->pop_from_stack();
307 if (!empty($this->stack_trace[$this->gallery_index])) {
308 $out[] = $this->stack_trace[$this->gallery_index];
309 }
310 }
311
312 return $out;
313 }
314
315 /**
316 * Makes calls to Google with the shortcode parameters.
317 *
318 * @param $query_urls - The URLs to be queried
319 * @param $attr - The shortcode attributes
320 * @return array
321 */
322 private function make_call($query_urls, $attr): array {
323 global $photonic_google_refresh_token;
324 $this->push_to_stack('Making calls');
325
326 $incremented = false;
327 $components = [];
328 $access = $this->access_all_or_shared($attr);
329
330 $all_ids = [];
331 foreach ($query_urls as $query_url => $method_and_args) {
332 $this->push_to_stack("Query $query_url");
333 if ($access['self'] && 'https://photoslibrary.googleapis.com/v1/sharedAlbums' === $query_url) {
334 $defer = true;
335 }
336 else {
337 $defer = false;
338 }
339
340 if (!empty($photonic_google_refresh_token) && !empty($this->access_token)) {
341 $query_url = add_query_arg('access_token', $this->access_token, $query_url);
342 }
343
344 foreach ($method_and_args as $method => $args) {
345 $this->push_to_stack('Sending request');
346 if (empty($args['filters'])) {
347 $call_args = [];
348 $call_args['method'] = $method;
349 $call_args['body'] = $args;
350 $call_args['sslverify'] = PHOTONIC_SSL_VERIFY;
351 $response = wp_remote_request($query_url, $call_args);
352 }
353 else {
354 $headers = [];
355 $headers['Accept'] = 'application/json';
356 $headers['Content-Type'] = 'application/json';
357
358 $response = wp_remote_request(
359 $query_url,
360 [
361 'method' => 'POST',
362 'headers' => $headers,
363 'httpversion' => '1.0',
364 'body' => wp_json_encode($args),
365 ]
366 );
367 }
368 $this->pop_from_stack();
369
370 if (!is_wp_error($response)) {
371 $this->push_to_stack('Processing Response');
372 $body = wp_remote_retrieve_body($response);
373
374 if (!$incremented) {
375 $incremented = true;
376 }
377
378 $output = $this->process_response($body, $attr, $defer, $all_ids);
379
380 if (!is_null($output)) {
381 if ($defer && is_array($output)) {
382 foreach ($output as $object) {
383 $all_ids[] = $object->id;
384 }
385 }
386 else {
387 $components[] = $output;
388 }
389 }
390
391 $this->pop_from_stack();
392 }
393 else {
394 $this->pop_from_stack(); // "Query $query_url"
395 $this->pop_from_stack(); // 'Making calls'
396 return [new Error($response->get_error_message())];
397 }
398 }
399 $this->pop_from_stack();
400 }
401
402 $this->pop_from_stack();
403 return $components;
404 }
405
406 /**
407 * @param $body
408 * @param $short_code
409 * @param bool|false $deferred
410 * @param array $remove
411 * @return mixed
412 */
413 private function process_response($body, $short_code, $deferred = false, $remove = []) {
414 global $photonic_google_photo_title_display, $photonic_google_photos_per_row_constraint, $photonic_gallery_template_page,
415 $photonic_google_photos_constrain_by_count, $photonic_google_photo_pop_title_display, $photonic_google_hide_album_photo_count_display;
416
417 if (!empty($body)) {
418 $body = json_decode($body);
419 $row_constraints = ['constraint-type' => $photonic_google_photos_per_row_constraint, 'count' => $photonic_google_photos_constrain_by_count];
420 $display = $short_code['display'];
421 if (isset($body->albums) || isset($body->sharedAlbums)) {
422 $albums = $body->albums ?? $body->sharedAlbums;
423
424 $pagination = $this->get_pagination($body, $short_code);
425 $dummy_options = [];
426 $albums = $this->build_level_2_objects($albums, $short_code, $remove, $dummy_options, $pagination);
427
428 global $photonic_google_photos_layout_engine;
429 $layout_engine = $short_code['layout_engine'] ?? $photonic_google_photos_layout_engine;
430 if ('css' === $layout_engine) {
431 $this->update_thumbnail_information($albums, $short_code);
432 }
433
434 if ($deferred) {
435 return $albums;
436 }
437
438 $album_list = new Album_List($short_code);
439
440 $album_list->albums = $albums;
441 $album_list->row_constraints = $row_constraints;
442 $album_list->type = 'albums';
443 $album_list->singular_type = 'album';
444 $album_list->title_position = $photonic_google_photo_title_display;
445 $album_list->level_1_count_display = !empty($photonic_google_hide_album_photo_count_display);
446 $album_list->pagination = $pagination;
447 $album_list->album_opens_gallery = ('page' === $short_code['popup'] && !empty($photonic_gallery_template_page) && is_string(get_post_status($photonic_gallery_template_page)));
448
449 return $album_list;
450 }
451 elseif (isset($body->mediaItems)) {
452 if ('local' === $display) {
453 $title_position = $photonic_google_photo_title_display;
454 }
455 else {
456 $row_constraints = ['constraint-type' => 'padding'];
457 $title_position = $photonic_google_photo_pop_title_display;
458 }
459
460 $pagination = $this->get_pagination($body, $short_code);
461
462 $photos = $body->mediaItems;
463 $photos = $this->build_level_1_objects($photos, $short_code);
464
465 $photo_list = new Photo_List($short_code);
466 $photo_list->photos = $photos;
467 $photo_list->title_position = $title_position;
468 $photo_list->row_constraints = $row_constraints;
469 $photo_list->parent = 'album';
470 $photo_list->pagination = $pagination;
471
472 return $photo_list;
473 }
474 elseif (isset($body->error)) {
475 $err = esc_html__('Failed to get data. Error:', 'photonic') . "<br/><code>\n";
476 $err .= $body->error->message;
477 $err .= "</code><br/>\n";
478
479 return new Error($err);
480 }
481 }
482 else {
483 $err = esc_html__('Failed to get data. Error:', 'photonic') . "<br/><code>\n";
484 $err .= $body;
485 $err .= "</code><br/>\n";
486
487 return new Error($err);
488 }
489
490 return null;
491 }
492
493 public function build_level_1_objects($response, array $short_code, $module_parameters = [], $options = []): array {
494 $objects = [];
495 $sizes = [
496 'thumb_size' => [
497 'size' => $short_code['thumb_size'],
498 'crop' => $short_code['crop_thumb'],
499 ],
500 'tile_size' => [
501 'size' => $short_code['tile_size'],
502 ],
503 'main_size' => [
504 'size' => $short_code['main_size'],
505 ]
506 ];
507
508 foreach ($response as $photo) {
509 $photonic_photo = new Photo();
510
511 $is_video = false;
512 if (!empty($photo->mediaMetadata)) {
513 if (!empty($photo->mediaMetadata->video)) {
514 $is_video = true;
515 }
516
517 if (!empty($photo->mediaMetadata->creationTime)) {
518 $photonic_photo->taken_on = sanitize_text_field($photo->mediaMetadata->creationTime);
519 }
520 }
521
522 $photonic_photo->thumb_size = $photonic_photo->tile_size = $photonic_photo->main_size = [];
523
524 $media = explode(',', $short_code['media']);
525 $videos_ok = in_array('videos', $media, true) || in_array('all', $media, true);
526 $photos_ok = in_array('photos', $media, true) || in_array('all', $media, true);
527 if (($is_video && !$videos_ok) || (!$is_video && !$photos_ok)) {
528 continue;
529 }
530
531 $photonic_photo->id = $photo->id;
532 $photonic_photo->thumbnail = esc_url($photo->baseUrl . "=w{$short_code['thumb_size']}-h{$short_code['thumb_size']}" . ('crop' === $short_code['crop_thumb'] ? '-c' : ''));
533 $photonic_photo->tile_image = esc_url($photo->baseUrl . "=w{$short_code['tile_size']}-h{$short_code['tile_size']}");
534 $photonic_photo->main_image = esc_url($photo->baseUrl . "=w{$short_code['main_size']}-h{$short_code['main_size']}");
535
536 $this->calculate_sizes($photo, $photonic_photo, $sizes);
537
538 if ($is_video) {
539 $photonic_photo->video = esc_url($photo->baseUrl . "={$short_code['video_size']}");
540 $photonic_photo->mime = $photo->mimeType ?: 'video/mp4';
541 }
542 else {
543 $photonic_photo->download = esc_url($photonic_photo->main_image . '-d');
544 }
545
546 if (!isset($photo->productUrl)) {
547 $photonic_photo->main_page = esc_url($photonic_photo->main_image);
548 }
549 else {
550 $photonic_photo->main_page = esc_url($photo->productUrl);
551 }
552
553 if (!empty($photo->description)) {
554 $photonic_photo->title = wp_kses_post($photo->description);
555 }
556 else {
557 $photonic_photo->title = '';
558 }
559
560 $photonic_photo->alt_title = $photonic_photo->title;
561 $photonic_photo->description = $photonic_photo->title;
562
563 $objects[] = $photonic_photo;
564 }
565
566 return $objects;
567 }
568
569 /**
570 * @param $objects_or_response
571 * @param array $short_code
572 * @param array $remove
573 * @param array $options
574 * @param Pagination|null $pagination
575 * @return array
576 */
577 public function build_level_2_objects($objects_or_response, array $short_code, array $remove = [], array &$options = [], ?Pagination &$pagination = null): array {
578 $filter = $short_code['filter'];
579 $filters = empty($filter) ? [] : explode(',', $filter);
580 $processed = [];
581
582 $objects = [];
583 foreach ($objects_or_response as $album) {
584 if (!empty($filters) && ((!in_array($album->id, $filters, true) && strtolower($short_code['filter_type']) !== 'exclude') ||
585 (in_array($album->id, $filters, true) && strtolower($short_code['filter_type']) === 'exclude'))) {
586 continue;
587 }
588
589 if (in_array($album->id, $remove, true)) {
590 continue;
591 }
592
593 $object = $this->process_album($album, $short_code);
594 if (!empty($object)) {
595 $objects[] = $object;
596 $processed[] = $album->id;
597 }
598 }
599
600 global $photonic_google_chain_queries;
601 if (!empty($pagination->next_token) && strtolower($short_code['filter_type']) !== 'exclude' && !empty($filters) && count($processed) < count($filters) && !empty($photonic_google_chain_queries)) {
602 $additional = [];
603 if (!empty($short_code['count'])) {
604 $additional['pageSize'] = intval($short_code['count']) > 50 ? 50 : intval($short_code['count']);
605 }
606
607 $additional['pageToken'] = $pagination->next_token;
608
609 $access = $this->access_all_or_shared($short_code);
610 if ($access['shared']) {
611 $query_url = 'https://photoslibrary.googleapis.com/v1/sharedAlbums';
612 }
613 if ($access['self']) {
614 $query_url = 'https://photoslibrary.googleapis.com/v1/albums';
615 }
616
617 if (!empty($query_url)) {
618 global $photonic_google_refresh_token;
619 if (!empty($photonic_google_refresh_token) && !empty($this->access_token)) {
620 $query_url = add_query_arg('access_token', $this->access_token, $query_url);
621 }
622
623 $call_args = [];
624 $call_args['method'] = 'GET';
625 $call_args['body'] = $additional;
626 $call_args['sslverify'] = PHOTONIC_SSL_VERIFY;
627 $response = wp_remote_request($query_url, $call_args);
628 if (!is_wp_error($response)) {
629 $body = wp_remote_retrieve_body($response);
630 $body = json_decode($body);
631 $inner_albums = $body->albums ?? $body->sharedAlbums;
632 if (!empty($body->nextPageToken)) {
633 $pagination->next_token = $body->nextPageToken;
634 }
635 else {
636 $pagination = new Pagination();
637 }
638
639 $remaining = array_diff($filters, $processed);
640 $remaining = implode(',', $remaining);
641 $inner_code = $short_code;
642 $inner_code['filter'] = $remaining;
643 $inner = $this->build_level_2_objects($inner_albums, $inner_code, $remove, $options, $pagination);
644 $objects = array_merge($objects, $inner);
645 }
646 }
647 }
648
649 return $objects;
650 }
651
652 private function calculate_sizes($photo, &$object, $sizes) {
653 if (!empty($photo->mediaMetadata->width) && !empty($photo->mediaMetadata->height)) {
654 $original_width = $photo->mediaMetadata->width;
655 $original_height = $photo->mediaMetadata->height;
656 $aspect_ratio = $original_width / $original_height;
657
658 foreach ($sizes as $size => $size_value) {
659 if (is_numeric($size_value['size']) && max($original_width, $original_height) >= $size_value['size']) {
660 if (!empty($size_value['crop'])) {
661 $object->{$size} = [
662 'w' => $size_value['size'],
663 'h' => $size_value['size'],
664 ];
665 }
666 else {
667 $object->{$size} = [
668 'w' => ($aspect_ratio > 1) ? $size_value['size'] : ($size_value['size'] * $aspect_ratio),
669 'h' => ($aspect_ratio < 1) ? $size_value['size'] : ($size_value['size'] / $aspect_ratio),
670 ];
671 }
672 }
673 elseif (is_numeric($size_value['size'])) {
674 $object->main_size = [
675 'w' => $original_width,
676 'h' => $original_height,
677 ];
678 }
679 }
680 }
681 }
682
683 private function update_thumbnail_information(&$albums, array $short_code) {
684 $thumbnail_albums = [];
685 $thumbnail_ids = [];
686 $additional = [];
687 foreach ($albums as $album) {
688 $thumbnail_albums[$album->thumbnail_id] = $album;
689 $thumbnail_ids[] = $album->thumbnail_id;
690 }
691
692 $query_url = 'https://photoslibrary.googleapis.com/v1/mediaItems:batchGet';
693 global $photonic_google_refresh_token;
694 if (!empty($photonic_google_refresh_token) && !empty($this->access_token)) {
695 $query_url = add_query_arg('access_token', $this->access_token, $query_url);
696 }
697
698 $additional['mediaItemIds'] = $thumbnail_ids;
699
700 $headers = [];
701 $headers['Accept'] = 'application/json';
702 $headers['Content-Type'] = 'application/json';
703
704 $args = [
705 'method' => 'GET',
706 'headers' => $headers,
707 'sslverify' => PHOTONIC_SSL_VERIFY,
708 'httpversion' => '1.0',
709 ];
710
711 $media_item_query = '';
712 foreach ($thumbnail_ids as $thumbnail_id) {
713 $media_item_query .= '&mediaItemIds=' . $thumbnail_id;
714 }
715
716 $query_url .= $media_item_query;
717
718 $response = wp_remote_request($query_url, $args);
719
720 if (!is_wp_error($response)) {
721 $body = wp_remote_retrieve_body($response);
722 $body = json_decode($body);
723 if (!empty($body->mediaItemResults) && is_array($body->mediaItemResults)) {
724 $media_items = $body->mediaItemResults;
725
726 $sizes = [
727 'thumb_size' => [
728 'size' => $short_code['thumb_size'],
729 'crop' => $short_code['crop_thumb'],
730 ],
731 'tile_size' => [
732 'size' => $short_code['tile_size'],
733 ],
734 ];
735
736 foreach ($media_items as $media_item) {
737 if (!empty($media_item->mediaItem) && !empty($thumbnail_albums[$media_item->mediaItem->id])) {
738 $album = $thumbnail_albums[$media_item->mediaItem->id];
739 $this->calculate_sizes($media_item->mediaItem, $album, $sizes);
740 }
741 }
742 }
743 }
744 }
745
746 /**
747 * @param $album
748 * @param $short_code
749 * @return Album
750 */
751 private function process_album($album, $short_code) {
752 if (empty($album->coverPhotoBaseUrl)) {
753 return null;
754 }
755
756 /* $sizes = [
757 'thumb_size' => [
758 'size' => $short_code['thumb_size'],
759 'crop' => $short_code['crop_thumb'],
760 ],
761 'tile_size' => [
762 'size' => $short_code['tile_size'],
763 ],
764 ];*/
765
766 $photonic_album = new Album();
767
768 $internal_short_code = $short_code;
769 $internal_short_code['layout'] = empty($short_code['photo_layout']) ? $short_code['layout'] : $short_code['photo_layout'];
770 unset($internal_short_code['filter']);
771 unset($internal_short_code['filter_type']);
772 unset($internal_short_code['next_token']);
773 $internal_short_code['view'] = 'photos';
774 $internal_short_code['album_id'] = $album->id;
775
776 $photonic_album->id = "{$album->id}";
777
778 $photonic_album->thumbnail = esc_url($album->coverPhotoBaseUrl . "=w{$short_code['thumb_size']}-h{$short_code['thumb_size']}" . ('crop' === $short_code['crop_thumb'] ? '-c' : ''));
779 $photonic_album->tile_image = esc_url($album->coverPhotoBaseUrl . "=w{$short_code['tile_size']}-h{$short_code['tile_size']}");
780 $photonic_album->thumbnail_id = $album->coverPhotoMediaItemId;
781
782 // $this->calculate_sizes($album, $photonic_album, $sizes);
783
784 $photonic_album->main_page = '';
785
786 $photonic_album->title = wp_kses_post($album->title);
787 $photonic_album->counter = empty($album->totalMediaItems) ? $album->mediaItemsCount : $album->totalMediaItems;
788
789 global $photonic_gallery_template_page;
790 if ('page' === $short_code['popup'] && !empty($photonic_gallery_template_page) && is_string(get_post_status($photonic_gallery_template_page))) {
791 $photonic_album->gallery_url = $this->get_gallery_url(
792 $internal_short_code,
793 [
794 'title' => $photonic_album->title,
795 ]
796 );
797 }
798
799 return $photonic_album;
800 }
801
802 private function access_all_or_shared($short_code): array {
803 $all_or_shared = [];
804 $access = explode(',', $short_code['access']);
805 if (empty($access) || (in_array('shared', $access, true) && in_array('not-shared', $access, true)) || in_array('all', $access, true)) {
806 $all_or_shared['self'] = true;
807 $all_or_shared['shared'] = false;
808 }
809 elseif (count($access) === 1 && in_array('not-shared', $access, true)) {
810 $all_or_shared['self'] = true;
811 $all_or_shared['shared'] = true;
812 }
813 elseif (count($access) === 1 && in_array('shared', $access, true)) {
814 $all_or_shared['self'] = false;
815 $all_or_shared['shared'] = true;
816 }
817 return $all_or_shared;
818 }
819
820 public function authentication_URL() {
821 return 'https://accounts.google.com/o/oauth2/auth';
822 }
823
824 public function access_token_URL() {
825 return 'https://accounts.google.com/o/oauth2/token';
826 }
827
828 protected function set_token_validity($validity) {
829 $this->refresh_token_valid = $validity;
830 }
831
832 public function execute_helper($args = []): string {
833 if (empty($args['album_type']) || !in_array($args['album_type'], ['self', 'shared'], true)) {
834 $album_type = 'self';
835 }
836 else {
837 $album_type = $args['album_type'];
838 }
839 $query_url = ('self' === $album_type)
840 ? 'https://photoslibrary.googleapis.com/v1/albums'
841 : 'https://photoslibrary.googleapis.com/v1/sharedAlbums';
842 $parameters = [
843 'access_token' => $this->access_token,
844 'pageSize' => 50,
845 ];
846 if (!empty($args['nextPageToken'])) {
847 $parameters['pageToken'] = sanitize_text_field($args['nextPageToken']);
848 }
849
850 $call_args = [];
851 $call_args['method'] = 'GET';
852 $call_args['body'] = $parameters;
853 $call_args['sslverify'] = PHOTONIC_SSL_VERIFY;
854 $response = wp_remote_request($query_url, $call_args);
855
856 if (!is_wp_error($response)) {
857 if (isset($response['response']) && isset($response['response']['code'])) {
858 if (200 === $response['response']['code']) {
859 $body = json_decode(wp_remote_retrieve_body($response));
860 if ((isset($body->albums) && !empty($body->albums) && is_array($body->albums)) ||
861 (isset($body->sharedAlbums) && !empty($body->sharedAlbums) && is_array($body->sharedAlbums))) {
862 $albums = !empty($body->albums) ? $body->albums : $body->sharedAlbums;
863
864 $ret = "<table>\n";
865 $ret .= "\t<tr>\n";
866 $ret .= "\t\t<th>Album Title</th>\n";
867 $ret .= "\t\t<th>Thumbnail</th>\n";
868 $ret .= "\t\t<th>Album ID</th>\n";
869 $ret .= "\t\t<th>Media Count</th>\n";
870 $ret .= "\t</tr>\n";
871
872 foreach ($albums as $album) {
873 $ret .= "\t<tr>\n";
874 $ret .= "\t\t<td>" . wp_specialchars_decode(stripslashes(wp_filter_nohtml_kses($album->title)), ENT_QUOTES) . "</td>\n";
875 $ret .= "\t\t<td><img alt='thumbnail' src='" . esc_url($album->coverPhotoBaseUrl) . "=w75-h75-c' /></td>\n";
876 $ret .= "\t\t<td>" . wp_filter_nohtml_kses($album->id) . "</td>\n";
877 $ret .= "\t\t<td>" . wp_filter_nohtml_kses($album->mediaItemsCount) . "</td>\n";
878 $ret .= "\t</tr>\n";
879 }
880
881 if (!empty($body->nextPageToken)) {
882 $ret .= "\t<tr>\n";
883 $ret .= "\t\t<td colspan='4'>\n";
884 $ret .= '<input type="button" value="' . esc_attr__('Load More', 'photonic') . '" name="photonic-google-album-more" class="photonic-helper-more" data-photonic-token="' . esc_attr($body->nextPageToken) . '" data-photonic-platform="google" data-photonic-access="' . esc_attr($album_type) . '"/>';
885 $ret .= "\t\t</td>\n";
886 $ret .= "\t</tr>\n";
887 }
888
889 $ret .= "</table>\n";
890
891 return '<div class="photonic-helper">' . $ret . '</div>';
892 }
893 else {
894 return '<div class="photonic-helper">' . esc_html__('No albums found', 'photonic') . '</div>';
895 }
896 }
897 else {
898 Photonic::log($response['response']);
899 return '<div class="photonic-helper">' . sprintf(esc_html__('No data returned. Error code %s', 'photonic'), $response['response']['code']) . '</div>';
900 }
901 }
902 else {
903 Photonic::log($response);
904 return '<div class="photonic-helper">' . esc_html__('No data returned. Empty response, or empty error code.', 'photonic') . '</div>';
905 }
906 }
907 else {
908 return '<div class="photonic-helper">' . $response->get_error_message() . '</div>';
909 }
910 }
911
912 public function renew_token($refresh_token): array {
913 $token = [];
914 $error = '';
915 $response = Photonic::http(
916 $this->access_token_URL(),
917 'POST',
918 [
919 'client_id' => $this->client_id,
920 'client_secret' => $this->client_secret,
921 'refresh_token' => $refresh_token,
922 'grant_type' => 'refresh_token'
923 ]
924 );
925
926 if (!is_wp_error($response)) {
927 $token = $this->parse_token($response);
928 if (!empty($token)) {
929 $token['client_id'] = $this->client_id;
930 }
931 set_transient('photonic_' . $this->provider . '_token', $token, $token['oauth_token_expires']);
932 if (empty($token)) {
933 $error = print_r(wp_remote_retrieve_body($response), true);
934 }
935 }
936 else {
937 $error = $response->get_error_message();
938 }
939
940 return [$token, $error];
941 }
942
943 /**
944 * Not applicable for Google. We make this always return 0.
945 *
946 * @param int $soon_limit
947 * @return int|null
948 */
949 public function is_token_expiring_soon($soon_limit) {
950 return 0;
951 }
952
953 /**
954 * @param $body
955 * @param array $short_code
956 * @return Pagination
957 */
958 public function get_pagination($body, array $short_code = []): Pagination {
959 $pagination = new Pagination();
960 if (!empty($body->nextPageToken)) {
961 $pagination->total = 10;
962 $pagination->start = 0;
963 $pagination->end = 1;
964 $pagination->per_page = $short_code;
965 $pagination->next_token = $body->nextPageToken;
966 }
967 return $pagination;
968 }
969 }
970