PluginProbe
Photonic Gallery & Lightbox for Flickr, SmugMug & Others / 2.42
Photonic Gallery & Lightbox for Flickr, SmugMug & Others v2.42
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 / Modules / Zenfolio.php

Zenfolio.php in Photonic Gallery & Lightbox for Flickr, SmugMug & Others 2.42, at Modules/Zenfolio.php

1,161 lines 40.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Photonic_Plugin\Modules;
3
4 use Photonic_Plugin\Core\Photonic;
5 use Requests;
6 use WP_Error;
7
8 require_once('Core.php');
9 require_once('Level_One_Module.php');
10
11 /**
12 * Processor for Zenfolio photos. This extends the Photonic_Plugin\Modules\Core class and defines methods local to Zenfolio.
13 *
14 */
15
16 class Zenfolio extends Core implements Level_One_Module {
17 var $user_name, $user_agent, $token, $service_url, $secure_url, $unlocked_realms, $temp_keyring;
18 private static $instance = null;
19
20 protected function __construct() {
21 parent::__construct();
22 global $photonic_zenfolio_disable_title_link;
23 $this->provider = 'zenfolio';
24 $this->user_agent = "Photonic for ".get_home_url();
25 $this->link_lightbox_title = empty($photonic_zenfolio_disable_title_link);
26 $this->service_url = 'https://api.zenfolio.com/api/1.8/zfapi.asmx';
27 $this->secure_url = 'https://api.zenfolio.com/api/1.8/zfapi.asmx';
28 $this->unlocked_realms = [];
29 $this->temp_keyring = '';
30
31 $this->doc_links = [
32 'general' => 'https://aquoid.com/plugins/photonic/zenfolio/',
33 'photos' => 'https://aquoid.com/plugins/photonic/zenfolio/photos/',
34 'photosets' => 'https://aquoid.com/plugins/photonic/zenfolio/photosets/',
35 'groups' => 'https://aquoid.com/plugins/photonic/zenfolio/group/',
36 'hierarchies' => 'https://aquoid.com/plugins/photonic/zenfolio/group-hierarchy/',
37 ];
38 $this->perform_back_end_authentication();
39 }
40
41 public static function get_instance() {
42 if (self::$instance == null) {
43 self::$instance = new Zenfolio();
44 }
45 return self::$instance;
46 }
47
48 /**
49 * Main function that fetches the images associated with the shortcode.
50 *
51 * @param array $attr
52 * @param array $gallery_meta
53 * @return string
54 */
55 public function get_gallery_images($attr = [], &$gallery_meta = []) {
56 global $photonic_zenfolio_thumb_size, $photonic_zenfolio_main_size, $photonic_zenfolio_tile_size, $photonic_zenfolio_title_caption, $photonic_zenfolio_video_size, $photonic_zenfolio_media, $photonic_zenfolio_default_user;
57 $this->gallery_index++;
58 $this->push_to_stack('Get Gallery Images');
59
60 $attr = array_merge(
61 $this->common_parameters,
62 [
63 'caption' => $photonic_zenfolio_title_caption,
64 'thumb_size' => $photonic_zenfolio_thumb_size,
65 'main_size' => $photonic_zenfolio_main_size,
66 'video_size' => $photonic_zenfolio_video_size,
67 'tile_size' => $photonic_zenfolio_tile_size,
68
69 'count' => 500,
70 'offset' => 0,
71 'media' => $photonic_zenfolio_media,
72 'structure' => 'nested',
73 'login_name' => $photonic_zenfolio_default_user,
74 ], $attr);
75 $attr = array_map('trim', $attr);
76
77 $attr['limit'] = empty($attr['limit']) ? $attr['count'] : $attr['limit'];
78 $attr['photo_count'] = empty($attr['photo_count']) ? $attr['limit'] : $attr['photo_count'];
79
80 $attr['overlay_size'] = empty($attr['overlay_size']) ? $attr['thumb_size'] : $attr['overlay_size'];
81 $attr['overlay_video_size'] = empty($attr['overlay_video_size']) ? $attr['video_size'] : $attr['overlay_video_size'];
82
83 extract($attr);
84
85 if (isset($_COOKIE['photonic-zf-keyring'])) {
86 $realms = $this->make_wp_call('KeyringGetUnlockedRealms', ['keyring' => $_COOKIE['photonic-zf-keyring']]);
87 if (!empty($realms) && !empty($realms->result)) {
88 $this->unlocked_realms = $realms->result;
89 }
90 }
91
92 $chained_methods = [];
93 $zenfolio_params = [];
94 $attr['headers_already_called'] = true;
95 if (!empty($attr['view'])) {
96 switch ($attr['view']) {
97 case 'photos':
98 if (!empty($object_id)) {
99 $chained_methods[] = 'LoadPhoto';
100 if(($h = stripos($object_id, 'h')) !== false) {
101 $object_id = substr($object_id, $h + 1);
102 $object_id = hexdec($object_id);
103 }
104 else if (($p = stripos($object_id, 'p')) !== false) {
105 $object_id = substr($object_id, $p + 1);
106 }
107 else if (strlen($object_id) == 7) {
108 $object_id = hexdec($object_id);
109 }
110
111 $zenfolio_params['photoId'] = $object_id;
112 $zenfolio_params['level'] = 'Full';
113 }
114 else if (!empty($text) || !empty($category_code)) {
115 $zenfolio_params['searchId'] = '';
116 $zenfolio_params['sortOrder'] = !empty($sort_order) ? $sort_order : 'Date'; // Popularity | Date | Rank
117 if (!empty($text)) {
118 $zenfolio_params['query'] = $text;
119 $chained_methods[] = 'SearchPhotoByText';
120 }
121 else if (!empty($category_code)) {
122 $zenfolio_params['categoryCode'] = $category_code;
123 $chained_methods[] = 'SearchPhotoByCategory';
124 }
125 $zenfolio_params['offset'] = $attr['offset'];
126 $zenfolio_params['limit'] = $attr['limit'];
127 }
128 else if (!empty($kind)) {
129 $zenfolio_params['offset'] = $attr['offset'];
130 $zenfolio_params['limit'] = $attr['limit'];
131 switch ($kind) {
132 case 'popular':
133 $chained_methods[] = 'GetPopularPhotos';
134 break;
135
136 case 'recent':
137 $chained_methods[] = 'GetRecentPhotos';
138 break;
139
140 default:
141 $this->pop_from_stack();
142 return $this->error(sprintf(esc_html__('Invalid %s parameter.', 'photonic'), '<code>kind</code>').
143 Photonic::doc_link($this->doc_links['photos']));
144 }
145 }
146 else {
147 $this->pop_from_stack();
148 return $this->error(sprintf(esc_html__('The %1$s parameter is required if %2$s is not specified.', 'photonic'), '<code>kind</code>', '<code>object_id</code>').
149 Photonic::doc_link($this->doc_links['photos']));
150 }
151 break;
152
153 case 'photosets':
154 if (!empty($object_id)) {
155 if(($p = stripos($object_id, 'p')) !== false) {
156 $object_id = substr($object_id, $p + 1);
157 }
158
159 $zenfolio_params['photosetId'] = $object_id;
160 $zenfolio_params['level'] = 'Level2';
161 $zenfolio_params['includePhotos'] = false;
162
163 if (!empty($password) && empty($realm_id)) {
164 $first_call = $this->make_wp_call('LoadPhotoSet', $zenfolio_params);
165 if (isset($first_call->result) && !empty($first_call->result)) {
166 $photoset = $first_call->result;
167 if (isset($photoset->AccessDescriptor)) {
168 $realm_id = $photoset->AccessDescriptor->Id;
169 }
170 }
171 }
172
173 if (!empty($password) && !empty($realm_id)) {
174 if (!in_array($realm_id, $this->unlocked_realms)) {
175 $attr['headers_already_called'] = empty($attr['panel']); //false;
176 $chained_methods[] = 'KeyringAddKeyPlain';
177 $zenfolio_params['keyring'] = empty($_COOKIE['photonic-zf-keyring']) ? '' : $_COOKIE['photonic-zf-keyring'];
178 $zenfolio_params['realmId'] = $realm_id;
179 $zenfolio_params['password'] = $password;
180 }
181 }
182
183 $chained_methods[] = 'LoadPhotoSet';
184 $zenfolio_params['startingIndex'] = $attr['offset'];
185 $zenfolio_params['numberOfPhotos'] = $attr['limit'];
186 $chained_methods[] = 'LoadPhotoSetPhotos';
187 }
188 else if (!empty($login_name)) {
189 $chained_methods[] = 'LoadGroupHierarchy';
190 $attr['structure'] = 'flat';
191 $zenfolio_params['loginName'] = $login_name;
192 }
193 else if (!empty($photoset_type)) {
194 if (!empty($text) || !empty($category_code)) {
195 $zenfolio_params['searchId'] = '';
196 $chained_methods[] = !empty($text) ? 'SearchSetByText' : 'SearchSetByCategory';
197 }
198 else if (!empty($kind)) {
199 switch ($kind) {
200 case 'popular':
201 $chained_methods[] = 'GetPopularSets';
202 break;
203
204 case 'recent':
205 $chained_methods[] = 'GetRecentSets';
206 break;
207
208 default:
209 $this->pop_from_stack();
210 return $this->error(sprintf(esc_html__('Invalid %s parameter.', 'photonic'), '<code>kind</code>').
211 Photonic::doc_link($this->doc_links['photosets']));
212 }
213 }
214
215 if (strtolower($photoset_type) == 'gallery' || strtolower($photoset_type) == 'galleries') {
216 $zenfolio_params['type'] = 'Gallery';
217 }
218 else if (strtolower($photoset_type) == 'collection' || strtolower($photoset_type) == 'collections') {
219 $zenfolio_params['type'] = 'Collection';
220 }
221 else {
222 $this->pop_from_stack();
223 return $this->error(sprintf(esc_html__('Invalid %1$s parameter. Permissible values are %2$s or %3$s.', 'photonic'), '<code>photoset_type</code>', '<code>Gallery</code>', '<code>Collection</code>').
224 Photonic::doc_link($this->doc_links['photosets']));
225 }
226
227 if (!empty($text) || !empty($category_code)) {
228 $zenfolio_params['sortOrder'] = !empty($sort_order) ? $sort_order : (!empty($text) ? 'Rank' : 'Date');
229 if (!empty($text)) {
230 $zenfolio_params['query'] = $text;
231 }
232 else if (!empty($category_code)) {
233 $zenfolio_params['categoryCode'] = $category_code;
234 }
235 }
236 $zenfolio_params['offset'] = $attr['offset'];
237 $zenfolio_params['limit'] = $attr['limit'];
238 }
239 else if (!empty($filter) && empty($login_name)) {
240 $this->pop_from_stack();
241 return $this->error(sprintf(esc_html__('The %1$s parameter is required if %2$s is specified.', 'photonic'), '<code>login_name</code>', '<code>filter</code>').
242 Photonic::doc_link($this->doc_links['photosets']));
243 }
244 else if (empty($kind)) {
245 $this->pop_from_stack();
246 return $this->error(sprintf(esc_html__('The %1$s parameter is required if %2$s or %3$s is not specified.', 'photonic'), '<code>kind</code>', '<code>object_id</code>', '<code>login_name</code>').
247 Photonic::doc_link($this->doc_links['photosets']));
248 }
249 else if (empty($photoset_type)) {
250 $this->pop_from_stack();
251 return $this->error(sprintf(esc_html__('The %1$s parameter is required if %2$s or %3$s is not specified.', 'photonic'), '<code>photoset_type</code>', '<code>object_id</code>', '<code>login_name</code>').
252 Photonic::doc_link($this->doc_links['photosets']));
253 }
254
255 if (!empty($login_name) && !empty($category_code)) {
256 $attr['user_specific_category'] = true;
257 }
258
259 if (!empty($login_name) && !empty($text)) {
260 $attr['user_specific_text'] = true;
261 }
262 break;
263
264 case 'hierarchy':
265 if (empty($login_name)) {
266 $this->pop_from_stack();
267 return $this->error(sprintf(esc_html__('The %s parameter is required.', 'photonic'), '<code>login_name</code>').
268 Photonic::doc_link($this->doc_links['hierarchies']));
269 }
270 $chained_methods[] = 'LoadGroupHierarchy';
271 $zenfolio_params['loginName'] = $login_name;
272 break;
273
274 case 'group':
275 if (empty($object_id)) {
276 $this->pop_from_stack();
277 return $this->error(sprintf(esc_html__('The %s parameter is required.', 'photonic'), '<code>object_id</code>').
278 Photonic::doc_link($this->doc_links['groups']));
279 }
280 $chained_methods[] = 'LoadGroup';
281 if(($f = stripos($object_id, 'f')) !== false) {
282 $object_id = substr($object_id, $f + 1);
283 }
284 $zenfolio_params['groupId'] = $object_id;
285 $zenfolio_params['level'] = 'Full';
286 $zenfolio_params['includeChildren'] = true;
287 break;
288 }
289 }
290
291 if (!empty($attr['panel'])) {
292 $attr['display'] = 'popup';
293 }
294 else {
295 $attr['display'] = 'in-page';
296 }
297
298 $header_display = $this->get_header_display($attr);
299 $attr['header_display'] = $header_display;
300
301 $level_2_meta = [
302 'start' => $attr['offset'],
303 'per-page' => $attr['limit'],
304 ];
305 $attr['level_2_meta'] = $level_2_meta;
306
307 $call_return = $this->make_chained_calls($chained_methods, $zenfolio_params, $attr, $gallery_meta);
308
309 if ($call_return == $this->password_protected) {
310 $this->pop_from_stack();
311 return $call_return;
312 }
313 else if (empty($call_return)) {
314 $this->pop_from_stack();
315 return '';
316 }
317
318 $ret = $this->finalize_markup($call_return, $attr);
319 $this->pop_from_stack();
320
321 return $ret.$this->get_stack_markup();
322 }
323
324 /**
325 * Calls a Zenfolio method with the passed parameters.
326 *
327 * @param $method
328 * @param null $params
329 * @param null $keyring
330 * @return array|mixed|null|object|string|string[]|WP_Error
331 */
332 function make_wp_call($method, $params = null, $keyring = null) {
333 $request = $this->prepare_request($method, $params, $keyring);
334 $response = $this->make_wp_request($method, $request);
335 return $response;
336 }
337
338 /**
339 * Makes a sequence of calls to different Zenfolio methods. This is particularly useful in case of authenticated calls, where
340 * first the authentication happens, then the content is displayed, all in the same call.
341 *
342 * @param array $methods
343 * @param array $zenfolio_args
344 * @param array $short_code
345 * @param array $gallery_meta
346 * @return string
347 */
348 function make_chained_calls($methods, $zenfolio_args, &$short_code = [], &$gallery_meta = []) {
349 $this->push_to_stack('Make chained calls');
350 $ret = '';
351 $keyring = null;
352 $original_params = [];
353 foreach ($zenfolio_args as $param => $value) {
354 $original_params[$param] = $value;
355 }
356
357 foreach ($methods as $method) {
358 $this->push_to_stack("Making call for $method");
359 $keyring_params = [];
360 if ($method == 'KeyringGetUnlockedRealms') {
361 $keyring_params['keyring'] = $zenfolio_args['keyring'];
362 $response = $this->make_wp_call($method, $keyring_params);
363 if (isset($response->result)) {
364 $this->unlocked_realms = $response->result;
365 }
366 }
367 else if ($method == 'KeyringAddKeyPlain') {
368 if (in_array($zenfolio_args['realmId'], $this->unlocked_realms)) {
369 continue;
370 }
371 $keyring_params['keyring'] = $zenfolio_args['keyring'];
372 $keyring_params['realmId'] = $zenfolio_args['realmId'];
373 $keyring_params['password'] = $zenfolio_args['password'];
374 $response = $this->make_wp_call($method, $keyring_params);
375
376 if (!empty($response->result)) {
377 // Sometimes the cookie isn't set by the setcookie command (happens when the password is passed as a shortcode parameter
378 // instead of the password prompt)
379 $keyring = $response->result;
380 if (!in_array($keyring_params['realmId'], $this->unlocked_realms)) {
381 $this->unlocked_realms[] = $keyring_params['realmId'];
382 $this->temp_keyring = $keyring;
383 }
384
385 if (!$short_code['headers_already_called']) {
386 setcookie('photonic-zf-keyring', $keyring, time() + 60 * 60 * 24, COOKIEPATH);
387 }
388 }
389 else {
390 $ret = $this->password_protected;
391 break;
392 }
393 }
394 else {
395 foreach ($original_params as $param => $value) {
396 $zenfolio_args[$param] = $value;
397 }
398
399 $keyring_fields = ['keyring', 'realmId', 'password'];
400 foreach ($zenfolio_args as $param => $value) {
401 if (in_array($param, $keyring_fields)) {
402 unset($zenfolio_args[$param]);
403 }
404 }
405
406 if ($method === 'LoadPhotoSetPhotos') {
407 unset($zenfolio_args['level']);
408 unset($zenfolio_args['includePhotos']);
409 }
410 else if ($method === 'LoadPhotoSet') {
411 unset($zenfolio_args['startingIndex']);
412 unset($zenfolio_args['numberOfPhotos']);
413 }
414
415 $response = $this->make_wp_call($method, $zenfolio_args, $keyring);
416 $this->pop_from_stack();
417 $this->push_to_stack('Processing response');
418 $ret .= $this->process_response($method, $response, $short_code, $gallery_meta);
419 $this->pop_from_stack();
420 }
421 }
422 $this->pop_from_stack();
423 return $ret;
424 }
425
426 /**
427 * Routing function that takes the response and redirects it to the appropriate processing function.
428 *
429 * @param $method
430 * @param $response
431 * @param array $short_code
432 * @param array $gallery_meta
433 * @return string
434 */
435 function process_response($method, $response, &$short_code = [], &$gallery_meta = []) {
436 $header_display = $short_code['header_display'];
437 $level_2_meta = $short_code['level_2_meta'];
438
439 if (!empty($response->result)) {
440 $result = $response->result;
441 $ret = '';
442
443 switch ($method) {
444 case 'GetPopularPhotos':
445 case 'GetRecentPhotos':
446 case 'SearchPhotoByText':
447 case 'SearchPhotoByCategory':
448 $level_2_meta['total'] = $result->TotalCount;
449 $short_code['level_2_meta'] = $level_2_meta;
450 $ret = $this->process_photos($result, 'stream', $short_code);
451 break;
452
453 case 'LoadPhoto':
454 $ret = $this->process_photo($result, $short_code);
455 break;
456
457 case 'GetPopularSets':
458 case 'GetRecentSets':
459 case 'SearchSetByText':
460 case 'SearchSetByCategory':
461 $ret = $this->process_sets($result, $short_code);
462 break;
463
464 case 'LoadPhotoSet':
465 case 'LoadPhotoSetPhotos':
466 if (isset($result->ImageCount)) {
467 $level_2_meta['total'] = $result->ImageCount;
468 $short_code['level_2_meta'] = $level_2_meta;
469 }
470 $ret = $this->process_set($result, ['header_display' => $header_display], $short_code, $gallery_meta);
471 break;
472
473 case 'LoadGroupHierarchy':
474 $ret = $this->process_group_hierarchy($result, ['header_display' => $header_display], $short_code);
475 break;
476
477 case 'LoadGroup':
478 $ret = $this->process_group($result, ['header_display' => $header_display], $short_code, 0);
479 break;
480 }
481 return $ret;
482 }
483 else if ($response == $this->password_protected) {
484 return $response;
485 }
486 else if (!empty($response->error)) {
487 if (!empty($response->error->message)) {
488 return $this->error(esc_html__('Zenfolio returned an error:', 'photonic')."<br/>\n".$response->error->message);
489 }
490 else {
491 return $this->error(esc_html__('Unknown error', 'photonic'));
492 }
493 }
494 else {
495 return $this->error(esc_html__('Unknown error', 'photonic'));
496 }
497 }
498
499 /**
500 * Takes an array of photos and displays each as a thumbnail. Each thumbnail, upon clicking launches a lightbox.
501 *
502 * @param $response
503 * @param string $parent
504 * @param array $short_code
505 * @return string
506 */
507 function process_photos($response, $parent, $short_code = []) {
508 if (!is_array($response)) {
509 if (empty($response->Photos) || !is_array($response->Photos)) {
510 return $this->error(esc_html__('Response is not an array', 'photonic'));
511 }
512 $response = $response->Photos;
513 }
514
515 global $photonic_zenfolio_photos_per_row_constraint, $photonic_zenfolio_photo_title_display, $photonic_zenfolio_photos_constrain_by_padding, $photonic_zenfolio_photos_constrain_by_count;
516 $ret = '';
517 $row_constraints = ['constraint-type' => $photonic_zenfolio_photos_per_row_constraint, 'padding' => $photonic_zenfolio_photos_constrain_by_padding, 'count' => $photonic_zenfolio_photos_constrain_by_count];
518 $photo_objects = $this->build_level_1_objects($response, $short_code);
519
520 $level_2_meta = $short_code['level_2_meta'];
521 $level_2_meta['end'] = $level_2_meta['start'] + count($response);
522
523 $ret .= $this->layout_gallery($photo_objects,
524 [
525 'title_position' => $photonic_zenfolio_photo_title_display,
526 'row_constraints' => $row_constraints,
527 'parent' => $parent,
528 'level_2_meta' => $level_2_meta,
529 ],
530 $short_code,
531 1
532 );
533
534 return $ret;
535 }
536
537 function build_level_1_objects($response, $short_code = [], $module_parameters = [], $options = []) {
538 if (!is_array($response)) {
539 if (empty($response->Photos) || !is_array($response->Photos)) {
540 return [];
541 }
542 $response = $response->Photos;
543 }
544
545 $tile_size = (empty($short_code['tile_size']) || $short_code['tile_size'] == 'same') ? $short_code['main_size'] : $short_code['tile_size'];
546
547 $type = '$type';
548 $photo_objects = [];
549
550 $media = explode(',', $short_code['media']);
551 $videos_ok = in_array('videos', $media) || in_array('all', $media);
552 $photos_ok = in_array('photos', $media) || in_array('all', $media);
553 foreach ($response as $photo) {
554 if (empty($photo->$type) || $photo->$type != 'Photo' || ($photo->IsVideo && !$videos_ok) || (!$photo->IsVideo && !$photos_ok)) {
555 continue;
556 }
557 $appendage = [];
558 if (isset($photo->Sequence)) {
559 $appendage[] = 'sn='.$photo->Sequence;
560 }
561 if (isset($photo->UrlToken)) {
562 $appendage[] = 'tk='.$photo->UrlToken;
563 }
564
565 $photo_object = [];
566 $photo_object['thumbnail'] = 'https://'.$photo->UrlHost.$photo->UrlCore.'-'.$short_code['thumb_size'].'.jpg';
567 $photo_object['main_image'] = 'https://'.$photo->UrlHost.$photo->UrlCore.'-'.$short_code['main_size'].'.jpg';
568 $photo_object['tile_image'] = 'https://'.$photo->UrlHost.$photo->UrlCore.'-'.$tile_size.'.jpg';
569 if ($photo->IsVideo) {
570 $photo_object['video'] = substr($photo->OriginalUrl, 0, strlen($photo->OriginalUrl) - 7).$short_code['video_size'].'.mp4';
571 }
572 $photo_object['download'] = $photo_object['main_image'].'?'.implode('&', $appendage);
573 $photo_object['title'] = $photo->Title;
574 $photo_object['alt_title'] = $photo->Title;
575 $photo_object['description'] = $photo->Caption;
576 $photo_object['main_page'] = $photo->PageUrl;
577 $photo_object['id'] = $photo->Id;
578
579 $photo_object['provider'] = $this->provider;
580
581 $photo_objects[] = $photo_object;
582 }
583
584 return $photo_objects;
585 }
586
587 function build_level_2_objects($response, $short_code = []) {
588 global $photonic_zenfolio_hide_password_protected_thumbnail, $photonic_gallery_template_page;
589 $tile_size = (empty($short_code['tile_size']) || $short_code['tile_size'] == 'same') ? $short_code['main_size'] : $short_code['tile_size'];
590
591 $filter_list = [];
592 if (!empty($short_code['filter'])) {
593 $filter_list = explode(',', $short_code['filter']);
594 }
595
596 $objects = [];
597 foreach ($response as $photoset) {
598 if (empty($photoset->TitlePhoto)) {
599 continue;
600 }
601 if (!empty($photoset->AccessDescriptor) && !empty($photoset->AccessDescriptor->AccessType) && $photoset->AccessDescriptor->AccessType == 'Password' && !empty($photonic_zenfolio_hide_password_protected_thumbnail)) {
602 continue;
603 }
604
605 $object = [];
606
607 $photo = $photoset->TitlePhoto;
608 $object['id_1'] = $photoset->Id;
609 $object['thumbnail'] = 'https://'.$photo->UrlHost.$photo->UrlCore.'-'.$short_code['thumb_size'].'.jpg';
610 $object['tile_image'] = 'https://'.$photo->UrlHost.$photo->UrlCore.'-'.$tile_size.'.jpg';
611 $object['main_page'] = $photoset->PageUrl;
612 $object['title'] = esc_attr($photoset->Title);
613 $object['counter'] = $photoset->PhotoCount;
614 $object['data_attributes'] = [
615 'thumb-size' => $short_code['thumb_size'],
616 'photo-count' => $short_code['photo_count'],
617 'photo-more' => empty($short_code['photo_more']) ? '' : $short_code['photo_more'],
618 'overlay-size' => $short_code['overlay_size'],
619 'overlay-video-size' => $short_code['overlay_video_size'],
620 ];
621
622 if (!empty($photoset->AccessDescriptor) && !empty($photoset->AccessDescriptor->AccessType) && $photoset->AccessDescriptor->AccessType == 'Password') {
623 if (!in_array($photoset->AccessDescriptor->Id, $this->unlocked_realms)) {
624 $object['classes'] = ['photonic-zenfolio-passworded'];
625 $object['passworded'] = 1;
626 $object['realm_id'] = $photoset->AccessDescriptor->Id;
627 $object['data_attributes']['realm'] = $photoset->AccessDescriptor->Id;
628 }
629 }
630
631 $internal_short_code = $short_code;
632 $internal_short_code['view'] = 'photosets';
633 $internal_short_code['object_id'] = $photoset->Id;
634
635 $internal_short_code['layout'] = empty($short_code['photo_layout']) ? $short_code['layout'] : $short_code['photo_layout'];
636 $internal_short_code['limit'] = empty($short_code['photo_count']) ? $short_code['limit'] : $short_code['photo_count'];
637 $internal_short_code['more'] = empty($short_code['photo_more']) ? $short_code['more'] : $short_code['photo_more'];
638
639 if ($short_code['popup'] == 'page' && !empty($photonic_gallery_template_page) && is_string(get_post_status($photonic_gallery_template_page))) {
640 $object['gallery_url'] = $this->get_gallery_url($internal_short_code, [
641 'title' => $object['title'],
642 ]);
643 }
644
645 $page_url = parse_url($photoset->PageUrl);
646 $page_url = $page_url['path'];
647 $page_url = explode('/', $page_url);
648 if (count($page_url) > 1) {
649 $page_url = $page_url[1];
650 }
651
652 if (!is_array($page_url) && (count($filter_list) === 0 || (count($filter_list) > 0 && in_array($page_url, $filter_list) && strtolower($short_code['filter_type']) !== 'exclude') ||
653 (count($filter_list) > 0 && !in_array($page_url, $filter_list) && strtolower($short_code['filter_type']) === 'exclude'))) {
654 $objects[] = $object;
655 }
656 else if (is_array($page_url)) { // Something went wrong. Let's be safe and add the object.
657 $objects[] = $object;
658 }
659 }
660 return $objects;
661 }
662
663 /**
664 * Prints a single photo with the title as an <h3> and the caption as the image caption.
665 *
666 * @param $photo
667 * @param $short_code
668 * @return string
669 */
670 function process_photo($photo, $short_code) {
671 $type = '$type';
672 if (empty($photo->$type) || $photo->$type != 'Photo') {
673 return '';
674 }
675
676 return $this->generate_single_photo_markup([
677 'src' => 'https://'.$photo->UrlHost.$photo->UrlCore.'-'.$short_code['main_size'].'.jpg',
678 'href' => $photo->PageUrl,
679 'title' => $photo->Title,
680 'caption' => $photo->Caption,
681 ]
682 );
683 }
684
685 /**
686 * Takes an array of photosets and displays a thumbnail for each of them. Password-protected thumbnails might be excluded via the options.
687 *
688 * @param $response
689 * @param array $short_code
690 * @return string
691 */
692 function process_sets($response, $short_code = []) {
693 if (!is_array($response)) {
694 if (empty($response->PhotoSets) || !is_array($response->PhotoSets)) {
695 return $this->error(esc_html__('Response is not an array', 'photonic'));
696 }
697 $response = $response->PhotoSets;
698 }
699
700 global $photonic_zenfolio_sets_per_row_constraint, $photonic_zenfolio_sets_constrain_by_count, $photonic_zenfolio_sets_constrain_by_padding,
701 $photonic_zenfolio_set_title_display, $photonic_zenfolio_hide_set_photos_count_display;
702 $row_constraints = ['constraint-type' => $photonic_zenfolio_sets_per_row_constraint, 'padding' => $photonic_zenfolio_sets_constrain_by_padding, 'count' => $photonic_zenfolio_sets_constrain_by_count];
703 $objects = $this->build_level_2_objects($response, $short_code);
704 $ret = $this->layout_gallery($objects,
705 [
706 'row_constraints' => $row_constraints,
707 'type' => 'photosets',
708 'singular_type' => 'set',
709 'title_position' => $photonic_zenfolio_set_title_display,
710 'level_1_count_display' => $photonic_zenfolio_hide_set_photos_count_display,
711 ],
712 $short_code,
713 2
714 );
715 return $ret;
716 }
717
718 /**
719 * Displays a header with a basic summary for a photoset, along with thumbnails for all associated photos.
720 *
721 * @param $response
722 * @param array $options
723 * @param array $short_code
724 * @param array $gallery_meta
725 * @return string
726 */
727 function process_set($response, $options = [], &$short_code = [], &$gallery_meta = []) {
728 $ret = '';
729 $level_2_meta = $short_code['level_2_meta'];
730
731 $media = explode(',', $short_code['media']);
732 $videos_ok = in_array('videos', $media) || in_array('all', $media);
733 $photos_ok = in_array('photos', $media) || in_array('all', $media);
734
735 if (!is_array($response)) {
736 global $photonic_zenfolio_link_set_page, $photonic_zenfolio_hide_set_thumbnail, $photonic_zenfolio_hide_set_title, $photonic_zenfolio_hide_set_photo_count;
737
738 $header = $this->get_header_object($response, $short_code['thumb_size']);
739 $hidden = ['thumbnail' => !empty($photonic_zenfolio_hide_set_thumbnail), 'title' => !empty($photonic_zenfolio_hide_set_title), 'counter' => !empty($photonic_zenfolio_hide_set_photo_count)];
740 $counters = [];
741 if ($photos_ok) $counters['photos'] = $response->ImageCount;
742 if ($videos_ok) $counters['videos'] = $response->VideoCount;
743
744 $level_2_meta['total'] = ($photos_ok ? $response->ImageCount : 0) + ($videos_ok ? $response->VideoCount : 0);
745 $short_code['level_2_meta'] = $level_2_meta;
746 $ret .= $this->process_object_header($header,
747 [
748 'type' => 'set',
749 'hidden' => $this->get_hidden_headers($options['header_display'], $hidden),
750 'counters' => $counters,
751 'link' => empty($photonic_zenfolio_link_set_page),
752 'display' => $short_code['display'],
753 ],
754 $gallery_meta
755 );
756 }
757 else {
758 $ret .= $this->process_photos($response, 'set', $short_code);
759 }
760 return $ret;
761 }
762
763 /**
764 * Takes a Zenfolio response object and converts it into an associative array with a title, a thumbnail URL and a link URL.
765 *
766 * @param $object
767 * @param $thumb_size
768 * @return array
769 */
770 public function get_header_object($object, $thumb_size) {
771 $header = [];
772
773 $header['title'] = $object->Title ?: '';
774 $header['description'] = $object->Caption ?: '' ;
775 if (!empty($object->Title)) {
776 if (!empty($object->TitlePhoto)) {
777 $photo = $object->TitlePhoto;
778 $header['thumb_url'] = 'https://' . $photo->UrlHost . $photo->UrlCore . '-' . $thumb_size . '.jpg';
779 }
780 $header['link_url'] = $object->PageUrl;
781 }
782
783 return $header;
784 }
785
786 /**
787 * For a given user this prints out the group hierarchy. This starts with the root level and first prints all immediate
788 * children photosets. It then goes into each child group and recursively displays the photosets for each of them in separate sections.
789 *
790 * @param $response
791 * @param array $options
792 * @param array $short_code
793 * @return string
794 */
795 function process_group_hierarchy($response, $options = [], $short_code = []) {
796 if (empty($response->Elements)) {
797 return $this->error(esc_html__('No galleries, collections or groups defined for this user', 'photonic'));
798 }
799
800 $filters = [];
801 if (!empty($short_code['kind']) && in_array(strtolower($short_code['kind']), ['popular', 'recent']) && !empty($short_code['login_name']) && empty($short_code['filter'])) {
802 $pr_response = $this->make_wp_call('LoadPublicProfile', ['login_name' => $short_code['login_name']]);
803 if (!empty($pr_response->result)) {
804 $pr_response = $pr_response->result;
805 if (strtolower($short_code['kind']) == 'popular' && !empty($pr_response->FeaturedPhotoSets)) {
806 foreach ($pr_response->FeaturedPhotoSets as $photoset) {
807 $filters[] = $photoset->Id;
808 }
809 }
810 else if (strtolower($short_code['kind']) == 'recent' && !empty($pr_response->RecentPhotoSets)) {
811 foreach ($pr_response->RecentPhotoSets as $photoset) {
812 $filters[] = $photoset->Id;
813 }
814 }
815 if (empty($filters)) {
816 return '';
817 }
818 }
819 }
820
821 $all_photosets = [];
822 $ret = $this->process_group($response, $options, $short_code, 0, $all_photosets, $filters);
823 return $ret;
824 }
825
826 /**
827 * For a given group this displays the immediate children photosets and then recursively displays all children groups.
828 *
829 * @param $group
830 * @param array $options
831 * @param array $short_code
832 * @param $level
833 * @param array $all_photosets
834 * @param array $recent_popular
835 * @return string
836 */
837 function process_group($group, $options, $short_code, $level, &$all_photosets = [], $recent_popular = []) {
838 $ret = '';
839 $type = '$type';
840 if (!isset($group->Elements)) {
841 $object_id = $group->Id;
842 $method = 'LoadGroup';
843 if(($f = stripos($object_id, 'f')) !== false) {
844 $object_id = substr($object_id, $f + 1);
845 }
846 $params = [];
847 $params['groupId'] = $object_id;
848 $params['level'] = 'Full';
849 $params['includeChildren'] = true;
850 $response = $this->make_wp_call($method, $params);
851 if (!empty($response->result)) {
852 $group = $response->result;
853 }
854 }
855
856 if (empty($group->Elements)) {
857 return '';
858 }
859
860 $elements = $group->Elements;
861 $photosets = [];
862 $groups = [];
863 global $photonic_zenfolio_hide_password_protected_thumbnail;
864 $image_count = 0;
865 $requests = [];
866
867 foreach ($elements as $element) {
868 if ($element->$type == 'PhotoSet') {
869 if (!empty($short_code['photoset_type']) && in_array(strtolower($short_code['photoset_type']), ['gallery', 'collection']) && $short_code['photoset_type'] !== $element->Type) {
870 continue;
871 }
872
873 if (!empty($element->AccessDescriptor) && !empty($element->AccessDescriptor->AccessType) && $element->AccessDescriptor->AccessType == 'Password' && !empty($photonic_zenfolio_hide_password_protected_thumbnail)) {
874 continue;
875 }
876
877 if (!empty($short_code['user_specific_category']) || !empty($short_code['user_specific_text'])) {
878 // Need an extra call here, since the LoadGroup doesn't return Level2 attributes for the children
879 // Calls are slow, so we make all of them together at the end of this instead of calling the API individually
880 $params = [];
881 $params['photosetId'] = $element->Id;
882 $params['level'] = 'Level2';
883 $params['includePhotos'] = false;
884 $method = 'LoadPhotoSet';
885
886 $request = $this->prepare_request($method, $params);
887 $requests[] = [
888 'url' => $this->secure_url,
889 'type' => 'POST',
890 'headers' => $request['headers'],
891 'data' => $request['body'],
892 ];
893 }
894 $photosets[$element->Id] = $element;
895 $image_count += $element->ImageCount;
896 }
897 else if ($element->$type == 'Group') {
898 $groups[] = $element;
899 }
900 }
901
902 if (!empty($requests)) {
903 $responses = Requests::request_multiple($requests);
904 foreach ($responses as $ps_response) {
905 if (is_a($ps_response, 'Requests_Response')) {
906 $found = false;
907 $ps_response = json_decode($ps_response->body);
908 if (!empty($ps_response->result)) {
909 $ps_response = $ps_response->result;
910 if (empty($ps_response->Categories) && !empty($short_code['user_specific_category'])) {
911 $found = false;
912 }
913 else if (!empty($short_code['user_specific_category'])) {
914 $categories = $ps_response->Categories;
915 foreach ($categories as $category) {
916 if ($category == $short_code['category_code']) {
917 $found = true;
918 }
919 }
920 }
921
922 if (!$found) {
923 if (!empty($short_code['user_specific_text'])) {
924 // Check Title, Caption, Keywords
925 $text = $short_code['text'];
926 $text = explode(',', $text);
927 $text = array_map('trim', $text);
928 $to_match = [];
929 foreach ($text as $item) {
930 $to_match[] = '\b'.$item.'\b';
931 }
932 $to_match = '/('.implode('|', $to_match).')/i';
933 $keywords = implode(',', $ps_response->Keywords);
934 $found = preg_match($to_match, $ps_response->Title) || preg_match($to_match, $ps_response->Caption) || preg_match($to_match, $keywords);
935 }
936 }
937
938 if (!$found) {
939 unset($photosets[$ps_response->Id]);
940 }
941 }
942 }
943 }
944 }
945
946 if (!empty($recent_popular)) {
947 foreach ($photosets as $id => $set) {
948 if (!in_array($id, $recent_popular)) {
949 unset($photosets[$id]);
950 }
951 }
952 }
953
954 $all_photosets = array_merge($all_photosets, array_values($photosets));
955
956 global $photonic_zenfolio_hide_empty_groups;
957 global $photonic_zenfolio_link_group_page, $photonic_zenfolio_hide_group_title, $photonic_zenfolio_hide_group_photo_count, $photonic_zenfolio_hide_group_group_count, $photonic_zenfolio_hide_group_set_count;
958
959 $hidden = [
960 'thumbnail' => true,
961 'title' => !empty($photonic_zenfolio_hide_group_title),
962 'counter' => !(empty($photonic_zenfolio_hide_group_photo_count) || empty($photonic_zenfolio_hide_group_group_count) || empty($photonic_zenfolio_hide_group_set_count)),
963 ];
964
965 if (!empty($group->Title) && ($image_count > 0 || empty($photonic_zenfolio_hide_empty_groups))) {
966 $header = $this->get_header_object($group, $short_code['thumb_size']);
967
968 $counters = [
969 'sets' => empty($photonic_zenfolio_hide_group_set_count) ? count($photosets) : 0,
970 'groups' => empty($photonic_zenfolio_hide_group_group_count) ? count($groups) : 0,
971 'photos' => empty($photonic_zenfolio_hide_group_photo_count)? $image_count : 0,
972 ];
973
974 if ($short_code['structure'] !== 'flat') {
975 $ret .= $this->process_object_header($header,
976 [
977 'type' => 'set',
978 'hidden' => $this->get_hidden_headers($options['header_display'], $hidden),
979 'counters' => $counters,
980 'link' => empty($photonic_zenfolio_link_group_page),
981 'display' => $short_code['display'],
982 ]
983 );
984 }
985 }
986
987 if ($short_code['structure'] !== 'flat') {
988 $ret .= $this->process_sets($photosets, $short_code);
989 }
990
991 foreach ($groups as $group) {
992 $out = $this->process_group($group, $options, $short_code, $level + 1, $all_photosets, $recent_popular);
993 if ($short_code['structure'] !== 'flat') {
994 $ret .= $out;
995 }
996 }
997
998 if ($short_code['structure'] === 'flat' && $level === 0) {
999 if (!empty($header)) {
1000 $counters = [
1001 'sets' => empty($photonic_zenfolio_hide_group_set_count) ? count($all_photosets) : 0,
1002 ];
1003
1004 $ret .= $this->process_object_header($header,
1005 [
1006 'type' => 'set',
1007 'hidden' => $this->get_hidden_headers($options['header_display'], $hidden),
1008 'counters' => $counters,
1009 'link' => empty($photonic_zenfolio_link_group_page),
1010 'display' => $short_code['display'],
1011 ]
1012 );
1013 }
1014 $ret .= $this->process_sets($all_photosets, $short_code);
1015 }
1016 return $ret;
1017 }
1018
1019 function authenticate($password) {
1020 global $photonic_zenfolio_default_user;
1021 $photonic_authentication = get_option('photonic_authentication');
1022 if (!isset($photonic_authentication['zenfolio'])) {
1023 $photonic_authentication['zenfolio'] = [];
1024 }
1025 $ret = [];
1026 if (!empty($photonic_zenfolio_default_user)) {
1027 $challenge_response = $this->make_wp_call('GetChallenge', ['loginName' => $photonic_zenfolio_default_user]);
1028
1029 if (!empty($challenge_response)) {
1030 $salt = $challenge_response->result->PasswordSalt;
1031 $salt = call_user_func_array('pack', array_merge(['C*'], $salt));
1032 $pass_hash = hash('sha256', $salt.utf8_encode($password), true);
1033
1034 $challenge = $challenge_response->result->Challenge;
1035
1036 $this->perform_back_end_authentication($pass_hash, $challenge);
1037 if (empty($this->token)) {
1038 $ret['error'] = esc_html__('Authentication failed.', 'photonic');
1039 unset($photonic_authentication['zenfolio']['pass_hash']);
1040 }
1041 else {
1042 $photonic_authentication['zenfolio']['pass_hash'] = unpack('C*', $pass_hash);
1043 $ret['success'] = $this->token;
1044 }
1045 }
1046 else {
1047 $ret['error'] = esc_html__('Failed to get challenge.', 'photonic');
1048 }
1049 }
1050 else {
1051 unset($photonic_authentication['zenfolio']['pass_hash']);
1052 $ret['error'] = sprintf(esc_html__('Default user not defined. Please define one under %s', 'photonic'), '<em>Photonic &rarr; Settings &rarr; Zenfolio &rarr; Zenfolio Photo Settings &rarr; Default User</em>');
1053 }
1054 update_option('photonic_authentication', $photonic_authentication);
1055 return $ret;
1056 }
1057
1058 function perform_back_end_authentication($pass_hash = '', $challenge = false) {
1059 global $photonic_zenfolio_default_user;
1060 if (!empty($photonic_zenfolio_default_user)) {
1061 $photonic_authentication = get_option('photonic_authentication');
1062 $auth_done = !empty($photonic_authentication) && !empty($photonic_authentication['zenfolio']) && !empty($photonic_authentication['zenfolio']['pass_hash']);
1063 if ($auth_done || !empty($pass_hash)) {
1064 if (empty($pass_hash)) {
1065 $pass_hash = $photonic_authentication['zenfolio']['pass_hash'];
1066 $pass_hash = call_user_func_array('pack', array_merge(['C*'], $pass_hash));
1067 }
1068
1069 if (empty($challenge)) {
1070 $challenge_response = $this->make_wp_call('GetChallenge', ['loginName' => $photonic_zenfolio_default_user]);
1071 $challenge = $challenge_response->result->Challenge;
1072 }
1073
1074 $challenge_pack = call_user_func_array('pack', array_merge(['C*'], $challenge));
1075 $challenge_hash = hash('sha256', $challenge_pack.$pass_hash, true);
1076 $proof = array_values(unpack('C*', $challenge_hash));
1077
1078 $auth_response = $this->make_wp_call('Authenticate', ['challenge' => $challenge, 'proof' => $proof]);
1079 if (!empty($auth_response->result)) {
1080 $this->token = $auth_response->result;
1081 return;
1082 }
1083 }
1084 }
1085 $this->token = false;
1086 }
1087
1088 /**
1089 * @param $method
1090 * @param $params
1091 * @param $keyring
1092 * @return array
1093 */
1094 function prepare_request($method, $params, $keyring = null) {
1095 $request = [];
1096 $request['method'] = $method;
1097 $request['params'] = array_values($params);
1098 $request['id'] = 1;
1099 $bodyString = json_encode($request);
1100 $bodyLength = strlen($bodyString);
1101
1102 $headers = [];
1103 $headers['Host'] = 'api.zenfolio.com';
1104 $headers['User-Agent'] = $this->user_agent;
1105 if ($this->token && $method !== 'GetChallenge' && $method !== 'Authenticate') {
1106 $headers['X-Zenfolio-Token'] = $this->token;
1107 }
1108 if (isset($_COOKIE['photonic-zf-keyring'])) {
1109 $headers['X-Zenfolio-Keyring'] = $_COOKIE['photonic-zf-keyring'];
1110 }
1111 else if (!empty($keyring)) {
1112 $headers['X-Zenfolio-Keyring'] = $keyring;
1113 }
1114 else if (!empty($this->temp_keyring)) {
1115 $headers['X-Zenfolio-Keyring'] = $this->temp_keyring;
1116 }
1117 $headers['Content-Type'] = 'application/json';
1118 $headers['Content-Length'] = $bodyLength;
1119 return ['headers' => $headers, 'body' => $bodyString];
1120 }
1121
1122 /**
1123 * @param $method
1124 * @param $request
1125 * @return array|mixed|null|object|string|string[]|WP_Error
1126 */
1127 public function make_wp_request($method, $request) {
1128 $args = [
1129 'user-agent' => $this->user_agent,
1130 'sslverify' => PHOTONIC_SSL_VERIFY,
1131 'timeout' => 30,
1132 'headers' => $request['headers'],
1133 'method' => 'POST',
1134 'body' => $request['body'],
1135 ];
1136
1137 $response = wp_remote_request($this->secure_url, $args);
1138 $response = wp_remote_retrieve_body($response);
1139
1140 //PHP/WP's json_decode() function really messes up the "long" ids returned by Zenfolio. The following takes care of this.
1141 // Can't pass the 4th argument as outlined here: https://php.net/manual/en/function.json-decode.php, since it only came into existence in PHP 5.4
1142 $response = preg_replace('/"Id":(\d+)/', '"Id":"$1"', $response);
1143 $response = preg_replace('/"RealmId":(\d+)/', '"Id":"$1"', $response);
1144 if ($method == 'KeyringGetUnlockedRealms') {
1145 $realm_ids = [];
1146 preg_match('/([\[^,\d]+])/', $response, $realm_ids);
1147 if (!empty($realm_ids)) {
1148 $realm_ids = $realm_ids[0];
1149 $replace = $realm_ids;
1150 $replace = str_replace('[', '["', $replace);
1151 $replace = str_replace(']', '"]', $replace);
1152 $replace = str_replace(',', '","', $replace);
1153 $response = str_replace($realm_ids, $replace, $response);
1154 }
1155 }
1156
1157 $response = json_decode($response);
1158 return $response;
1159 }
1160 }
1161