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 / SmugMug.php

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

957 lines 35.7 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\Collection;
7 use Photonic_Plugin\Components\Error;
8 use Photonic_Plugin\Components\Header;
9 use Photonic_Plugin\Components\Pagination;
10 use Photonic_Plugin\Components\Photo_List;
11 use Photonic_Plugin\Core\Photonic;
12 use Photonic_Plugin\Components\Album;
13 use Photonic_Plugin\Components\Photo;
14 use WP_Error;
15
16 require_once 'OAuth1.php';
17 require_once 'Level_One_Module.php';
18 require_once 'Level_Two_Module.php';
19 require_once 'Pageable.php';
20 require_once PHOTONIC_PATH . '/Components/Collection.php';
21
22 class SmugMug extends OAuth1 implements Level_One_Module, Level_Two_Module, Pageable {
23 private $highlights_processed;
24 private static $instance = null;
25
26 protected function __construct() {
27 parent::__construct();
28 global $photonic_smug_api_key, $photonic_smug_api_secret, $photonic_smug_access_token, $photonic_smug_disable_title_link, $photonic_smug_token_secret, $photonic_smug_show_buy_link;
29
30 $this->api_key = !empty($photonic_smug_api_key) ? trim($photonic_smug_api_key) : '86MZ8N8TqJf5x2fQ4FRWXRtJ3C6Jm7XV';
31 $this->api_secret = trim($photonic_smug_api_secret);
32 $this->token = trim($photonic_smug_access_token);
33 $this->token_secret = trim($photonic_smug_token_secret);
34 $this->provider = 'smug';
35 $this->link_lightbox_title = empty($photonic_smug_disable_title_link);
36 $this->oauth_version = '1.0a';
37 $this->base_url = 'https://api.smugmug.com/api/v2/';
38 $this->show_buy_link = !empty($photonic_smug_show_buy_link);
39
40 $this->doc_links = [
41 'general' => 'https://aquoid.com/plugins/photonic/smugmug/',
42 'photos' => 'https://aquoid.com/plugins/photonic/smugmug/smugmug-photos/',
43 'albums' => 'https://aquoid.com/plugins/photonic/smugmug/smugmug-albums/',
44 'tree' => 'https://aquoid.com/plugins/photonic/smugmug/smugmug-tree/',
45 'folders' => 'https://aquoid.com/plugins/photonic/smugmug/folders/',
46 ];
47
48 $this->set_oauth_done();
49 $this->highlights_processed = [];
50 }
51
52 /**
53 * The main gallery builder for SmugMug. SmugMug takes the following parameters:
54 * - nick_name = The nickname of the user. This is mandatory for SmugMug.
55 * - view = tree | albums | album | images. If left blank, a value of 'tree' is assumed.
56 * - columns = The number of columns to show the output in
57 * - album or album_key = The album slug, which is the AlbumKey. This is needed if view='album' or 'images'. Prior to version 1.57 album_id was required
58 * - empty = true | false. If true, empty albums and categories are returned in the response, otherwise they are ignored.
59 * - columns = The number of columns to return the output in. Optional.
60 *
61 * @param array $attr
62 * @return array
63 */
64 public function get_gallery_images($attr = []): array {
65 global $photonic_smug_title_caption, $photonic_smug_album_sort_order;
66 global $photonic_smug_thumb_size, $photonic_smug_main_size, $photonic_smug_tile_size, $photonic_smug_video_size, $photonic_smug_media, $photonic_smug_default_user;
67 $this->push_to_stack('Get Gallery Images');
68
69 if (empty($this->api_key)) {
70 $this->pop_from_stack();
71 return [new Error(esc_html__('SmugMug API Key not defined.', 'photonic') . Photonic::doc_link($this->doc_links['general']))];
72 }
73
74 $attr = array_merge(
75 $this->common_parameters,
76 [
77 'caption' => $photonic_smug_title_caption,
78 'thumb_size' => $photonic_smug_thumb_size,
79 'main_size' => $photonic_smug_main_size,
80 'tile_size' => $photonic_smug_tile_size,
81 'video_size' => $photonic_smug_video_size,
82
83 'empty' => 'false',
84 'view' => 'tree',
85 'nick_name' => $photonic_smug_default_user,
86 'start' => 1,
87 'count' => 500,
88 'photo_count' => 500,
89 'media' => $photonic_smug_media,
90 'album_sort_order' => $photonic_smug_album_sort_order,
91 ],
92 $attr
93 );
94
95 /*
96 * if ($attr['main_size'] == 'custom') {
97 if (!empty($attr['custom_main_size'])) {
98 $attr['main_size'] = $attr['custom_main_size'];
99 }
100 else {
101 $attr['main_size'] = 'Largest';
102 }
103 }
104 if ($attr['thumb_size'] == 'custom') {
105 if (!empty($attr['custom_thumb_size'])) {
106 $attr['thumb_size'] = $attr['custom_thumb_size'];
107 }
108 else {
109 $attr['thumb_size'] = 'Tiny';
110 }
111 }
112 if ($attr['tile_size'] == 'custom' && !empty($attr['custom_tile_size'])) {
113 if (!empty($attr['custom_tile_size'])) {
114 $attr['tile_size'] = $attr['custom_tile_size'];
115 }
116 else {
117 $attr['tile_size'] = 'same';
118 }
119 }
120 */
121
122 $attr = array_map('trim', $attr);
123
124 $args = [
125 'APIKey' => $this->api_key,
126 '_accept' => 'application/json'
127 ];
128
129 $chained_calls = [];
130
131 $args['_expandmethod'] = 'inline';
132 $args['_verbosity'] = '1';
133
134 if ('tree' === $attr['view'] || 'albums' === $attr['view']) {
135 if (empty($attr['nick_name'])) {
136 $this->pop_from_stack();
137 return [new Error(sprintf(esc_html__('The %1$s attribute is required for the %2$s and %3$s views.', 'photonic'), '<code>nick_name</code>', '<code>tree</code>', '<code>albums</code>'))];
138 }
139 }
140
141 switch ($attr['view']) {
142 case 'albums':
143 if (!empty($attr['site_password'])) {
144 $chained_calls[] = $this->base_url . 'user/' . $attr['nick_name'] . '!unlock';
145 $args['Password'] = $attr['site_password'];
146 }
147
148 $chained_calls[] = $this->base_url . 'user/' . $attr['nick_name'] . '!albums';
149 $args['_expand'] = 'HighlightImage.ImageSizes,HighlightImage.ImageSizeDetails,HighlightImage.LargestImage';
150 $args['Order'] = $attr['album_sort_order'];
151
152 if (500 === absint($attr['count'])) {
153 $attr['count'] = 100;
154 $attr['photo_count'] = 500;
155 }
156
157 break;
158
159 case 'album':
160 case 'images':
161 $album_field = '';
162 $is_album = true;
163 if (!empty($attr['album_key'])) {
164 $album_field = $attr['album_key'];
165 }
166 elseif (!empty($attr['album'])) {
167 if (stripos($attr['album'], '_') === false) {
168 $album_field = $attr['album'];
169 }
170 else {
171 $album_field = substr($attr['album'], stripos($attr['album'], '_') + 1);
172 }
173 }
174 else {
175 $is_album = false;
176 }
177
178 if (!empty($attr['password']) || !empty($attr['site_password'])) {
179 $args['Password'] = !empty($attr['password']) ? $attr['password'] : $attr['site_password'];
180 $chained_calls[] = $this->base_url . 'album/' . $album_field . '!unlock';
181 }
182
183 if (!empty($attr['text'])) {
184 $args['Text'] = $attr['text'];
185 }
186 if (!empty($attr['keywords'])) {
187 $keywords = explode(',', $attr['keywords']);
188 $keywords = wp_json_encode($keywords);
189 $args['Keywords'] = $keywords;
190 }
191 if (!empty($attr['sort_method'])) {
192 $args['SortMethod'] = $attr['sort_method'];
193 }
194 if (!empty($attr['sort_order'])) {
195 $args['SortDirection'] = $attr['sort_order'];
196 }
197
198 /*
199 Weird issue: If _expand is called with a comma-separated list, and is passed in $args[], it doesn't expand everything.
200 So, for comma-separated values, _expand is appended to the URL.
201 However, if we do this, and OAuth is being used, the signature generation gets messed up, as the signature is generated with the "?_expand=...",
202 which doesn't match the signature for when we make a call with $signed_args, because SmugMug internally parses out the URL and puts _expand=...
203 somewhere in the middle. So, to fix, for authenticated calls we are separating out the _expand parameter and making two calls. Additionally we are
204 moving the parameter from the URL to the array in the generate_signature method.
205
206 Update, v1.59 - Using _expand and combining both the expansions prevents the usage of the <code>start</code> and <code>count</code>
207 attributes from the album node. So, splitting the call anyway, to introduce the album!images node...
208 */
209 if ($is_album) {
210 $chained_calls[] = $this->base_url . 'album/' . $album_field . '?_expand=HighlightImage.ImageSizes,HighlightImage.ImageSizeDetails,HighlightImage.LargestImage,Node.FormattedValues';
211 // if ('images' === $attr['view']) { // Fix for https://wordpress.org/support/topic/smugmug-filter-on-keywords-not-working-for-me/
212 if (!empty($args['Keywords']) || !empty($args['Text'])) { // Fix for https://wordpress.org/support/topic/smugmug-filter-on-keywords-not-working-for-me/
213 $args['Scope'] = 'https://api.smugmug.com/api/v2/album/' . $album_field;
214 }
215 else {
216 $chained_calls[] = $this->base_url . 'album/' . $album_field . '!images?_expand=ImageSizes,ImageSizeDetails,LargestImage';
217 }
218 }
219 elseif (!empty($attr['nick_name']) || !empty($attr['folder'])) {
220 if (!empty($attr['folder'])) {
221 $args['Scope'] = 'https://api.smugmug.com/api/v2/node/' . $attr['folder'];
222 }
223 else {
224 $args['Scope'] = 'https://api.smugmug.com/api/v2/user/' . $attr['nick_name'];
225 }
226 }
227
228 if (!empty($args['Scope'])) {
229 // Only search if you have view='images'
230 $chained_calls[] = 'https://api.smugmug.com/api/v2/image!search?_expand=ImageSizes,ImageSizeDetails,LargestImage';
231 }
232
233 break;
234
235 case 'folder':
236 if (!empty($attr['folder'])) {
237 $this->prepare_chained_calls_for_folder($chained_calls, $attr['folder'], $attr['count']);
238 }
239 break;
240
241 case 'tree':
242 default:
243 $this->push_to_stack('Initial user tree');
244 $initial_call = $this->base_url . 'user/' . $attr['nick_name'];
245 $initial_response = Photonic::http($initial_call, 'GET', $args, null, 90, PHOTONIC_SSL_VERIFY);
246
247 if (!empty($attr['site_password'])) {
248 $chained_calls[] = $this->base_url . 'user/' . $attr['nick_name'] . '!unlock';
249 $args['Password'] = $attr['site_password'];
250 }
251
252 if (is_array($initial_response['response']) && isset($initial_response['response']['code']) && 200 === $initial_response['response']['code']) {
253 $body = $initial_response['body'];
254 $body = json_decode($body);
255 $body = $body->Response;
256 $node = $body->User->Uris->Node->Uri;
257 $pieces = explode('/', $node);
258 $node = array_pop($pieces);
259 $this->prepare_chained_calls_for_folder($chained_calls, $node, $attr['count']);
260 }
261 $this->pop_from_stack();
262 break;
263 }
264
265 if (!empty($attr['nick_name'])) {
266 $args['NickName'] = $attr['nick_name'];
267 }
268
269 if (!empty($attr['start'])) {
270 $args['start'] = $attr['start'];
271 }
272
273 if (!empty($attr['count'])) {
274 $args['count'] = $attr['count'];
275 }
276 $attr['photo_count'] = empty($attr['photo_count']) ? $attr['count'] : $attr['photo_count'];
277
278 $attr['overlay_size'] = empty($attr['overlay_size']) ? $attr['thumb_size'] : $attr['overlay_size'];
279 $attr['overlay_video_size'] = empty($attr['overlay_video_size']) ? $attr['video_size'] : $attr['overlay_video_size'];
280
281 $header_display = $this->get_header_display($attr);
282 $attr['header_display'] = $header_display;
283
284 $out = $this->make_chained_calls($chained_calls, $args, $attr);
285 $this->pop_from_stack();
286
287 if (!empty($this->stack_trace[$this->gallery_index])) {
288 $out[] = $this->stack_trace[$this->gallery_index];
289 }
290 return $out;
291 }
292
293 /**
294 * Runs a sequence of web-service calls to get information. Most often a single web-service call with the "Extras" parameter suffices for SmugMug.
295 * But there are some scenarios, e.g. clicking on an album to get a popup of all images in that album, where you need to chain the calls for the header.
296 *
297 * @param $chained_calls
298 * @param $smug_args
299 * @param $short_code
300 * @return array
301 */
302 private function make_chained_calls($chained_calls, $smug_args, $short_code): array {
303 $this->push_to_stack('Make chained calls');
304 $header_done = false;
305
306 if (is_array($chained_calls) && count($chained_calls) > 0) {
307 $components = [];
308
309 $cookies = [];
310 $header = null;
311
312 foreach ($chained_calls as $chained_call) {
313 $this->push_to_stack("Chained call: $chained_call");
314 if (false !== stripos($chained_call, '!unlock')) {
315 $response = Photonic::http($chained_call, 'POST', $smug_args);
316 if (!is_wp_error($response)) {
317 if (is_array($response['response']) && isset($response['response']['code']) && 200 === $response['response']['code']) {
318 $cookies = $response['cookies'];
319 foreach ($response['cookies'] as $cookie) {
320 $cookies[$cookie->name] = $cookie->value;
321 }
322 }
323 }
324 else {
325 $this->pop_from_stack(); // 'Chained call'
326 $this->pop_from_stack(); // 'Make chained calls'
327 return [$this->wp_error_message($response)];
328 }
329 }
330
331 // The following is NOT an "else" because we are modifying the headers with the cookies, if required
332 if (false === stripos($chained_call, '!unlock')) {
333 $this->push_to_stack('Making call');
334 if ($this->oauth_done && empty($cookies)) {
335 $signed_args = $this->sign_call($chained_call, 'GET', $smug_args);
336 $response = Photonic::http($chained_call, 'GET', $signed_args, null, 90, PHOTONIC_SSL_VERIFY, [], $cookies);
337 }
338 else {
339 $response = Photonic::http($chained_call, 'GET', $smug_args, null, 90, PHOTONIC_SSL_VERIFY, [], $cookies);
340 }
341
342 $this->pop_from_stack();
343
344 if (!is_wp_error($response)) {
345 $body = $response['body'];
346 $body = json_decode($body);
347
348 if (isset($body->Code) && 200 === $body->Code) {
349 $body = $body->Response;
350 if (false !== stripos($chained_call, '!albums')) {
351 $albums = $body->Album;
352 if (is_array($albums) && count($albums) > 0) {
353 $filter = [];
354 if (!empty($short_code['filter'])) {
355 $filter = explode(',', $short_code['filter']);
356 }
357 $components[] = $this->process_albums($albums, '', $filter, $short_code, $body->Pages);
358 }
359 }
360 elseif ((false !== stripos($chained_call, '/album/') || (false !== stripos($chained_call, '/image!search') && false !== stripos($chained_call, rawurlencode('/api/v2/album/')))) && false === stripos($chained_call, '!unlock')) {
361 $password_check_passed = true;
362 if (isset($body->Album)) {
363 $album = $body->Album;
364 $password_check_passed = ('Password' === $album->SecurityType && isset($album->Uris->AlbumImages)) || 'Password' !== $album->SecurityType;
365
366 global $photonic_smug_disable_title_link, $photonic_smug_hide_album_thumbnail, $photonic_smug_hide_album_title, $photonic_smug_hide_album_photo_count;
367 $hidden = [
368 'thumbnail' => !empty($photonic_smug_hide_album_thumbnail),
369 'title' => !empty($photonic_smug_hide_album_title),
370 'counter' => !empty($photonic_smug_hide_album_photo_count),
371 ];
372 $counters = ['photos' => $album->ImageCount ?? 0];
373
374 $display = $short_code['display'] ?? 'local';
375
376 if (!$header_done) {
377 $header = new Header();
378 $header->title = wp_kses_post($album->Name);
379 $header->page_url = $album->WebUri;
380
381 if (isset($album->FormattedValues->Description->html) && !empty($album->FormattedValues->Description->html)) {
382 // wp_kses_post is OK here, as we want to permit <a> and other tags - this markup is printed directly on a page and not in a lightbox.
383 $header->description = wp_kses_post($album->FormattedValues->Description->html);
384 }
385 elseif (isset($album->Uris->Node->Node->FormattedValues->Description->html) && !empty($album->Uris->Node->Node->FormattedValues->Description->html)) {
386 // wp_kses_post is OK here, as we want to permit <a> and other tags - this markup is printed directly on a page and not in a lightbox.
387 $header->description = wp_kses_post($album->Uris->Node->Node->FormattedValues->Description->html);
388 }
389 else {
390 // wp_kses_post is OK here, as we want to permit <a> and other tags - this markup is printed directly on a page and not in a lightbox.
391 $header->description = wp_kses_post($album->Description ?? '');
392 }
393
394 if (isset($album->Uris->HighlightImage->Image)) {
395 $header->thumb_url = $album->Uris->HighlightImage->Image->ThumbnailUrl;
396 }
397 elseif (isset($album->Uris->AlbumImages->AlbumImage) && is_array($album->Uris->AlbumImages->AlbumImage)) {
398 $rand = wp_rand(0, $album->ImageCount - 1);
399 $header->thumb_url = $album->Uris->AlbumImages->AlbumImage[$rand]->ThumbnailUrl;
400 }
401
402 $header->header_for = 'album';
403 $header->hidden_elements = $this->get_hidden_headers($short_code['header_display'], $hidden);
404 $header->counters = $counters;
405 $header->enable_link = empty($photonic_smug_disable_title_link);
406 $header->display_location = $display;
407
408 $header_done = true;
409 }
410 }
411 if ($password_check_passed) {
412 $comp = $this->process_images($response, $short_code, $header);
413 $components = array_merge($components, $comp);
414 }
415 else {
416 $components[] = $this->password_protected;
417 }
418 }
419 elseif (false !== stripos($chained_call, '/user/')) {
420 $root_node = $body->User->Uris->Node->Node;
421 $components[] = $this->get_collection($root_node, $short_code);
422 }
423 elseif (false !== stripos($chained_call, '/node/')) {
424 $components[] = $this->get_collection($body->Node, $short_code);
425 }
426 elseif (false !== stripos($chained_call, '/image!search')) {
427 $comp = $this->process_images($response, $short_code, $header);
428 $components = array_merge($components, $comp);
429 }
430 }
431 }
432 else {
433 $this->pop_from_stack(); // 'Chained call'
434 $this->pop_from_stack(); // 'Make chained calls'
435 return [$this->wp_error_message($response)];
436 }
437 }
438 $this->pop_from_stack();
439 }
440 $this->pop_from_stack();
441 return $components;
442 }
443 $this->pop_from_stack();
444 return [];
445 }
446
447 public function get_config_object($count = 500, $levels = 5): array {
448 $base = [
449 'expand' => [
450 'HighlightImage' => [
451 'expand' => [
452 'ImageSizes' => [],
453 'ImageSizeDetails' => [],
454 'LargestImage' => [],
455 ],
456 ],
457 'NodeCoverImage' => [
458 'expand' => [
459 'ImageSizes' => [],
460 'ImageSizeDetails' => [],
461 'LargestImage' => [],
462 ],
463 ],
464 'ChildNodes' => [
465 'args' => [
466 'count' => $count
467 ],
468 ],
469 ],
470 ];
471
472 $level = 1;
473 $combined = $base;
474 while ($level < absint($levels)) {
475 $combined['expand']['ChildNodes']['expand'] = $combined['expand'];
476 $level++;
477 }
478 return $combined;
479 }
480
481 private function prepare_chained_calls_for_folder(&$chained_calls, $folder_node, $count) {
482 $config = $this->get_config_object($count);
483 $config_str = wp_json_encode($config);
484
485 if (!empty($folder_node)) {
486 $chained_calls[] = $this->base_url . 'node/' . $folder_node . '?_config=' . $config_str;
487 }
488 }
489
490 /**
491 * @param $node
492 * @param $short_code
493 * @param string $indent
494 * @param int $level
495 * @return Collection
496 */
497 private function get_collection($node, $short_code, string $indent = '', int $level = 0): Collection {
498 $collection = new Collection();
499 $collection->indent = $indent;
500
501 $albums = [];
502 $folders = [];
503
504 if (is_array($node)) {
505 foreach ($node as $child) {
506 if ('Album' === $child->Type) {
507 if (!in_array($child->NodeID . '-' . $this->gallery_index, $this->highlights_processed, true)) {
508 $this->highlights_processed[] = $child->NodeID . '-' . $this->gallery_index;
509 $albums[] = $child;
510 }
511 }
512 elseif ('Folder' === $child->Type) {
513 $folders[] = $child;
514 }
515 }
516 }
517 elseif ('Folder' === $node->Type) {
518 if (isset($node->Uris->ChildNodes->Node)) {
519 $child_nodes = $node->Uris->ChildNodes->Node;
520 foreach ($child_nodes as $child) {
521 if ('Album' === $child->Type) {
522 if (!in_array($child->NodeID . '-' . $this->gallery_index, $this->highlights_processed, true)) {
523 $this->highlights_processed[] = $child->NodeID . '-' . $this->gallery_index;
524 }
525 else {
526 continue;
527 }
528
529 if (isset($child->Uris->Album->Album)) {
530 $albums[] = $child->Uris->Album->Album; // Tree?
531 }
532 else {
533 $albums[] = $child; // Node?
534 }
535 }
536 elseif ('Folder' === $child->Type) {
537 $folders[] = $child;
538 }
539 }
540 }
541 }
542
543 if (count($albums) > 0 || count($folders) > 0) {
544 if (!is_array($node)) {
545 $hidden = ['thumbnail' => true, 'title' => false, 'counter' => false];
546
547 $header = new Header();
548 $header->title = wp_kses_post($node->Name);
549 $header->description = wp_kses_post($node->Description ?? '');
550 $header->header_for = 'folder';
551 $header->hidden_elements = $this->get_hidden_headers($short_code['header_display'], $hidden);
552 $header->enable_link = false;
553
554 $collection->header = $header;
555 }
556 }
557
558 if (count($albums) > 0) {
559 $collection->album_list = $this->process_albums($albums, $indent . "\t\t", [], $short_code, null);
560 }
561
562 if (count($folders) > 0) {
563 foreach ($folders as $folder) {
564 $collection->collections[] = $this->get_collection($folder, $short_code, $indent . "\t\t", $level + 1);
565 }
566 }
567
568 return $collection;
569 }
570
571 /**
572 * Parse an array of album objects returned by the SmugMug API, then return an appropriate response.
573 *
574 * @param $albums
575 * @param string $indent
576 * @param array $album_filter
577 * @param $short_code
578 * @param $pages
579 * @return Album_List
580 */
581 private function process_albums($albums, string $indent, array $album_filter, $short_code, $pages): Album_List {
582 global $photonic_smug_albums_album_per_row_constraint, $photonic_smug_albums_album_constrain_by_count, $photonic_smug_albums_album_title_display, $photonic_smug_hide_albums_album_photos_count_display, $photonic_gallery_template_page;
583 $objects = $this->build_level_2_objects($albums, $short_code, $album_filter);
584
585 $pagination = $this->get_pagination($pages);
586 $row_constraints = ['constraint-type' => $photonic_smug_albums_album_per_row_constraint, 'count' => $photonic_smug_albums_album_constrain_by_count];
587
588 $album_list = new Album_List($short_code);
589 $album_list->albums = $objects;
590 $album_list->title_position = $photonic_smug_albums_album_title_display;
591 $album_list->row_constraints = $row_constraints;
592 $album_list->type = 'albums';
593 $album_list->singular_type = 'album';
594 $album_list->level_1_count_display = $photonic_smug_hide_albums_album_photos_count_display;
595 $album_list->pagination = $pagination;
596 $album_list->indent = $indent;
597 $album_list->album_opens_gallery = ('page' === $short_code['popup'] && !empty($photonic_gallery_template_page) && is_string(get_post_status($photonic_gallery_template_page)));
598
599 return $album_list;
600 }
601
602 /**
603 * Takes a response, then parses out the images from that response and returns a set of thumbnails for it. This method handles
604 * local / template / lightbox images as well as images in a modal panel.
605 *
606 * @param $response
607 * @param array $short_code
608 * @param $header
609 * @return array
610 */
611 private function process_images($response, array $short_code, $header): array {
612 global $photonic_smug_photos_per_row_constraint, $photonic_smug_photos_constrain_by_count,
613 $photonic_smug_photo_title_display, $photonic_smug_photo_pop_title_display;
614 $body = $response['body'];
615 $body = json_decode($body);
616
617 $components = [];
618 if ('Ok' === $body->Message) {
619 $body = $body->Response;
620 if (isset($body->Album)) {
621 $album = $body->Album;
622 $images = $album->Uris->AlbumImages;
623 }
624 elseif (isset($body->Image)) {
625 $images = $body->Image;
626 }
627 else {
628 $images = $body;
629 }
630
631 $photo_objects = [];
632 if (isset($images->AlbumImage)) {
633 $photo_objects = $this->build_level_1_objects($images->AlbumImage, $short_code);
634 }
635 if (is_array($images)) {
636 $photo_objects = $this->build_level_1_objects($images, $short_code);
637 }
638
639 $pages = $images->Pages ?? (is_array($images) && isset($body->Pages) ? $body->Pages : null);
640 $pagination = $this->get_pagination($pages);
641
642 if (!empty($photo_objects)) {
643 if (!empty($header)) {
644 $components[] = $header;
645 }
646
647 if (isset($short_code['display']) && 'modal' === $short_code['display']) {
648 $row_constraints = ['constraint-type' => 'padding'];
649 $title_position = $photonic_smug_photo_pop_title_display;
650 }
651 else {
652 $row_constraints = ['constraint-type' => $photonic_smug_photos_per_row_constraint, 'count' => $photonic_smug_photos_constrain_by_count];
653 $title_position = $photonic_smug_photo_title_display;
654 }
655
656 $photo_list = new Photo_List($short_code);
657 $photo_list->photos = $photo_objects;
658
659 $photo_list->title_position = $title_position;
660 $photo_list->row_constraints = $row_constraints;
661 $photo_list->parent = 'album';
662 $photo_list->pagination = $pagination;
663
664 $components[] = $photo_list;
665 }
666 }
667 return $components;
668 }
669
670 private function construct_custom_size($custom, $sizes): string {
671 if (isset($sizes->LargeImageUrl)) {
672 $custom = str_replace('ImageUrl', '', $custom);
673 $large = $sizes->LargeImageUrl;
674 $split = explode('/', $large);
675 $split[count($split) - 2] = $custom;
676 $image_parts = explode('.', $split[count($split) - 1]);
677 $image_name_parts = explode('-', $image_parts[count($image_parts) - 2]);
678 $image_name_parts[count($image_name_parts) - 1] = $custom;
679 $image_parts[count($image_parts) - 2] = implode('-', $image_name_parts);
680 $split[count($split) - 1] = implode('.', $image_parts);
681 return implode('/', $split);
682 }
683 return '';
684 }
685
686 public function build_level_1_objects($response, array $short_code, $module_parameters = [], $options = []): array {
687 $thumb = "{$short_code['thumb_size']}ImageUrl";
688 $main = "{$short_code['main_size']}ImageUrl";
689 $tile = (empty($short_code['tile_size']) || 'same' === $short_code['tile_size']) ? $main : "{$short_code['tile_size']}ImageUrl";
690
691 $media = explode(',', $short_code['media']);
692 $videos_ok = in_array('videos', $media, true) || in_array('all', $media, true);
693 $photos_ok = in_array('photos', $media, true) || in_array('all', $media, true);
694
695 $photo_objects = [];
696 $sizes = [
697 'thumb_size' => sanitize_text_field($short_code['thumb_size']),
698 'main_size' => sanitize_text_field($short_code['main_size']),
699 'tile_size' => sanitize_text_field((empty($short_code['tile_size']) || 'same' === $short_code['tile_size']) ? $short_code['main_size'] : $short_code['tile_size']),
700 ];
701
702 if (is_array($response) && count($response) > 0) {
703 foreach ($response as $image) {
704 if ((empty($image->IsVideo) && !$photos_ok) || (!empty($image->IsVideo) && !$videos_ok)) {
705 continue;
706 }
707 $image_sizes = $image->Uris->ImageSizes->ImageSizes;
708
709 $photonic_photo = new Photo();
710 $photonic_photo->thumbnail = esc_url($image_sizes->{$thumb} ?? $this->construct_custom_size($thumb, $image_sizes));
711 $photonic_photo->main_image = esc_url($image_sizes->{$main} ?? $this->construct_custom_size($main, $image_sizes)); // $image_sizes->{$main};
712 $photonic_photo->tile_image = esc_url($image_sizes->{$tile} ?? $this->construct_custom_size($tile, $image_sizes)); // $image_sizes->{$tile};
713
714 $this->calculate_sizes($image, $photonic_photo, $sizes);
715
716 $photonic_photo->title = wp_kses_post($image->Title);
717 $photonic_photo->alt_title = $photonic_photo->title;
718
719 if (isset($image->FormattedValues->Caption->html) && !empty($image->FormattedValues->Caption->html)) {
720 $photonic_photo->description = wp_kses($image->FormattedValues->Caption->html, Photonic::$safe_description_tags);
721 }
722 else {
723 $photonic_photo->description = wp_kses($image->Caption, Photonic::$safe_description_tags);
724 }
725
726 if (isset($image->WebUri)) {
727 $photonic_photo->main_page = esc_url($image->WebUri);
728 $photonic_photo->buy_link = esc_url($image->WebUri . '/buy');
729 }
730
731 if (!empty($image->IsVideo)) {
732 $photonic_photo->video = esc_url($image_sizes->{$short_code['video_size'] . 'VideoUrl'});
733 }
734
735 if (isset($image->ArchivedUri)) {
736 $photonic_photo->download = esc_url($image->ArchivedUri);
737 }
738
739 $photonic_photo->id = $image->ImageKey;
740
741 if (!empty($image->DateTimeOriginal)) {
742 $photonic_photo->taken_on = sanitize_text_field($image->DateTimeOriginal);
743 }
744
745 if (!empty($image->DateTimeUploaded)) {
746 $photonic_photo->uploaded_on = sanitize_text_field($image->DateTimeUploaded);
747 }
748
749 $photo_objects[] = $photonic_photo;
750 }
751 }
752 return $photo_objects;
753 }
754
755 public function build_level_2_objects($objects_or_response, array $short_code, array $filter_list = [], array $options = [], ?Pagination &$pagination = null): array {
756 global $photonic_smug_hide_password_protected_thumbnail, $photonic_gallery_template_page;
757
758 $named_albums = $filter_list;
759 foreach ($named_albums as $idx => $album) {
760 if (false !== stripos($album, '_')) {
761 $named_albums[$idx] = substr($album, stripos($album, '_') + 1);
762 }
763 }
764
765 $objects = [];
766 if (is_array($objects_or_response) && count($objects_or_response) > 0) {
767 $main = "{$short_code['main_size']}ImageUrl";
768 $tile = (empty($short_code['tile_size']) || 'same' === $short_code['tile_size']) ? $main : "{$short_code['tile_size']}ImageUrl";
769
770 $sizes = [
771 'thumb_size' => $short_code['thumb_size'],
772 'tile_size' => (empty($short_code['tile_size']) || 'same' === $short_code['tile_size']) ? $short_code['main_size'] : $short_code['tile_size'],
773 ];
774
775 foreach ($objects_or_response as $album) {
776 if (!empty($photonic_smug_hide_password_protected_thumbnail) && isset($album->SecurityType) && 'None' !== $album->SecurityType) {
777 continue;
778 }
779
780 $highlight = $album->Uris->HighlightImage;
781 if ((!isset($album->ImageCount) || 0 !== $album->ImageCount)) {
782 $photonic_album = new Album();
783
784 if (isset($highlight->Image)) {
785 $thumbURL = esc_url($highlight->Image->Uris->ImageSizes->ImageSizes->{$short_code['thumb_size'] . 'ImageUrl'});
786 $tileURL = esc_url($highlight->Image->Uris->ImageSizes->ImageSizes->$tile);
787 $this->calculate_sizes($highlight->Image, $photonic_album, $sizes);
788 }
789 else {
790 if (in_array($short_code['thumb_size'], ['Small', 'Thumb', 'Tiny', 'Sm', 'Th', 'Ti'], true)) {
791 $thumbURL = esc_url(trailingslashit(PHOTONIC_URL) . 'include/images/placeholder-' . substr($short_code['thumb_size'], 0, 2) . '.png');
792 }
793 else {
794 $thumbURL = esc_url(trailingslashit(PHOTONIC_URL) . 'include/images/placeholder.png');
795 }
796 $tileURL = esc_url(trailingslashit(PHOTONIC_URL) . 'include/images/placeholder.png');
797 }
798
799 if (isset($album->AlbumKey)) {
800 $photonic_album->id = $album->AlbumKey;
801 }
802 else {
803 $uri = $album->Uris->Album->Uri;
804 $uri = explode('/', $uri);
805 $photonic_album->id = $uri[count($uri) - 1];
806 }
807 $photonic_album->thumbnail = $thumbURL;
808 $photonic_album->tile_image = $tileURL;
809
810 $photonic_album->main_page = esc_url($album->WebUri);
811
812 $photonic_album->title = wp_kses_post($album->Name);
813
814 if (isset($album->FormattedValues->Description->html) && !empty($album->FormattedValues->Description->html)) {
815 $photonic_album->description = wp_kses($album->FormattedValues->Description->html, Photonic::$safe_description_tags);
816 }
817 elseif (isset($album->Uris->Node->Node->FormattedValues->Description->html) && !empty($album->Uris->Node->Node->FormattedValues->Description->html)) {
818 $photonic_album->description = wp_kses($album->Uris->Node->Node->FormattedValues->Description->html, Photonic::$safe_description_tags);
819 }
820 else {
821 $photonic_album->description = !empty($album->Description) ? wp_kses($album->Description, Photonic::$safe_description_tags) : '';
822 }
823
824 if ('page' === $short_code['popup'] && !empty($photonic_gallery_template_page) && is_string(get_post_status($photonic_gallery_template_page))) {
825 $internal_short_code = $short_code;
826 $internal_short_code['view'] = 'album';
827 $internal_short_code['album'] = $photonic_album->id;
828 $internal_short_code['layout'] = empty($short_code['photo_layout']) ? $short_code['layout'] : $short_code['photo_layout'];
829 $internal_short_code['count'] = empty($short_code['photo_count']) ? $short_code['count'] : $short_code['photo_count'];
830
831 $photonic_album->gallery_url = $this->get_gallery_url($internal_short_code, ['title' => $photonic_album->title]);
832 }
833
834 if (isset($album->ImageCount)) {
835 $photonic_album->counter = $album->ImageCount;
836 }
837
838 if (isset($album->SecurityType) && 'Password' === $album->SecurityType) {
839 $photonic_album->classes = ['photonic-smug-passworded'];
840 $photonic_album->passworded = 1;
841 }
842
843 if (empty($named_albums) || (in_array($album->AlbumKey, $named_albums, true) && 'exclude' !== strtolower($short_code['filter_type'])) ||
844 (!in_array($album->AlbumKey, $named_albums, true) && 'exclude' === strtolower($short_code['filter_type']))) {
845 $objects[] = $photonic_album;
846 }
847 }
848 }
849 }
850 return $objects;
851 }
852
853 private function calculate_sizes($image, &$photo_object, $sizes) {
854 if (isset($image->Uris->ImageSizeDetails) && isset($image->Uris->LargestImage)) {
855 $image_size_details = $image->Uris->ImageSizeDetails;
856 $largest_image = $image->Uris->LargestImage;
857
858 foreach ($sizes as $size => $size_value) {
859 if ((isset($largest_image->LargestImage) && 'Largest' === $size_value) || isset($image_size_details->ImageSizeDetails->{'ImageSize' . $size_value})) {
860 if ('Largest' === $size_value) {
861 $ref = $largest_image->LargestImage;
862 }
863 else {
864 $ref = $image_size_details->ImageSizeDetails->{'ImageSize' . $size_value};
865 }
866 $photo_object->{$size} = [
867 'w' => $ref->Width,
868 'h' => $ref->Height,
869 ];
870 }
871 }
872 }
873 }
874
875 /**
876 * Access Token URL
877 *
878 * @return string
879 */
880 public function access_token_URL(): string {
881 return 'https://api.smugmug.com/services/oauth/1.0a/getAccessToken';
882 }
883
884 /**
885 * Authenticate URL
886 *
887 * @return string
888 */
889 public function authenticate_URL(): string {
890 return 'https://api.smugmug.com/services/oauth/1.0a/authorize';
891 }
892
893 /**
894 * Authorize URL
895 *
896 * @return string
897 */
898 public function authorize_URL(): string {
899 return 'https://api.smugmug.com/services/oauth/1.0a/authorize';
900 }
901
902 /**
903 * Request Token URL
904 *
905 * @return string
906 */
907 public function request_token_URL(): string {
908 return 'https://api.smugmug.com/services/oauth/1.0a/getRequestToken';
909 }
910
911 /**
912 * Tests to see if the OAuth Access Token that is cached is still valid. This is important because a user might have manually revoked
913 * access for your app through the provider's control panel.
914 *
915 * @return array|WP_Error
916 */
917 public function check_access_token() {
918 $signed_parameters = $this->sign_call($this->base_url . 'user/sayontan', 'GET', []);
919 $end_point = $this->base_url . 'user/sayontan?' . self::build_query($signed_parameters);
920 return Photonic::http($end_point, 'GET', null, null, 90, PHOTONIC_SSL_VERIFY);
921 }
922
923 /**
924 * Takes the response for the "Check access token", then tries to determine whether the check was successful or not.
925 *
926 * @param $response
927 * @return bool
928 */
929 public function is_access_token_valid($response): bool {
930 if (is_wp_error($response)) {
931 return false;
932 }
933
934 $response = $response['response'];
935 if (200 === $response['code']) {
936 return true;
937 }
938 return false;
939 }
940
941 /**
942 * @param $pages
943 * @param array $short_code
944 * @return Pagination
945 */
946 public function get_pagination($pages, array $short_code = []): Pagination {
947 $pagination = new Pagination();
948 if (!empty($pages)) {
949 $pagination->total = $pages->Total;
950 $pagination->start = $pages->Start;
951 $pagination->end = $pages->Start + $pages->Count - 1;
952 $pagination->per_page = $pages->RequestedCount;
953 }
954 return $pagination;
955 }
956 }
957