PluginProbe
Photonic Gallery & Lightbox for Flickr, SmugMug & Others / 3.37
Photonic Gallery & Lightbox for Flickr, SmugMug & Others v3.37
3.37 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 All 142 releases
photonic / Platforms / Google_Photos.php

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

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