PluginProbe
Photonic Gallery & Lightbox for Flickr, SmugMug & Others / 2.34
Photonic Gallery & Lightbox for Flickr, SmugMug & Others v2.34
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 / admin / flow / Photonic_Flow_Manager.php

Photonic_Flow_Manager.php in Photonic Gallery & Lightbox for Flickr, SmugMug & Others 2.34, at admin/flow/Photonic_Flow_Manager.php

2,099 lines 85.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class Photonic_Flow_Manager
4 * This is the core module for displaying the gallery builder. This is used with two other files:
5 * - Flow.php: contains the top-level markup for the gallery builder
6 * - Photonic_Flow_Fields.php: contains all the fields by screen
7 *
8 * @package Photonic
9 * @subpackage Flow
10 * @since 2.00
11 */
12 class Photonic_Flow_Manager {
13 var $flow_fields, $field_list, $display_types, $shortcode_attributes, $aka_attributes, $date_parts, $date_part_hints,
14 $error_mandatory, $error_no_response, $error_unknown, $error_not_found, $error_no_data_returned, $error_not_permitted, $error_authentication, $error_missing_api,
15 $force_next_screen, $force_previous_screen, $is_gutenberg;
16
17 function __construct() {
18 require_once("Photonic_Flow_Fields.php");
19 $this->flow_fields = new Photonic_Flow_Fields();
20
21 $this->field_list = $this->flow_fields->get_flow_options();
22 $this->force_next_screen = -1;
23 $this->force_previous_screen = -1;
24
25 $this->error_mandatory = esc_html__('Please fill the mandatory fields. Mandatory fields are marked with a red "*".', 'photonic');
26 $this->error_no_response = esc_html__('No response from server.', 'photonic');
27 $this->error_not_found = esc_html__('Not found.', 'photonic');
28 $this->error_no_data_returned = esc_html__('No data was returned for the user you provided. Please verify that the user has the content you are looking for.', 'photonic');
29 $this->error_not_permitted = esc_html__('Incorrect value passed for "%1$s": %2$s', 'photonic');
30 $this->error_missing_api = esc_html__('Please set up your %1$s and secret under %2$s', 'photonic');
31 $this->error_authentication = esc_html__('Please set up your %1$s Authentication from %2$s', 'photonic');
32
33 $this->date_parts = [0 => esc_html__('Year', 'photonic'), 1 => esc_html__('Month', 'photonic'), 2 => esc_html__('Date', 'photonic')];
34 $this->date_part_hints = [0 => esc_html__('Year (0 - 9999)', 'photonic'), 1 => esc_html__('Month (0 - 12)', 'photonic'), 2 => esc_html__('Date (0 - 31)', 'photonic')];
35
36 $this->display_types = [
37 'single-photo' => 0,
38 'multi-photo' => 1,
39 'album-photo' => 1,
40 'folder-photo' => 1,
41 'user-photo' => 1,
42 'gallery-photo' => 1,
43 'collection-photo' => 1,
44 'current-post' => 1,
45 'another-post' => 1,
46 'multi-album' => 2,
47 'multi-gallery' => 2,
48 'multi-collection' => 2,
49 'multi-gallery-collection' => 2,
50 'collection' => 3,
51 'collections' => 3,
52 'folder' => 3,
53 'tree' => 3,
54 'group' => 3,
55 'group-hierarchy' => 3,
56 ];
57
58 $this->shortcode_attributes = [
59 'common' => [
60 'columns', 'count', 'more', 'photo_count', 'photo_more', 'title_position', 'caption', 'media', 'main_size', 'thumb_size', 'tile_size', 'video_size', 'popup', 'thumbnail_effect', 'show_gallery', // All lightbox
61 'speed', 'timeout', 'fx', 'pause', 'strip-style', 'controls', // Slideshow
62 ],
63 'flickr' => ['user_id', 'group_id', 'collections_display', 'tags', 'tag_mode', 'text', 'sort', 'privacy_filter',],
64 'smugmug' => ['nick_name', 'password', 'text', 'keywords', 'sort_order', 'sort_method', 'album_sort_order',],
65 'google' => ['user_id', 'access', 'protection', 'crop_thumb', 'content_filters',],
66 'zenfolio' => ['login_name', 'text', 'category_code', 'sort_order', 'structure',],
67 'instagram' => ['embed_type'],
68 'wp' => [],
69 ];
70 $this->aka_attributes = [
71 'flickr' => ['per_page' => 'count'],
72 'zenfolio' => ['limit' => 'count'],
73 'wp' => ['slide_size' => 'main_size'],
74 ];
75
76 $this->check_for_blanks = [
77 'more', 'photo_more', 'count', 'photo_count',
78 ];
79 }
80
81 /**
82 * Gets the content to be displayed on a flow screen. This is invoked by an AJAX call. The current screen number is passed to this function.
83 * The function performs validations behind the scenes and if everything looks OK it returns the content for the next screen, otherwise
84 * an error is returned.
85 *
86 * @return string
87 */
88 function get_screen() {
89 $screen = isset($_POST['screen']) ? sanitize_text_field($_POST['screen']) : 0;
90 $provider = isset($_POST['provider']) ? sanitize_text_field($_POST['provider']) : '';
91 $display_type = isset($_POST['display_type']) ? sanitize_text_field($_POST['display_type']) : '';
92
93 $raw_shortcode = !empty($_POST['photonic-editor-shortcode-raw']) ? sanitize_text_field($_POST['photonic-editor-shortcode-raw']) : '';
94 if (!empty($raw_shortcode)) {
95 $input = base64_decode($raw_shortcode);
96 $input = json_decode($input);
97 if (!empty($input->shortcode) && !empty($input->shortcode->attrs) && !empty($input->shortcode->attrs->named)) {
98 $input = $input->shortcode->attrs->named;
99 }
100 }
101 else {
102 $raw_shortcode = !empty($_POST['photonic-editor-json']) ? stripslashes_deep($_POST['photonic-editor-json']) : '';
103 $input = json_decode($raw_shortcode);
104 if (!empty($_POST['photonic-gutenberg-active'])) {
105 $this->is_gutenberg = true;
106 }
107 }
108
109 $deconstructed = $this->deconstruct_shortcode($input);
110
111 $output = $this->validate($screen, $provider, $deconstructed);
112 if (!empty($output['error'])) {
113 return '<div class="photonic-flow-error">' . $output['error'] . "</div>\n";
114 }
115
116 $screen = ((int)$screen) + 1;
117 if ($this->force_next_screen > -1) {
118 $screen = $this->force_next_screen;
119 }
120
121 $screen_fields = [];
122 if (!empty($this->field_list['screen-' . $screen])) {
123 $screen_fields = $this->field_list['screen-' . $screen];
124 }
125 $ret = '';
126
127 if ($screen == 2 || $screen == '2') {
128 /*
129 * The display_type screen. This gathers inputs about what the user wants to show: photos from an album, a collection of
130 * albums, user trees, single photos etc., along with who the photos are from - the default user, a different user, any user etc.
131 */
132 $fields = $screen_fields[$provider]['display'];
133 $ret .= $this->render_all_fields($fields, $deconstructed);
134 $ret = (empty($screen_fields[$provider]['header']) ? '' : "<h1>" . $screen_fields[$provider]['header'] . "</h1>\n") .
135 (empty($screen_fields[$provider]['desc']) ? '' : "<p>" . $screen_fields[$provider]['desc'] . "</p>\n") .
136 $ret;
137 }
138 else if ($screen == 3) {
139 /*
140 * The Gallery Builder screen, where all the photos / albums / folders are displayed
141 */
142 $fields = $screen_fields[$provider][$display_type]['display'];
143 $ret .= $this->render_all_fields($fields, $deconstructed);
144
145 $ret = (empty($screen_fields[$provider][$display_type]['header']) ? '' : "<h1>" . $screen_fields[$provider][$display_type]['header'] . "</h1>\n") .
146 (empty($screen_fields[$provider][$display_type]['desc']) ? '' : "<p>" . $screen_fields[$provider][$display_type]['desc'] . "</p>\n") .
147 str_replace('{{placeholder_value}}', $output['success'], $ret);
148 }
149 else if ($screen == 4) {
150 /*
151 * The layout selection screen
152 */
153 $ret .= $output['success'];
154 }
155 else {
156 $ret .= $output['success'];
157 }
158
159 return $ret;
160 }
161
162 /**
163 * Checks all inputs amongst themselves. If the inputs are valid, then the content for the next screen is generated by this call.
164 * All heavy logic, including API calls and processing of the responses happens here.
165 *
166 * @param $screen
167 * @param $provider
168 * @param array $existing
169 * @return array|string
170 */
171 function validate($screen, $provider, $existing = []) {
172 if (empty($screen) || !is_numeric($screen) || (is_numeric($screen) && (intval($screen) <= 0 || intval($screen) > 5))) {
173 return ['error' => sprintf(esc_html__('Invalid screen value: %s', 'photonic'), $screen)];
174 }
175 if (!in_array($provider, ['wp', 'flickr', 'picasa', 'google', 'smugmug', 'zenfolio', 'instagram'])) {
176 return ['error' => sprintf(esc_html__('Invalid photo provider: %s', 'photonic'), $provider)];
177 }
178
179 $screen = intval($screen);
180 $display_type = isset($_POST['display_type']) ? sanitize_text_field($_POST['display_type']) : '';
181
182 if ($screen == 1) {
183 switch ($provider) {
184 case 'flickr':
185 global $photonic_flickr_api_key, $photonic_flickr_api_secret;
186 if (empty($photonic_flickr_api_key) || empty($photonic_flickr_api_secret)) {
187 return ['error' => sprintf($this->error_missing_api, 'Flickr API key', '<em>Photonic &rarr; Settings &rarr; Flickr &rarr; Flickr Settings</em>')];
188 }
189 break;
190
191 case 'picasa':
192 return ['error' => esc_html__('Google has deprecated the Picasa API with effect from January 2019. Please consider using the Google Photos module.', 'photonic')];
193
194 case 'google':
195 global $photonic_google_client_id, $photonic_google_client_secret, $photonic_google_refresh_token;
196 // if (!empty($photonic_google_use_own_keys) && (empty($photonic_google_client_id) || empty($photonic_google_client_secret))) {
197 if (empty($photonic_google_client_id) || empty($photonic_google_client_secret)) {
198 return ['error' => sprintf($this->error_missing_api, 'Google Client ID', '<em>Photonic &rarr; Settings &rarr; Google Photos &rarr; Google Photos Settings</em>')];
199 }
200 else if (empty($photonic_google_refresh_token)) {
201 return ['error' => sprintf($this->error_authentication, 'Google Photos', '<em>Photonic &rarr; Authentication</em>')];
202 }
203 break;
204
205 case 'smugmug':
206 case 'zenfolio':
207 return '';
208
209 case 'instagram':
210 global $photonic_instagram_access_token;
211 if (empty($photonic_instagram_access_token)) {
212 return ['error' => sprintf($this->error_authentication, 'Instagram', '<em>Photonic &rarr; Authentication</em>')];
213 }
214 break;
215
216 default: // wp
217 return '';
218 }
219 }
220 else if ($screen == 2) {
221 $screen_fields = $this->field_list['screen-' . $screen];
222 $fields = $screen_fields[$provider]['display'];
223 $flattened_fields = [];
224 foreach ($fields as $id => $field) {
225 if (!empty($field['type']) && $field['type'] != 'field_list') {
226 $flattened_fields[$id] = $field;
227 }
228 else if (!empty($field['type']) && $field['type'] == 'field_list') {
229 $flattened_fields = array_merge($field['list'], $flattened_fields);
230 }
231 }
232
233 if (empty($display_type) || (empty($_POST['for']) && in_array($provider, ['flickr', 'smugmug', 'zenfolio']) && empty($existing))) {
234 return ['error' => $this->error_mandatory];
235 }
236
237 if (empty($_POST['for']) && in_array($provider, ['flickr', 'smugmug', 'zenfolio']) && !empty($existing)) {
238 return ['error' => esc_html__('While the "For whom?" setting may not be required for the shortcode to function in the front-end, it is required to edit this shortcode in this editor. Please specify a value.', 'photonic')];
239 }
240
241 if (!in_array($display_type, array_keys($flattened_fields['display_type']['options']))) {
242 return ['error' => sprintf(esc_html__('Invalid display type: %s', 'photonic'), $display_type)];
243 }
244
245 $for = !empty($_POST['for']) ? sanitize_text_field($_POST['for']) : null;
246
247 switch ($provider) {
248 case 'flickr':
249 if ($display_type != 'multi-photo' && in_array($for, ['group', 'any'])) {
250 $err = esc_html__('Incompatible selections:', 'photonic') . "<br/>\n";
251 $err .= $flattened_fields['display_type']['desc'] . ": " . $flattened_fields['display_type']['options'][$display_type] . "<br/>\n";
252 $err .= $flattened_fields['for']['desc'] . ": " . $flattened_fields['for']['options'][$for] . "<br/>\n";
253 return ['error' => $err];
254 }
255
256 $group = sanitize_text_field($_POST['group']);
257 $login = sanitize_text_field($_POST['login']);
258 global $photonic_flickr_default_user, $photonic_flickr_api_key;
259 if ($for == 'current' && empty($photonic_flickr_default_user)) {
260 return ['error' => sprintf(esc_html__('Default user not defined under %1$s. %2$sSelect "Another user" and put in your user id.', 'photonic'),
261 '<em>Photonic &rarr; Settings &rarr; Flickr &rarr; Flickr Settings &rarr; Default User</em>', '<br/>')];
262 }
263
264 if (($for == 'group' && empty($group)) || ($for == 'other' && empty($login))) {
265 return ['error' => $this->error_mandatory];
266 }
267
268 $parameters = [];
269 $user = $for == 'current' ? $photonic_flickr_default_user : ($for == 'other' ? $login : '');
270 if (($for == 'other' || $for == 'current') && !empty($user)) {
271 $url = 'https://api.flickr.com/services/rest/?format=json&nojsoncallback=1&api_key=' . $photonic_flickr_api_key . '&method=flickr.urls.lookupUser&url=' . urlencode('https://www.flickr.com/photos/') . $user;
272 $response = wp_remote_request($url, ['sslverify' => PHOTONIC_SSL_VERIFY]);
273 $response = $this->process_response($response, 'flickr', 'user');
274 if (!empty($response['error'])) {
275 // Maybe the user provided the full URL instead of just the user name?
276 $url = 'https://api.flickr.com/services/rest/?format=json&nojsoncallback=1&api_key=' . $photonic_flickr_api_key . '&method=flickr.urls.lookupUser&url=' . urlencode($user);
277 $response = wp_remote_request($url, ['sslverify' => PHOTONIC_SSL_VERIFY]);
278 $response = $this->process_response($response, 'flickr', 'user');
279 if (!empty($response['error'])) {
280 return $response;
281 }
282 $parameters = array_merge($response['success'], $parameters);
283 }
284 else {
285 $parameters = array_merge($response['success'], $parameters);
286 }
287 }
288
289 if ($for == 'group' && !empty($group)) {
290 $url = 'https://api.flickr.com/services/rest/?format=json&nojsoncallback=1&api_key=' . $photonic_flickr_api_key . '&method=flickr.urls.lookupGroup&url=' . urlencode('https://www.flickr.com/groups/') . $group;
291 $response = wp_remote_request($url, ['sslverify' => PHOTONIC_SSL_VERIFY]);
292 $response = $this->process_response($response, 'flickr', 'group');
293 if (!empty($response['error'])) {
294 // Maybe the user provided the full URL instead of just the group name?
295 $url = 'https://api.flickr.com/services/rest/?format=json&nojsoncallback=1&api_key=' . $photonic_flickr_api_key . '&method=flickr.urls.lookupGroup&url=' . urlencode($user);
296 $response = wp_remote_request($url, ['sslverify' => PHOTONIC_SSL_VERIFY]);
297 $response = $this->process_response($response, 'flickr', 'group');
298 if (!empty($response['error'])) {
299 return $response;
300 }
301 $parameters = array_merge($response['success'], $parameters);
302 }
303 else {
304 $parameters = array_merge($response['success'], $parameters);
305 }
306 }
307
308 // All OK so far. Let's try to get the data for the next screen
309 $parameters['api_key'] = $photonic_flickr_api_key;
310 $parameters['format'] = 'json';
311 $parameters['nojsoncallback'] = 1;
312
313 if ($display_type == 'single-photo') {
314 $parameters['view'] = 'photo';
315 $parameters['method'] = 'flickr.photos.search';
316 $parameters['per_page'] = 500;
317 }
318 else if ($display_type == 'multi-photo') {
319 $parameters['view'] = 'photos';
320 $parameters['method'] = 'flickr.photos.search';
321 }
322 else if ($display_type == 'multi-album' || $display_type == 'album-photo') {
323 $parameters['view'] = 'photosets';
324 $parameters['method'] = 'flickr.photosets.getList';
325 }
326 else if ($display_type == 'multi-gallery' || $display_type == 'gallery-photo') {
327 $parameters['view'] = 'galleries';
328 $parameters['method'] = 'flickr.galleries.getList';
329 $parameters['per_page'] = 500;
330 }
331 else if ($display_type == 'collection' || $display_type == 'collections') {
332 $parameters['view'] = 'collections';
333 $parameters['method'] = 'flickr.collections.getTree';
334 }
335
336 global $photonic_flickr_gallery, $photonic_flickr_access_token;
337 if (!isset($photonic_flickr_gallery)) {
338 $photonic_flickr_gallery = new Photonic_Flickr_Processor();
339 }
340 if (!empty($photonic_flickr_access_token)) {
341 $parameters = $photonic_flickr_gallery->sign_call('https://api.flickr.com/services/rest/', 'GET', $parameters);
342 }
343
344 $url = add_query_arg($parameters, 'https://api.flickr.com/services/rest/');
345 $response = wp_remote_request($url, ['sslverify' => PHOTONIC_SSL_VERIFY]);
346 $hidden = [];
347 if (isset($parameters['user_id'])) $hidden['user_id'] = $parameters['user_id'];
348 if (isset($parameters['group_id'])) $hidden['group_id'] = $parameters['group_id'];
349 return $this->process_response($response, 'flickr', $display_type, $hidden, $existing, $url);
350
351 case 'smugmug':
352 global $photonic_smugmug_gallery, $photonic_smug_access_token, $photonic_smug_default_user;
353 if (!isset($photonic_smugmug_gallery)) {
354 $photonic_smugmug_gallery = new Photonic_SmugMug_Processor();
355 }
356
357 if ($for == 'current' && empty($photonic_smug_default_user)) {
358 return ['error' => sprintf(esc_html__('Default user not defined under %1$s. %2$sSelect "Another user" and put in your user id.', 'photonic'), '<em>Photonic &rarr; Settings &rarr; SmugMug &rarr; SmugMug Settings &rarr; Default User</em>', '<br/>')];
359 }
360
361 if ($for == 'other' && empty($_POST['user'])) {
362 return ['error' => $this->error_mandatory];
363 }
364
365 $nick_name = sanitize_text_field($_POST['user']);
366 $user = $for == 'current' ? $photonic_smug_default_user : ($for == 'other' ? $nick_name : '');
367 $args = [
368 'APIKey' => $photonic_smugmug_gallery->api_key,
369 '_accept' => 'application/json',
370 '_expandmethod' => 'inline',
371 '_verbosity' => '1',
372 ];
373
374 if ($display_type == 'album-photo' || $display_type == 'multi-album') {
375 $call = "https://api.smugmug.com/api/v2/user/$user!albums";
376 $args['_expand'] = 'HighlightImage.ImageSizes';
377 $args['count'] = 500;
378 }
379 else {
380 $call = "https://api.smugmug.com/api/v2/user/$user";
381 }
382
383 if (!empty($photonic_smug_access_token)) {
384 $args = $photonic_smugmug_gallery->sign_call($call, 'GET', $args);
385 }
386
387 if ($display_type == 'folder' || $display_type == 'tree' || $display_type == 'folder-photo' || $display_type == 'user-photo') {
388 // Not able to use wp_remote_request - getting a signature_invalid response
389 $temp = Photonic::http($call, 'GET', $args, null, 300);
390 $temp = $this->process_response($temp, 'smugmug', 'user');
391
392 if (!empty($temp['success'])) {
393 $node = $temp['success'];
394 $call = 'https://api.smugmug.com' . $node . '!children';
395 if ($display_type == 'tree' || $display_type == 'user-photo') {
396 $args['_expand'] = 'NodeCoverImage.ImageSizes';
397 }
398 else {
399 $config = $photonic_smugmug_gallery->get_config_object(500);
400 $config_str = json_encode($config);
401 $args['_config'] = $config_str;
402 }
403
404 // Sign call again because we made an interim call
405 if (!empty($photonic_smug_access_token)) {
406 unset($args['oauth_consumer_key']);
407 unset($args['oauth_nonce']);
408 unset($args['oauth_signature']);
409 unset($args['oauth_signature_method']);
410 unset($args['oauth_timestamp']);
411 unset($args['oauth_version']);
412 unset($args['oauth_token']);
413 unset($args['oauth_verifier']);
414 $args = $photonic_smugmug_gallery->sign_call($call, 'GET', $args);
415 }
416 }
417 else {
418 return $temp;
419 }
420 }
421
422 $response = Photonic::http($call, 'GET', $args, null, 300);
423 return $this->process_response($response, 'smugmug', $display_type, ['nick_name' => $user], $existing, $call);
424
425 case 'google':
426 global $photonic_google_refresh_token, $photonic_google_gallery;
427 if (!isset($photonic_google_gallery)) {
428 $photonic_google_gallery = new Photonic_Google_Photos_Processor();
429 }
430
431 $photonic_google_gallery->perform_back_end_authentication($photonic_google_refresh_token);
432
433 $parameters = [];
434
435 // All OK so far. Let's try to get the data for the next screen
436 $parameters['access_token'] = $photonic_google_gallery->access_token;
437
438 $query_url = '';
439 if ($display_type == 'multi-photo') {
440 $query_url = 'https://photoslibrary.googleapis.com/v1/mediaItems:search';
441 }
442 else if ($display_type == 'multi-album' || $display_type == 'album-photo') {
443 $query_url = 'https://photoslibrary.googleapis.com/v1/albums?pageSize=50';
444 }
445
446 $query_url = add_query_arg($parameters, $query_url);
447 $response = wp_remote_request($query_url, ['method' => ($display_type == 'multi-photo' ? 'POST' : 'GET'), 'sslverify' => PHOTONIC_SSL_VERIFY]);
448 return $this->process_response($response, 'google', $display_type, [], $existing, $query_url);
449
450 case 'zenfolio':
451 if ((!in_array($display_type, ['multi-photo', /*'multi-gallery', 'multi-collection'*/]) && $for == 'any') ||
452 (!in_array($display_type, ['single-photo', 'gallery-photo', 'collection-photo', 'multi-gallery', 'multi-collection', 'multi-gallery-collection', 'group', 'group-hierarchy']) && in_array($for, ['current', 'other']))) {
453 $err = esc_html__('Incompatible selections:', 'photonic') . "<br/>\n";
454 $err .= $flattened_fields['display_type']['desc'] . ": " . $flattened_fields['display_type']['options'][$display_type] . "<br/>\n";
455 $err .= $flattened_fields['for']['desc'] . ": " . $flattened_fields['for']['options'][$for] . "<br/>\n";
456 return ['error' => $err];
457 }
458
459 if ($for == 'other' && empty($_POST['login_name'])) {
460 return ['error' => $this->error_mandatory];
461 }
462
463 $login_name = sanitize_text_field($_POST['login_name']);
464 global $photonic_zenfolio_default_user;
465 if ($for == 'current' && empty($photonic_zenfolio_default_user)) {
466 return ['error' => sprintf(esc_html__('Default user not defined under %1$s. %2$sSelect "Another user" and put in your user id.', 'photonic'), '<em>Photonic &rarr; Settings &rarr; Zenfolio &rarr; Zenfolio Photo Settings &rarr; Default User</em>', '<br/>')];
467 }
468
469 $parameters = [];
470 $user = $for == 'current' ? $photonic_zenfolio_default_user : ($for == 'other' ? $login_name : '');
471
472 if ($display_type == 'multi-photo') {
473 $url = 'https://api.zenfolio.com/api/1.8/zfapi.asmx/SearchPhotoByCategory';
474 $parameters['searchId'] = '5';
475 $parameters['sortOrder'] = 'Popularity';
476 $parameters['categoryCode'] = '1018000';
477 $parameters['offset'] = 0;
478 $parameters['limit'] = 500;
479 }
480 else if (in_array($display_type, ['multi-gallery', 'multi-collection']) && $for == 'any') {
481 $url = 'https://api.zenfolio.com/api/1.8/zfapi.asmx/SearchSetByCategory';
482 $parameters['searchId'] = '5';
483 $parameters['sortOrder'] = 'Popularity';
484 $parameters['categoryCode'] = '1018000';
485 $parameters['offset'] = 0;
486 $parameters['limit'] = 500;
487 }
488 else {
489 $url = 'https://api.zenfolio.com/api/1.8/zfapi.asmx/LoadGroupHierarchy';
490 $parameters['loginName'] = $user;
491 }
492
493 $response = wp_remote_request($url, ['sslverify' => PHOTONIC_SSL_VERIFY, 'body' => $parameters]);
494 return $this->process_response($response, 'zenfolio', $display_type, ['login_name' => $user], $existing, $url);
495
496 case 'instagram':
497 global $photonic_instagram_gallery;
498 if (!isset($photonic_google_gallery)) {
499 $photonic_instagram_gallery = new Photonic_Instagram_Processor();
500 }
501
502 $base_url = 'https://graph.instagram.com/me/media?fields='.$photonic_instagram_gallery->field_list.'&access_token='.$photonic_instagram_gallery->access_token;
503 $response = wp_remote_request($base_url, ['sslverify' => PHOTONIC_SSL_VERIFY]);
504
505 return $this->process_response($response, 'instagram', $display_type, [], $existing, $base_url);
506
507 default: // wp
508 $this->force_next_screen = 4;
509 $this->force_previous_screen = 2;
510 return ['success' => $this->get_layout_selector($display_type, $existing)];
511 }
512 }
513 else if ($screen == 3) {
514 //Check for display_type
515 $screen_fields = $this->field_list['screen-' . $screen];
516 $provider_fields = $screen_fields[$provider];
517 $fields = $provider_fields[$display_type]['display'];
518 foreach ($fields as $id => $field) {
519 $checks = $this->do_basic_option_check($id, $field, true);
520 if (!empty($checks)) {
521 return $checks;
522 }
523
524 if ($id == 'selection' && sanitize_text_field($_POST['selection']) == 'selected' && empty($_POST['selected_data'])) {
525 return ['error' => esc_html__('Please select what you want to show.', 'photonic')];
526 }
527
528 if (in_array($display_type, ['single-photo', 'album-photo', 'gallery-photo', 'folder-photo', 'collection-photo']) && empty($_POST['selected_data'])) {
529 return ['error' => esc_html__('Please select what you want to show.', 'photonic')];
530 }
531 }
532
533 // All OK? Get next screen
534 if ($display_type != 'single-photo') {
535 $output = $this->get_layout_selector($display_type, $existing, $provider);
536 }
537 else {
538 $this->force_next_screen = 6;
539 $this->force_previous_screen = 3;
540 $output = $this->construct_shortcode();
541 }
542 return ['success' => $output];
543 }
544 else if ($screen == 4) {
545 $layout = isset($_POST['layout']) ? sanitize_text_field($_POST['layout']) : '';
546 if (empty($layout)) {
547 return ['error' => $this->error_mandatory];
548 }
549 $layout_options = $this->flow_fields->get_layout_options();
550 if (!array_key_exists($layout, $layout_options)) {
551 return ['error' => sprintf(esc_html__('Invalid layout: %s', 'photonic'), $layout)];
552 }
553
554 // All good. Next screen:
555 return ['success' => $this->get_layout_options($provider, $display_type, $layout, $existing)];
556 }
557 else {
558 $passworded = isset($_POST['selection_passworded']) ? sanitize_text_field($_POST['selection_passworded']) : '';
559 $password = isset($_POST['password']) ? sanitize_text_field($_POST['password']) : '';
560 if (!empty($passworded) && empty($password)) {
561 return ['error' => $this->error_mandatory];
562 }
563 return ['success' => $this->construct_shortcode()];
564 }
565 return '';
566 }
567
568 /**
569 * Utility method that takes an array of fields, determines if each member is a "field list" or a "field". If it is a field
570 * list it processes it as a field list, otherwise it processes it as a field.
571 *
572 * @param array $fields
573 * @param array $existing
574 * @return String
575 */
576 private function render_all_fields($fields, $existing = []) {
577 $output = '';
578 foreach ($fields as $id => $field) {
579 if (!empty($field['type']) && $field['type'] == 'field_list') {
580 $output .= $this->process_field_list($id, $field, $existing);
581 }
582 else if (!empty($field['type'])) {
583 $output .= $this->process_field($id, $field, 0, null, $existing);
584 }
585 }
586 return $output;
587 }
588
589 /**
590 * Takes a "field list" and processes each field in it individually. A "field list" can contain an interdependent sequence
591 * of fields, in which case each member gets a sequential number assigned to it. The actual logic of sequencing is handled on the
592 * front-end.
593 * E.g. If, for Flickr, you select "Multiple Photos", then "Another User", the front-end will show a "User" text field.
594 * But if "Group" is selected, it shows a "Group" text field
595 *
596 * @param $field_list_name
597 * @param array $field_list
598 * @param array $existing
599 * @return string
600 */
601 function process_field_list($field_list_name, $field_list, $existing = []) {
602 if (!is_array($field_list) || empty($field_list['type']) || $field_list['type'] != 'field_list' || empty($field_list['list'])) {
603 return '';
604 }
605 else {
606 $ret = '';
607 $counter = 0;
608 $sequence_group = null;
609 foreach ($field_list['list'] as $id => $field) {
610 if ($field_list['list_type'] == 'sequence') {
611 $counter++;
612 $sequence_group = $field_list_name;
613 }
614 $ret .= $this->process_field($id, $field, $counter, $sequence_group, $existing);
615 }
616 return $ret;
617 }
618 }
619
620 /**
621 * Main code to render an input element on the flow-screen. Almost all types of inputs have switches for display here.
622 *
623 * @param string $id
624 * @param array $field
625 * @param $sequence
626 * @param null $sequence_group
627 * @param array $existing
628 * @return string
629 */
630 function process_field($id, $field, $sequence, $sequence_group = null, $existing = []) {
631 if (!is_array($field) || empty($field['type'])) {
632 return '';
633 }
634
635 if (!empty($field['post_condition']) && is_array($field['post_condition'])) {
636 foreach ($field['post_condition'] as $var => $permitted_values) {
637 $pass = false;
638 foreach ($permitted_values as $permitted) {
639 if (isset($_POST[$var]) && $_POST[$var] === $permitted) {
640 $pass = true;
641 break;
642 }
643 }
644 if (!$pass) {
645 // Variable has not been set in a different screen. Hide this field.
646 return '';
647 }
648 }
649 }
650
651 $req = empty($field['req']) ? '' : '<span class="photonic-required"><abbr title="'.esc_html__('Required', 'photonic').'">*</abbr></span>';
652 $default = !empty($existing[$id]) ? $existing[$id] : (isset($field['std']) ? $field['std'] : '');
653 $hint = '';
654 $hint_in = '';
655 if (!empty($field['hint'])) {
656 $hint = "<div class='photonic-flow-hint' role='tooltip' id='{$id}-hint'>{$field['hint']}</div>\n";
657 $hint_in = "aria-describedby='{$id}-hint'";
658 }
659
660 switch ($field['type']) {
661 case 'text':
662 $ret = "<label class='photonic-flow-option-name'>{$field['desc']}$req<input type='text' name='{$id}' value='" . $default . "' $hint_in/>$hint</label>";
663 break;
664
665 case 'radio':
666 $ret = !empty($field['desc']) ? '<div class="photonic-flow-option-name">' . $field['desc'] . $req . '</div>' : '';
667 foreach ($field['options'] as $option_value => $option_description) {
668 $option_condition = (empty($field['option-conditions']) || empty($field['option-conditions'][$option_value])) ? '' :
669 "data-photonic-option-condition='" . json_encode($field['option-conditions'][$option_value]) . "'";
670 $checked = checked($default, $option_value, false);
671 $ret .= "\t<div class='photonic-flow-field-radio'><label><input type='radio' name='{$id}' value='$option_value' $checked $option_condition/>" . $option_description . "</label></div>\n";
672 }
673 break;
674
675 case 'select':
676 $ret = "<label class='photonic-flow-option-name'>{$field['desc']}$req\n\t<select name='{$id}' $hint_in>\n";
677 foreach ($field['options'] as $option_value => $option_description) {
678 $option_condition = (empty($field['option-conditions']) || empty($field['option-conditions'][$option_value])) ? '' :
679 "data-photonic-option-condition='" . json_encode($field['option-conditions'][$option_value]) . "'";
680 $selected = selected($default, $option_value, false);
681 $ret .= "\t\t<option value='$option_value' $selected $option_condition>" . esc_attr($option_description) . "</option>\n";
682 }
683 $ret .= "\t</select>\n$hint</label>\n";
684 break;
685
686 case 'image-select':
687 $ret = '';
688 $ret .= '<div class="photonic-flow-option-name">' . $field['desc'] . '</div>';
689 if (!empty($default)) {
690 $selection = !in_array($default, $field['options']) ? array_keys($field['options'])[0] : $default;
691 }
692 else {
693 $selection = array_keys($field['options'])[0];
694 }
695 foreach ($field['options'] as $option_name => $desc) {
696 $esc_desc = esc_attr($desc);
697 $selected = ($option_name == $selection) ? 'selected' : '';
698 $ret .= "<div class=\"photonic-flow-selector photonic-flow-$id-$option_name $selected\" title=\"$esc_desc\">\n\t<span class=\"photonic-flow-selector-inner photonic-$id\" data-photonic-selection-id=\"$option_name\">&nbsp;</span>\n\t<div class='photonic-flow-selector-info'>$desc</div>\n</div>\n";
699 }
700 if (!empty($ret)) {
701 $ret = "<div class='photonic-flow-selector-container photonic-flow-$id' data-photonic-flow-selector-mode='single-no-plus' data-photonic-flow-selector-for=\"$id\">\n<input type=\"hidden\" id=\"$id\" name=\"$id\" value='$selection'/>\n$ret</div>\n";
702 }
703 break;
704
705 case 'multi-select':
706 $ret = '';
707 $ret .= '<div class="photonic-flow-option-name">' . $field['desc'] . '</div>';
708 $selection = explode(',', $default);
709 foreach ($field['options'] as $option_value => $desc) {
710 $checked = in_array($option_value, $selection) ? 'checked' : '';
711 $ret .= "\t<label class='photonic-multi-select-item'><input type='checkbox' name='{$id}[]' value=\"$option_value\" $checked />$desc</label>\n";
712 }
713 if (!empty($ret)) {
714 $ret = "<div class='photonic-flow-multi-select-container'>\n$ret</div>\n";
715 }
716 break;
717
718 case 'date-filter':
719 $ret = '';
720 $ret .= '<div class="photonic-flow-option-name">' . $field['desc'] . '</div>';
721 $dates = !empty($default) ? explode(',', $default) : [];
722 $count = isset($field['count']) && is_numeric($field['count']) ? intval($field['count']) : 1;
723 $ret .= "<ol data-photonic-date-filter='$id' data-photonic-filter-count='$count'>\n";
724 $ctr = 0;
725 foreach ($dates as $didx => $date) {
726 $y_m_d = explode('/', $date);
727 $ret .= "\t<li>\n";
728 $ret .= "\t\t<div class='photonic-single-date'>\n";
729 for ($pidx = 0; $pidx < 3; $pidx++) {
730 $lower = strtolower($this->date_parts[$pidx]);
731 $ret .= "\t\t\t<label class='photonic-date-filter'>\n" .
732 "\t\t\t\t".substr($this->date_parts[$pidx], 0, 1)."<input type='text' class='photonic-date-$lower' name='{$id}_{$lower}[]' value=\"" . (isset($y_m_d[$pidx]) ? $y_m_d[$pidx] : '') . "\" aria-describedby='{$id}-{$didx}_{$lower}-hint'/>\n" .
733 "\t\t\t\t<div class='photonic-flow-hint' role='tooltip' id='{$id}-{$didx}_{$lower}-hint'>" . $this->date_part_hints[$pidx] . "</div>\n" .
734 "\t\t\t</label> \n";
735 }
736 $ret .= "\t\t</div>\n";
737 $ret .= "\t\t<a href='#' class='photonic-remove-date-filter' title='Remove filter'><span class=\"dashicons dashicons-no\"> </span></a>\n";
738 $ret .= "\t</li>\n";
739 $ctr++;
740 if ($ctr >= $count) {
741 break;
742 }
743 }
744 $ret .= "</ol>\n";
745 $ret .= "<input type='hidden' name='$id' value='$default'/>\n";
746 if ($ctr < $count) {
747 $ret .= "<a href='#' class='photonic-add-date-filter' data-photonic-add-date='$id'><span class=\"dashicons dashicons-plus-alt\"> </span> Add filter</a>\n";
748 }
749
750 break;
751
752 case 'date-range-filter':
753 $ret = '';
754 $ret .= '<div class="photonic-flow-option-name">' . $field['desc'] . '</div>';
755 $date_ranges = !empty($default) ? explode(',', $default) : [];
756 $count = isset($field['count']) && is_numeric($field['count']) ? intval($field['count']) : 1;
757 $ret .= "<ol data-photonic-date-range-filter='$id' data-photonic-filter-count='$count'>\n";
758 $ctr = 0;
759 foreach ($date_ranges as $date_range) {
760 $from_to = explode('-', $date_range);
761 if (count($from_to) != 2) {
762 continue;
763 }
764 $ret .= "\t<li>\n";
765 foreach ($from_to as $didx => $date) {
766 $ret .= "\t\t<div class='photonic-single-date'>\n";
767 $y_m_d = explode('/', $date);
768 $from_or_to = $didx === 0 ? 'start' : 'end';
769 for ($pidx = 0; $pidx < 3; $pidx++) {
770 $lower = strtolower($this->date_parts[$pidx]);
771 $ret .= "\t\t\t<label class='photonic-date-filter'>\n" .
772 "\t\t\t\t".substr($this->date_parts[$pidx], 0, 1)."<input type='text' class='photonic-date-$lower' name='{$id}_{$from_or_to}_{$lower}[]' value=\"" . (isset($y_m_d[$pidx]) ? $y_m_d[$pidx] : '') . "\" aria-describedby='{$id}-{$didx}_{$from_or_to}_{$lower}-hint'/>\n" .
773 "\t\t\t\t<div class='photonic-flow-hint' role='tooltip' id='{$id}-{$didx}_{$from_or_to}_{$lower}-hint'>" . $this->date_part_hints[$pidx] . "</div>\n" .
774 "\t\t\t</label> \n";
775 }
776 $ret .= "\t\t</div>\n";
777 }
778 $ret .= "\t\t<a href='#' class='photonic-remove-date-range-filter' title='Remove filter'><span class=\"dashicons dashicons-no\"> </span></a>\n";
779 $ret .= "\t</li>\n";
780 $ctr++;
781 if ($ctr >= $count) {
782 break;
783 }
784 }
785 $ret .= "</ol>\n";
786 $ret .= "<input type='hidden' name='$id' value='$default'/>\n";
787 if ($ctr < $count) {
788 $ret .= "<a href='#' class='photonic-add-date-range-filter' data-photonic-add-date-range='$id'><span class=\"dashicons dashicons-plus-alt\"> </span> Add filter</a>\n";
789 }
790
791 break;
792
793 case 'thumbnail-selector':
794 $ret = "<div class=\"photonic-flow-selector-container\" data-photonic-flow-selector-mode=\"{$field['mode']}\" data-photonic-flow-selector-for=\"{$field['for']}\">\n{{placeholder_value}}</div>\n";
795
796 $controls = "<div class='thumb-controls'>\n";
797 if ($field['mode'] != 'none') {
798 $controls .= "<input type='text' class='search-thumbs' name='thumb-search' id='thumb-search'/>\n";
799 }
800
801 if ($field['mode'] == 'multi') {
802 $controls .= esc_html__('Mark:', 'photonic').
803 sprintf(esc_html__('%1$sAll%2$s', 'photonic'), "<a href='#' class='photonic-mark photonic-mark-all' data-photonic-mark-for='{$field['for']}'>", '</a>').'|'.
804 sprintf(esc_html__('%1$sNone%2$s', 'photonic'), "<a href='#' class='photonic-mark photonic-mark-none' data-photonic-mark-for='{$field['for']}'>", '</a>');
805 }
806 $controls .= "</div>\n";
807 $ret = $controls.$ret;
808 break;
809
810 default:
811 return '';
812 }
813
814 if (!empty($ret)) {
815 if (!empty($field['hint'])) {
816 // $ret .= "<div class='photonic-flow-hint' role='tooltip' id='{$id}-hint'><div>{$field['hint']}</div></div>\n";
817 }
818
819 $sequence_str = '';
820 if ($sequence !== 0) {
821 $sequence_str = 'data-photonic-flow-sequence="' . $sequence . '"';
822 }
823
824 $sequence_group_str = '';
825 if (!is_null($sequence_group)) {
826 $sequence_group_str = 'data-photonic-flow-sequence-group="' . $sequence_group . '"';
827 }
828
829 $condition = '';
830 if (!empty($field['conditions'])) {
831 $condition = "data-photonic-condition='" . json_encode($field['conditions']) . "'";
832 }
833
834 $ret = "<div class='photonic-flow-field' $sequence_str $condition $sequence_group_str>\n" . $ret . "</div>\n";
835 }
836 return $ret;
837 }
838
839 /**
840 * Performs basic checks against whitelists. More advanced checks are handled in the <code>validate</code> function
841 *
842 * @param $id
843 * @param $field
844 * @param bool $check_required
845 * @return array|bool
846 */
847 private function do_basic_option_check($id, $field, $check_required = false) {
848 if (empty($field['type']) || ($field['type'] != 'select' && $field['type'] != 'radio')) {
849 return false;
850 }
851
852 if ($check_required && !empty($field['req']) && (!isset($_POST[$id]) || trim($_POST[$id]) === '')) {
853 return ['error' => $this->error_mandatory];
854 }
855
856 if (isset($_POST[$id]) && !in_array(sanitize_text_field($_POST[$id]), array_keys($field['options']))) {
857 return ['error' => sprintf($this->error_not_permitted, $field['name'], $_POST[$id])];
858 }
859 return false;
860 }
861
862 /**
863 * A special type of selector not handled by the <code>process_field</code> call. Layouts are used only on one screen,
864 * and are used by almost all types of providers. This displays the available layouts as icons to pick from.
865 *
866 * @param $display_type
867 * @param array $existing
868 * @param null $provider
869 * @return string
870 */
871 private function get_layout_selector($display_type, $existing = [], $provider = null) {
872 global $photonic_thumbnail_style;
873 $output = '';
874 $level = empty($this->display_types[$display_type]) ? -1 : $this->display_types[$display_type];
875 // $layout_from_option = empty($existing['layout']) ? (in_array($photonic_thumbnail_style, ['strip-below', 'strip-above', 'strip-right', 'no-strip']) ? 'slideshow' : $photonic_thumbnail_style) : $existing['layout'];
876 if (empty($existing['layout'])) {
877 if (in_array($photonic_thumbnail_style, ['strip-below', 'strip-above', 'strip-right', 'no-strip'])) {
878 $layout_from_option = 'slideshow';
879 }
880 else if ($photonic_thumbnail_style == 'square' && !empty($provider) && $provider == 'instagram') {
881 $layout_from_option = 'random';
882 }
883 else {
884 $layout_from_option = $photonic_thumbnail_style;
885 }
886 }
887 else {
888 $layout_from_option = $existing['layout'];
889 }
890
891 if ($level > 0) {
892 $layout_options = $this->flow_fields->get_layout_options();
893 foreach ($layout_options as $layout => $desc) {
894 $selected = $layout == $layout_from_option ? 'selected' : '';
895 if (($layout == 'slideshow' && $level == 1) || $layout != 'slideshow') {
896 $esc_desc = esc_attr($desc);
897 $output .= "<div class=\"photonic-flow-selector photonic-flow-layout-$layout $selected\" title=\"$esc_desc\">\n\t<span class=\"photonic-flow-selector-inner photonic-layout\" data-photonic-selection-id=\"$layout\">&nbsp;</span>\n\t<div class='photonic-flow-selector-info'>$desc</div>\n</div>\n";
898 }
899 }
900 if (!empty($output)) {
901 $output = "<div class='photonic-flow-selector-container photonic-flow-layout' data-photonic-flow-selector-mode='single-no-plus' data-photonic-flow-selector-for=\"layout\">\n<input type=\"hidden\" id=\"layout\" name=\"layout\" value='$layout_from_option'/>\n$output</div>\n";
902 }
903 }
904 $output = '<h1>' . esc_html__('Pick Your Layout', 'photonic') . '</h1>' .
905 "<p>" . sprintf(esc_html__('You can configure the default settings from %s.', 'photonic'), '<strong>Photonic &rarr; Settings &rarr; Generic Options &rarr; Generic Settings &rarr; Layouts</strong>') . "</p>\n".
906 $output;
907
908 if ($this->force_next_screen > -1) {
909 $output .= "\n<input type='hidden' name='force_next_screen' value='{$this->force_next_screen}'/>\n";
910 }
911 if ($this->force_previous_screen > -1) {
912 $output .= "\n<input type='hidden' name='force_previous_screen' value='{$this->force_previous_screen}'/>\n";
913 }
914
915 return $output;
916 }
917
918 /**
919 * Layouts use similar constructs such as <code>count</code> and <code>more</code> but also have differences.
920 * E.g. <code>columns</code> are not applicable to the justified grid or mosaic layouts etc. Similarly size options
921 * vary from provider to provider. So in the Photonic_Flow_Fields we have a hierarchy for this screen, by level, provider and layout
922 *
923 * @param $provider
924 * @param $display_type
925 * @param $layout
926 * @param array $existing
927 * @return string
928 */
929 private function get_layout_options($provider, $display_type, $layout, $existing = []) {
930 // All levels, all layouts - media
931 // L1, L2 All layouts - title position
932 // L1 All layouts - count, more
933 // L1, L2, L3 basic lightbox layouts - # of columns, constrain by etc., thumbnail size, full size
934 // L3 Flickr - auto-expand
935 $level = $this->display_types[$display_type];
936 $output = '<h1>' . esc_html__('Configure Your Layout', 'photonic') . '</h1>';
937
938 $extract = [];
939 if (!empty($this->field_list['screen-5'][$provider]['L' . $level])) {
940 $extract = array_merge($this->field_list['screen-5'][$provider]['L' . $level], $extract);
941 }
942
943 if (!empty($this->field_list['screen-5'][$layout][$provider])) {
944 $extract = array_merge($this->field_list['screen-5'][$layout][$provider], $extract);
945 }
946
947 if (!empty($this->field_list['screen-5'][$provider])) {
948 $extract = array_merge($this->field_list['screen-5'][$provider], $extract);
949 }
950
951 if (!empty($this->field_list['screen-5']['L' . $level])) {
952 $extract = array_merge($this->field_list['screen-5']['L' . $level], $extract);
953 }
954
955 if (!empty($this->field_list['screen-5'][$layout])) {
956 $extract = array_merge($this->field_list['screen-5'][$layout], $extract);
957 }
958
959 $output .= $this->render_all_fields($extract, $existing);
960 return $output;
961 }
962
963 /**
964 * Builds out the shortcode based on inputs from all previous screens. Some attributes are passed as they are, e.g. <code>count</code>.
965 * Others are used to determine other attributes, e.g. <code>display_type</code>
966 *
967 * @return string
968 */
969 private function construct_shortcode() {
970 global $photonic_alternative_shortcode;
971
972 $provider = sanitize_text_field($_POST['provider']);
973 $display_type = sanitize_text_field($_POST['display_type']);
974
975 $short_code = [];
976 if ($provider !== 'wp') {
977 $short_code['type'] = $provider;
978 }
979
980 switch ($provider) {
981 case 'flickr':
982 if ($display_type == 'single-photo') {
983 $short_code['view'] = 'photo';
984 $short_code['photo_id'] = sanitize_text_field($_POST['selected_data']);
985 }
986 else if ($display_type == 'multi-photo') {
987 $short_code['view'] = 'photos';
988 }
989 else if ($display_type == 'album-photo') {
990 $short_code['view'] = 'photosets';
991 $short_code['photoset_id'] = sanitize_text_field($_POST['selected_data']);
992 }
993 else if ($display_type == 'gallery-photo') {
994 $short_code['view'] = 'galleries';
995 $short_code['gallery_id'] = sanitize_text_field($_POST['selected_data']);
996 }
997 else if ($display_type == 'multi-album') {
998 $short_code['view'] = 'photosets';
999 }
1000 else if ($display_type == 'multi-gallery') {
1001 $short_code['view'] = 'galleries';
1002 }
1003 else if ($display_type == 'collection') {
1004 $short_code['view'] = 'collections';
1005 $short_code['collection_id'] = sanitize_text_field($_POST['selected_data']);
1006 }
1007 else if ($display_type == 'collections') {
1008 $short_code['view'] = 'collections';
1009 }
1010 break;
1011
1012 case 'smugmug':
1013 if ($display_type == 'album-photo') {
1014 $short_code['view'] = 'album';
1015 $short_code['album'] = sanitize_text_field($_POST['selected_data']);
1016 }
1017 else if ($display_type == 'folder-photo') {
1018 $short_code['view'] = 'images';
1019 $short_code['folder'] = sanitize_text_field($_POST['selected_data']);
1020 }
1021 else if ($display_type == 'user-photo') {
1022 $short_code['view'] = 'images';
1023 }
1024 else if ($display_type == 'multi-album') {
1025 $short_code['view'] = 'albums';
1026 }
1027 else if ($display_type == 'tree') {
1028 $short_code['view'] = 'tree';
1029 }
1030 else if ($display_type == 'folder') {
1031 $short_code['view'] = 'folder';
1032 $short_code['folder'] = sanitize_text_field($_POST['selected_data']);
1033 }
1034 break;
1035
1036 case 'google':
1037 if ($display_type == 'album-photo') {
1038 $short_code['view'] = 'photos';
1039 $short_code['album_id'] = sanitize_text_field($_POST['selected_data']);
1040 }
1041 else if ($display_type == 'multi-photo') {
1042 $short_code['view'] = 'photos';
1043 $date_filters = !empty($_POST['date_filters']) ? sanitize_text_field($_POST['date_filters']) : '';
1044 $date_range_filters = !empty($_POST['date_range_filters']) ? sanitize_text_field($_POST['date_range_filters']) : '';
1045 $short_code['date_filters'] = trim($date_filters.','.$date_range_filters, ',');
1046 }
1047 else if ($display_type == 'multi-album') {
1048 $short_code['view'] = 'albums';
1049 }
1050 break;
1051
1052 case 'zenfolio':
1053 if ($display_type == 'multi-photo') {
1054 $short_code['view'] = 'photos';
1055 }
1056 else if ($display_type == 'single-photo') {
1057 $short_code['view'] = 'photos';
1058 $short_code['object_id'] = sanitize_text_field($_POST['selected_data']);
1059 }
1060 else if ($display_type == 'gallery-photo'/* || $display_type == 'collection-photo'*/) {
1061 $short_code['view'] = 'photosets';
1062 $short_code['object_id'] = sanitize_text_field($_POST['selected_data']);
1063 }
1064 else if ($display_type == 'multi-gallery' || $display_type == 'multi-collection' || $display_type == 'multi-gallery-collection') {
1065 $short_code['view'] = 'photosets';
1066 if ($display_type == 'multi-gallery') {
1067 $short_code['photoset_type'] = 'Gallery';
1068 }
1069 else if ($display_type == 'multi-collection') {
1070 $short_code['photoset_type'] = 'Collection';
1071 }
1072 }
1073 else if ($display_type == 'group') {
1074 $short_code['view'] = 'group';
1075 $short_code['object_id'] = sanitize_text_field($_POST['selected_data']);
1076 }
1077 else if ($display_type == 'group-hierarchy') {
1078 $short_code['view'] = 'hierarchy';
1079 }
1080 break;
1081
1082 case 'instagram':
1083 if ($display_type == 'single-photo') {
1084 $short_code['media_id'] = sanitize_text_field($_POST['selected_data']);
1085 }
1086 if ($display_type == 'album-photo') {
1087 $short_code['carousel'] = sanitize_text_field($_POST['selected_data']);
1088 }
1089 break;
1090
1091 default:
1092 if ($display_type == 'current-post' && !empty($_POST['post_id'])) {
1093 $short_code['id'] = sanitize_text_field($_POST['post_id']);
1094 }
1095 else if (!empty($_POST['selected_data'])) {
1096 $short_code['ids'] = sanitize_text_field($_POST['selected_data']);
1097 }
1098 break;
1099 }
1100
1101 if (!empty($_POST['selection'])) {
1102 if ($_POST['selection'] != 'all') {
1103 $short_code['filter'] = sanitize_text_field($_POST['selected_data']);
1104 }
1105 if ($_POST['selection'] == 'not-selected') {
1106 $short_code['filter_type'] = 'exclude';
1107 }
1108 }
1109
1110 if (isset($_POST['headers']) && trim($_POST['headers'] !== '')) {
1111 if (trim($_POST['headers']) == 'none') {
1112 $short_code['headers'] = '';
1113 }
1114 else {
1115 $short_code['headers'] = sanitize_text_field($_POST['headers']);
1116 }
1117 }
1118
1119 $additional_attrs = array_merge(
1120 $this->shortcode_attributes[$provider],
1121 $this->shortcode_attributes['common']
1122 );
1123 foreach ($additional_attrs as $attr) {
1124 if (!empty($_POST[$attr]) && is_array($_POST[$attr])) {
1125 $short_code[$attr] = implode(',', $_POST[$attr]);
1126 }
1127 else if (!empty($_POST[$attr])) {
1128 $short_code[$attr] = $_POST[$attr];
1129 }
1130 }
1131
1132 if (!empty($_POST['layout'])) {
1133 $key = $provider == 'wp' ? 'style' : 'layout';
1134 if ($_POST['layout'] != 'slideshow') {
1135 $short_code[$key] = sanitize_text_field($_POST['layout']);
1136 }
1137 else {
1138 $short_code[$key] = sanitize_text_field($_POST['slideshow-style']);
1139 }
1140 }
1141 // layout
1142
1143 $raw_shortcode = !empty($_POST['photonic-editor-shortcode-raw']) ? sanitize_text_field($_POST['photonic-editor-shortcode-raw']) : '';
1144 if (!empty($raw_shortcode)) {
1145 $input = base64_decode($raw_shortcode);
1146 $input = json_decode($input);
1147 if (!empty($input->shortcode) && !empty($input->shortcode->attrs) && !empty($input->shortcode->attrs->named)) {
1148 $input = $input->shortcode->attrs->named;
1149 }
1150 }
1151 else {
1152 $raw_shortcode = !empty($_POST['photonic-editor-json']) ? stripslashes_deep($_POST['photonic-editor-json']) : '';
1153 $input = json_decode($raw_shortcode);
1154 }
1155
1156 if (!empty($input)) {
1157 $attr_array = (array)$input;
1158
1159 // If the type changes, regardless of everything else blank out the attributes
1160 if (!(empty($short_code['type']) && (empty($attr_array['type']) || in_array($attr_array['type'], ['wp', 'default']))) &&
1161 ($attr_array['type'] != $short_code['type'])) {
1162 $attr_array = [];
1163 }
1164 else {
1165 foreach ($short_code as $key => $value) {
1166 unset($attr_array[$key]);
1167 }
1168 }
1169
1170 if (!empty($this->aka_attributes[$provider])) {
1171 $aka = $this->aka_attributes[$provider];
1172 foreach ($aka as $key => $value) {
1173 if (isset($short_code[$key]) || isset($short_code[$value])) unset($attr_array[$key]);
1174 }
1175 }
1176
1177 // Others ...
1178 if (!empty($short_code['type']) && $short_code['type'] == 'instagram') {
1179 if (!empty($short_code['media_id'])) {
1180 unset($attr_array['view']);
1181 unset($attr_array['media']);
1182 }
1183 else if (!empty($short_code['media'])) {
1184 unset($attr_array['media_id']);
1185 }
1186 }
1187
1188 foreach ($attr_array as $key => $value) {
1189 if (in_array($key, ['more', 'photo_more', 'count', 'photo_count', 'show_gallery']) && empty($short_code[$key])) {
1190 unset($attr_array[$key]);
1191 }
1192 }
1193 $short_code = array_merge($short_code, $attr_array);
1194 }
1195
1196 if (!$this->is_gutenberg) {
1197 $output = '<h1>' . esc_html__('Your shortcode', 'photonic') . '</h1>';
1198 $output .= "<code id='photonic_shortcode'>[" . (empty($photonic_alternative_shortcode) ? 'gallery' : $photonic_alternative_shortcode).' ';
1199 $shortcode_attrs = [];
1200 foreach ($short_code as $attr => $value) {
1201 $shortcode_attrs[] = $attr . "='" . esc_attr($value) . "'";
1202 }
1203
1204 $output .= implode(' ', $shortcode_attrs);
1205 $output .= ']</code>';
1206 $output .= '<p>'.esc_html__('The above shortcode was generated based on your selections. You can either copy the above and paste it manually into your post, or click on the buttons below to insert it into or update your post', 'photonic').'</p>';
1207 }
1208 else {
1209 $output = '<h1>' . esc_html__('Your Gallery', 'photonic') . '</h1>';
1210 $output .= '<p>'.esc_html__('Based on your selections a gallery with the following attributes will be generated. Please click on the buttons below to insert it into or update your post:', 'photonic').'</p>';
1211 $shortcode_attrs = [];
1212 foreach ($short_code as $attr => $value) {
1213 $shortcode_attrs[] = '<code>'.$attr . ": " . esc_attr($value).'</code>';
1214 }
1215 $output .= implode("<br/>\n", $shortcode_attrs);
1216
1217 $output .= '<p>'.esc_html__('The following is the corresponding shortcode for this block. If for some reason you are unable to create a Photonic block you can paste this into a Shortcode block:', 'photonic').'</p>';
1218 $output .= "<code>[" . (empty($photonic_alternative_shortcode) ? 'gallery' : $photonic_alternative_shortcode).' ';
1219 $shortcode_attrs = [];
1220 foreach ($short_code as $attr => $value) {
1221 $shortcode_attrs[] = $attr . "='" . esc_attr($value) . "'";
1222 }
1223
1224 $output .= implode(' ', $shortcode_attrs);
1225 $output .= ']</code>';
1226
1227 $output .= "<input id='photonic_shortcode' name='photonic_shortcode' type='hidden' value='".esc_attr(json_encode($short_code))."' />\n";
1228 }
1229
1230 if ($this->force_next_screen > -1) {
1231 $output .= "\n<input type='hidden' name='force_next_screen' value='{$this->force_next_screen}'/>\n";
1232 }
1233 if ($this->force_previous_screen > -1) {
1234 $output .= "\n<input type='hidden' name='force_previous_screen' value='{$this->force_previous_screen}'/>\n";
1235 }
1236 return $output;
1237 }
1238
1239 /**
1240 * This is the inverse of the <code>construct_shortcode</code> method. If the Editor has a shortcode selected, this method
1241 * splits it out into the relevant screens.
1242 *
1243 * @param $input
1244 * @return array
1245 */
1246 private function deconstruct_shortcode($input) {
1247 $deconstructed = [];
1248 if (!empty($input)) {
1249 if ((!empty($input->type) && in_array($input->type, ['wp', 'default', 'flickr', 'smugmug', 'picasa', 'google', 'zenfolio', 'instagram'])) ||
1250 ((empty($input->type) && !empty($input->style)) && in_array($input->style, ['square', 'circle', 'random', 'masonry', 'mosaic', 'strip-above', 'strip-below', 'strip-right', 'no-strip']))
1251 ) {
1252 $deconstructed['provider'] = !empty($input->type) ? $input->type : 'wp';
1253
1254 switch ($deconstructed['provider']) {
1255 case 'flickr':
1256 // Potential for an existing (old) shortcode to not have a user_id. E.g. Just defining photoset_id.
1257 // Rather than defaulting to the default user, we will make the user put it in this time.
1258 if (!empty($input->user_id)) {
1259 $deconstructed['for'] = 'other';
1260 $deconstructed['login'] = $input->user_id;
1261 }
1262 else if (!empty($input->group_id)) {
1263 $deconstructed['for'] = 'group';
1264 $deconstructed['group'] = $input->group_id;
1265 }
1266
1267 if (!empty($input->view)) {
1268 if ($input->view == 'collections' && empty($input->collection_id)) {
1269 $deconstructed['display_type'] = 'collections';
1270 }
1271 else if ($input->view == 'galleries' && empty($input->gallery_id)) {
1272 $deconstructed['display_type'] = 'multi-gallery';
1273 }
1274 else if ($input->view == 'photosets' && empty($input->photoset_id)) {
1275 $deconstructed['display_type'] = 'multi-album';
1276 }
1277 else if ($input->view == 'photos') {
1278 $deconstructed['display_type'] = 'multi-photo';
1279 }
1280 }
1281
1282 if (!empty($input->collection_id)) {
1283 $deconstructed['display_type'] = 'collection';
1284 $deconstructed['selected_data'] = $input->collection_id;
1285 }
1286 else if (!empty($input->gallery_id)) {
1287 $deconstructed['display_type'] = 'gallery-photo';
1288 $deconstructed['selected_data'] = $input->gallery_id;
1289 }
1290 else if (!empty($input->photoset_id)) {
1291 $deconstructed['display_type'] = 'album-photo';
1292 $deconstructed['selected_data'] = $input->photoset_id;
1293 }
1294 else if (!empty($input->photo_id)) {
1295 $deconstructed['display_type'] = 'single-photo';
1296 $deconstructed['selected_data'] = $input->photo_id;
1297 }
1298
1299 break;
1300
1301 case 'smugmug':
1302 if (!empty($input->view)) {
1303 if ($input->view == 'tree') {
1304 $deconstructed['display_type'] = 'tree';
1305 }
1306 else if ($input->view == 'folder') {
1307 $deconstructed['display_type'] = 'folder';
1308 }
1309 else if ($input->view == 'albums') {
1310 $deconstructed['display_type'] = 'multi-album';
1311 }
1312 else if ($input->view == 'images' || $input->view == 'album') {
1313 if (!empty($input->album) || !empty($input->album_key) || !empty($input->album_id)) {
1314 $deconstructed['display_type'] = 'album-photo';
1315 }
1316 else if (!empty($input->folder)) {
1317 $deconstructed['display_type'] = 'folder-photo';
1318 }
1319 else if (!empty($input->nick_name)) {
1320 $deconstructed['display_type'] = 'user-photo';
1321 }
1322 }
1323
1324 if (!empty($input->folder)) {
1325 $deconstructed['selected_data'] = trim($input->folder);
1326 }
1327 else if (!empty($input->album_key)) { // old syntax
1328 $deconstructed['selected_data'] = $input->album_key;
1329 }
1330 else if (!empty($input->album)) {
1331 $album = explode('_', $input->album);
1332 if (count($album) == 2) {
1333 $deconstructed['selected_data'] = $album[1];
1334 }
1335 else if (count($album) == 1) {
1336 $deconstructed['selected_data'] = $album[0];
1337 }
1338 }
1339
1340 if (!empty($input->nick_name)) {
1341 $deconstructed['for'] = 'other';
1342 $deconstructed['user'] = $input->nick_name;
1343 }
1344 }
1345 break;
1346
1347 case 'google':
1348 if (!empty($input->view)) {
1349 if ($input->view == 'photos') {
1350 if (!empty($input->album_id)) {
1351 $deconstructed['display_type'] = 'album-photo';
1352 $deconstructed['selected_data'] = sanitize_text_field($input->album_id);
1353 }
1354 else {
1355 $deconstructed['display_type'] = 'multi-photo';
1356 if (!empty($input->date_filters)) {
1357 $filters = sanitize_text_field($input->date_filters);
1358 $filters = explode(',', $filters);
1359 $date_filters = [];
1360 $date_range_filters = [];
1361 foreach ($filters as $filter) {
1362 $maybe_range = explode('-', $filter);
1363 if (count($maybe_range) == 2) {
1364 $date_range_filters[] = $filter;
1365 }
1366 else if (count($maybe_range) == 1) {
1367 $date_filters[] = $filter;
1368 }
1369 }
1370 if (count($date_range_filters) > 5) {
1371 $date_range_filters = array_slice($date_range_filters, 0, 5);
1372 }
1373 if (count($date_filters) > 5) {
1374 $date_filters = array_slice($date_filters, 0, 5);
1375 }
1376 $date_range_filters = implode(',', $date_range_filters);
1377 $date_filters = implode(',', $date_filters);
1378 if (!empty($date_filters)) {
1379 $deconstructed['date_filters'] = $date_filters;
1380 }
1381 if (!empty($date_range_filters)) {
1382 $deconstructed['date_range_filters'] = $date_range_filters;
1383 }
1384 }
1385 }
1386 }
1387 else if ($input->view == 'albums') {
1388 $deconstructed['display_type'] = 'multi-album';
1389 }
1390 }
1391 break;
1392
1393 case 'zenfolio':
1394 if (!empty($input->view)) {
1395 if ($input->view == 'photos' && empty($input->object_id)) {
1396 $deconstructed['display_type'] = 'multi-photo';
1397 }
1398 else if ($input->view == 'photos' && !empty($input->object_id)) {
1399 $deconstructed['display_type'] = 'single-photo';
1400 $deconstructed['selected_data'] = $input->object_id;
1401 }
1402 else if ($input->view == 'photosets') {
1403 if (!empty($input->object_id)) {
1404 $deconstructed['display_type'] = 'gallery-photo';
1405 $deconstructed['selected_data'] = $input->object_id;
1406 }
1407 else if (empty($input->photoset_type)) {
1408 $deconstructed['display_type'] = 'multi-gallery-collection';
1409 }
1410 else if (strtolower($input->photoset_type) == 'collection') {
1411 $deconstructed['display_type'] = 'multi-collection';
1412 }
1413 else if (strtolower($input->photoset_type) == 'gallery') {
1414 $deconstructed['display_type'] = 'multi-gallery';
1415 }
1416 }
1417 else if ($input->view == 'hierarchy') {
1418 $deconstructed['display_type'] = 'group-hierarchy';
1419 }
1420 else if ($input->view == 'group') {
1421 $deconstructed['display_type'] = 'group';
1422 if (!empty($input->object_id)) {
1423 $deconstructed['selected_data'] = $input->object_id;
1424 }
1425 }
1426
1427 if ($input->view == 'photosets' || $input->view == 'hierarchy' || $input->view == 'group' ||
1428 $deconstructed['display_type'] == 'single-photo') {
1429 global $photonic_zenfolio_default_user;
1430 if (!isset($input->login_name) && !empty($photonic_zenfolio_default_user)) {
1431 $deconstructed['for'] = 'current';
1432 }
1433 else if (!empty($input->login_name)) {
1434 $deconstructed['login_name'] = $input->login_name;
1435 $deconstructed['for'] = 'other';
1436 }
1437 }
1438 }
1439 break;
1440
1441 case 'instagram':
1442 if (!empty($input->media_id)) {
1443 $deconstructed['display_type'] = 'single-photo';
1444 $deconstructed['selected_data'] = $input->media_id;
1445 }
1446 else if (!empty($input->carousel)) {
1447 $deconstructed['display_type'] = 'album-photo';
1448 $deconstructed['selected_data'] = $input->carousel;
1449 }
1450 else {
1451 $deconstructed['display_type'] = 'multi-photo';
1452 }
1453 break;
1454
1455 default:
1456 case 'wp':
1457 case 'default':
1458 if (empty($input->id) && empty($input->ids) && empty($input->include)) {
1459 $deconstructed['display_type'] = 'current-post';
1460 }
1461 else if (!empty($input->ids) || !empty($input->include)) {
1462 $deconstructed['display_type'] = 'multi-photo';
1463 $deconstructed['selected_data'] = !empty($input->ids) ? $input->ids : $input->include;
1464 }
1465 break;
1466 }
1467
1468 if (!empty($input->filter)) {
1469 $deconstructed['selected_data'] = $input->filter;
1470 if (empty($input->filter_type) && in_array($input->filter_type, ['include', 'exclude'])) {
1471 $deconstructed['selection'] = $input->filter_type;
1472 }
1473 else {
1474 $deconstructed['selection'] = 'selected';
1475 }
1476 }
1477 else {
1478 $deconstructed['selection'] = 'all';
1479 }
1480
1481 if (isset($this->aka_attributes[$deconstructed['provider']])) {
1482 $aka_attributes = $this->aka_attributes[$deconstructed['provider']];
1483 foreach ($aka_attributes as $attr => $aka) {
1484 if (isset($input->{$attr}) && !isset($deconstructed[$aka])) {
1485 $deconstructed[$aka] = sanitize_text_field($input->{$attr});
1486 }
1487 }
1488 }
1489
1490 $layout = empty($input->layout) ? (empty($input->style) ? '' : $input->style) : $input->layout;
1491 if (!empty($layout)) {
1492 if (in_array($layout, ['square', 'circle', 'random', 'masonry', 'mosaic',])) {
1493 $deconstructed['layout'] = $layout;
1494 }
1495 else if (in_array($layout, ['strip-above', 'strip-below', 'strip-right', 'no-strip'])) {
1496 $deconstructed['layout'] = 'slideshow';
1497 $deconstructed['slideshow-style'] = $layout;
1498 }
1499 }
1500
1501 $same_name_attrs = array_merge($this->shortcode_attributes['common'], $this->shortcode_attributes[$deconstructed['provider']]);
1502 foreach ($same_name_attrs as $attr) {
1503 if (isset($input->{$attr}) && !isset($deconstructed[$attr])) {
1504 $deconstructed[$attr] = $input->{$attr};
1505 }
1506 }
1507 }
1508 }
1509 return $deconstructed;
1510 }
1511
1512 /**
1513 * Displays an array of L1, L2 or L3 objects as a series of selectable thumbnails. Titles are deliberately not displayed because they mess
1514 * with the layout if they are too long.
1515 *
1516 * @param array $objects
1517 * @param bool $provider
1518 * @param array $existing
1519 * @param array $present
1520 * @param bool $more
1521 * @return string
1522 */
1523 function get_thumbnail_display($objects, $provider = false, $existing = [], &$present = [], $more = false) {
1524 $output = '';
1525 $selected_data = empty($existing['selected_data']) ? [] : explode(',', $existing['selected_data']);
1526 foreach ($objects as $object) {
1527 if (!is_array($object)) {
1528 $output .= '<h4>'.$object."</h4>\n";
1529 continue;
1530 }
1531 if (!isset($object['id'])) {
1532 continue;
1533 }
1534 $selected = '';
1535 if (in_array($object['id'], $selected_data) || (!empty($object['alt_id']) && in_array($object['alt_id'], $selected_data))
1536 || (!empty($object['alt_id2']) && in_array($object['alt_id2'], $selected_data))) {
1537 $selected = 'selected';
1538 $present[] = $object['id'];
1539 }
1540 $passworded = !empty($object['passworded']) ? 'passworded' : '';
1541
1542 $title = !empty($object['title']) ? esc_attr($object['title']) : '';
1543 $counts = !empty($object['counters']) ? ' (' . esc_attr(implode(', ', $object['counters'])) . ')' : '';
1544 $alt = !empty($object['alt_id']) ? "data-photonic-selection-alt-id='{$object['alt_id']}'" : '';
1545 $alt_2 = !empty($object['alt_id2']) ? "data-photonic-selection-alt-id-2='{$object['alt_id2']}'" : '';
1546
1547 $output .= "<div class='photonic-flow-selector $provider $selected $passworded'>\n";
1548 $output .= "\t<div class='photonic-flow-selector-inner' data-photonic-selection-id='{$object['id']}' $alt $alt_2>\n";
1549 $output .= "\t\t<img src='{$object['thumbnail']}' alt='$title$counts' title='$title$counts' />\n";
1550 $output .= "\t</div>\n";
1551 $output .= "</div>\n";
1552 }
1553 if ($more) {
1554 return $output;
1555 }
1556
1557 $output .= "<input type='hidden' name='existing_selection' id='existing_selection' value='" . (!empty($present) ? implode(',', $present) : '') . "'/>\n";
1558 return $output;
1559 }
1560
1561 /**
1562 * A recursive call to traverse a SmugMug node and generate a list of objects, with each object corresponding to a folder.
1563 * Actually a node is used instead of the folder because the folder object is deprecated by SmugMug, but nodes are only being
1564 * used for folders in Photonic.
1565 *
1566 * @param array $objects
1567 * @param $node
1568 */
1569 private function get_smugmug_folders(&$objects, $node) {
1570 $object = [];
1571 if ($node->Type == 'Folder') {
1572 $object['id'] = $node->NodeID;
1573 $object['title'] = !empty($node->Name) ? esc_attr($node->Name) : '';
1574
1575 if (isset($node->Uris->NodeCoverImage->Image->Uris->ImageSizes->ImageSizes->ThumbImageUrl)) {
1576 $object['thumbnail'] = $node->Uris->NodeCoverImage->Image->Uris->ImageSizes->ImageSizes->ThumbImageUrl;
1577 }
1578 else {
1579 $object['thumbnail'] = trailingslashit(PHOTONIC_URL) . 'include/images/placeholder-Th.png';
1580 }
1581
1582 if (isset($node->Uris->ChildNodes->Node)) {
1583 $child_nodes = $node->Uris->ChildNodes->Node;
1584 if (is_array($child_nodes)) {
1585 foreach ($child_nodes as $child_node) {
1586 $this->get_smugmug_folders($objects, $child_node);
1587 }
1588 }
1589 }
1590 $objects[] = $object;
1591 }
1592 }
1593
1594 /**
1595 * A recursive call to traverse a Zenfolio group and generate a list of objects, with each object corresponding to a photoset.
1596 *
1597 * @param $objects
1598 * @param $elements
1599 * @param $display_type
1600 */
1601 private function get_zenfolio_groups(&$objects, $elements, $display_type) {
1602 if (!empty($elements->PhotoSet) && $display_type != 'group') {
1603 foreach ($elements->PhotoSet as $photoset) {
1604 if ((($display_type == 'gallery-photo' || $display_type == 'multi-gallery') && $photoset->Type == 'Gallery') ||
1605 (($display_type == 'collection-photo' || $display_type == 'multi-collection') && $photoset->Type == 'Collection') ||
1606 $display_type == 'multi-gallery-collection' || $display_type == 'group-hierarchy' || $display_type == 'single-photo'
1607 ) {
1608 $object = [];
1609 $page_url = parse_url($photoset->PageUrl);
1610 $page_url = $page_url['path'];
1611 $page_url = explode('/', $page_url);
1612 if (count($page_url) > 1) {
1613 $page_url = $page_url[1];
1614 }
1615
1616 if (!is_array($page_url) && preg_match('/^p\d{9}/', $page_url) === 0) {
1617 $page_url = [];
1618 }
1619
1620 $object['id'] = !is_array($page_url) ? $page_url : $photoset->Id;
1621 $object['alt_id'] = $photoset->Id;
1622 $object['title'] = esc_attr($photoset->Title);
1623 $object['counters'] = [esc_html(sprintf(_n('%s media item', '%s media items', $photoset->PhotoCount, 'photonic'), $photoset->PhotoCount))];
1624
1625 $photo = $photoset->TitlePhoto;
1626 $object['thumbnail'] = 'https://' . $photo->UrlHost . $photo->UrlCore . '-1.jpg';
1627
1628 if (!empty($photoset->AccessDescriptor) && !empty($photoset->AccessDescriptor->AccessType) && $photoset->AccessDescriptor->AccessType == 'Password') {
1629 $object['passworded'] = 1;
1630 }
1631
1632 $objects[] = $object;
1633 }
1634 }
1635 }
1636
1637 if (!empty($elements->Group)) {
1638 foreach ($elements->Group as $group) {
1639 if ($display_type == 'group' || $display_type == 'group-hierarchy') {
1640 $object = [];
1641 $object['id'] = $group->Id;
1642 $object['title'] = $group->Title;
1643 $object['thumbnail'] = trailingslashit(PHOTONIC_URL) . 'include/images/placeholder-Ti.png';
1644 $objects[] = $object;
1645 }
1646
1647 if ($display_type != 'group-hierarchy' && !empty($group->Elements)) {
1648 $this->get_zenfolio_groups($objects, $group->Elements, $display_type);
1649 }
1650 }
1651 }
1652 }
1653
1654 /**
1655 * A wrapper function that invokes the individual <code>process_response</code> calls by provider.
1656 * - If the call is successful the results are passed as a thumbnail grid in an array with a "success" key
1657 * - If the call is unsuccessful an error message is passed in an array with an "error" key
1658 *
1659 * @param $response
1660 * @param $provider
1661 * @param null $display_type
1662 * @param array $form_parameters
1663 * @param array $existing
1664 * @param null $url
1665 * @param bool $more
1666 * @return array
1667 */
1668 function process_response($response, $provider, $display_type = null, $form_parameters = [], $existing = [], $url = null, $more = false) {
1669 if (!is_wp_error($response)) {
1670 if (isset($response['response']) && isset($response['response']['code'])) {
1671 if ($response['response']['code'] == 200) {
1672 $pagination = [];
1673 if ($provider == 'flickr') {
1674 $objects = $this->process_flickr_response($response, $display_type, $url, $pagination);
1675 }
1676 else if ($provider == 'smugmug') {
1677 $objects = $this->process_smugmug_response($response, $display_type, $url, $pagination);
1678 }
1679 else if ($provider == 'google') {
1680 $objects = $this->process_google_response($response, $url, $pagination);
1681 }
1682 else if ($provider == 'zenfolio') {
1683 $objects = $this->process_zenfolio_response($response, $display_type);
1684 }
1685 else if ($provider == 'instagram') {
1686 $objects = $this->process_instagram_response($response, $display_type, $url, $pagination);
1687 }
1688
1689 if (empty($objects)) {
1690 return ['error' => $this->error_no_data_returned];
1691 }
1692 else if (!empty($objects['error']) || !empty($objects['success'])) {
1693 // Happens for "Find user" kind of calls
1694 return $objects;
1695 }
1696
1697 $present = [];
1698 $output = $this->get_thumbnail_display($objects, $provider, $existing, $present, $more);
1699
1700 $selected_data = empty($existing['selected_data']) ? [] : explode(',', $existing['selected_data']);
1701 $missing = array_diff($selected_data, $present);
1702 if (!empty($missing)) {
1703 //
1704 }
1705
1706 if (!empty($pagination['url'])) {
1707 $data_display_type = empty($display_type) ? '' : $display_type;
1708 $output .= "<div class='photonic-more-wrapper'>\n".
1709 "\t<a href='#' class='photonic-flow-more' data-photonic-more-link='{$pagination['url']}' data-photonic-display-type='$data_display_type' data-photonic-provider='$provider'>".esc_html__('Load More', 'photonic')."</a>\n".
1710 "</div>";
1711 }
1712
1713 if (!$more) {
1714 foreach ($form_parameters as $id => $value) {
1715 $output .= "<input type='hidden' name='$id' value='" . esc_attr($value) . "' />\n";
1716 }
1717 }
1718 return ['success' => $output];
1719 }
1720 else {
1721 Photonic::log($response['response']);
1722 return ['error' => sprintf(esc_html__('No data returned. Error code %s', 'photonic'), $response['response']['code'])];
1723 }
1724 }
1725 else {
1726 Photonic::log($response);
1727 return ['error' => esc_html__('No data returned. Empty response, or empty error code.', 'photonic')];
1728 }
1729 }
1730 else {
1731 return ['error' => $response->get_error_message()];
1732 }
1733 }
1734
1735 /**
1736 * Processes a response from Flickr to build it out into a gallery of thumbnails. Flickr has L1, L2 and L3 displays in the flow.
1737 *
1738 * @param $response
1739 * @param $display_type
1740 * @param null $url
1741 * @param array $pagination
1742 * @return array
1743 */
1744 private function process_flickr_response($response, $display_type, $url = null, &$pagination = []) {
1745 $objects = [];
1746 $body = json_decode(wp_remote_retrieve_body($response));
1747
1748 if (isset($body->stat) && $body->stat == 'fail') {
1749 Photonic::log($response);
1750 return ['error' => $body->message];
1751 }
1752
1753 if (isset($body->photosets) && isset($body->photosets->photoset)) {
1754 // $page = (int)($body->photosets->page); // Not needed because apparently the photosets.getList call returns all photosets for now
1755 // $pages = (int)($body->photosets->pages); // Not needed because apparently the photosets.getList call returns all photosets for now
1756 $photosets = $body->photosets->photoset;
1757 foreach ($photosets as $flickr_object) {
1758 $object = [];
1759 $object['id'] = $flickr_object->id;
1760 $object['title'] = esc_attr($flickr_object->title->_content);
1761 $object['counters'] = [];
1762 if (!empty($flickr_object->photos)) $object['counters'][] = esc_html(sprintf(_n('%s photo', '%s photos', $flickr_object->photos, 'photonic'), $flickr_object->photos));
1763 if (!empty($flickr_object->videos)) $object['counters'][] = esc_html(sprintf(_n('%s video', '%s videos', $flickr_object->videos, 'photonic'), $flickr_object->videos));
1764 $object['thumbnail'] = "https://farm" . $flickr_object->farm . ".staticflickr.com/" . $flickr_object->server . "/" . $flickr_object->primary . "_" . $flickr_object->secret . "_q.jpg";
1765 $objects[] = $object;
1766 }
1767 }
1768 else if (isset($body->galleries) && isset($body->galleries->gallery)) {
1769 $page = (int)($body->galleries->page);
1770 $pages = (int)($body->galleries->pages);
1771 $galleries = $body->galleries->gallery;
1772 foreach ($galleries as $flickr_object) {
1773 $object = [];
1774 $object['id'] = $flickr_object->id;
1775 $object['title'] = esc_attr($flickr_object->title->_content);
1776 $object['counters'] = [];
1777 if (!empty($flickr_object->count_photos)) $object['counters'][] = esc_html(sprintf(_n('%s photo', '%s photos', $flickr_object->count_photos, 'photonic'), $flickr_object->count_photos));
1778 if (!empty($flickr_object->count_videos)) $object['counters'][] = esc_html(sprintf(_n('%s video', '%s videos', $flickr_object->count_videos, 'photonic'), $flickr_object->count_videos));
1779 $object['thumbnail'] = "https://farm" . $flickr_object->primary_photo_farm . ".staticflickr.com/" . $flickr_object->primary_photo_server . "/" . $flickr_object->primary_photo_id . "_" . $flickr_object->primary_photo_secret . "_q.jpg";
1780 $objects[] = $object;
1781 }
1782 }
1783 else if (isset($body->photos) && isset($body->photos->photo)) {
1784 if ($display_type == 'single-photo') {
1785 $page = (int)($body->photos->page);
1786 $pages = (int)($body->photos->pages);
1787 if ($page < $pages && !empty($url)) {
1788 $url = remove_query_arg('page', $url);
1789 $pagination['url'] = add_query_arg(['page' => $page + 1], $url);
1790 }
1791 }
1792 $photos = $body->photos->photo;
1793 foreach ($photos as $flickr_object) {
1794 $object = [];
1795 $object['id'] = $flickr_object->id;
1796 $object['title'] = esc_attr($flickr_object->title);
1797 $object['thumbnail'] = 'https://farm' . $flickr_object->farm . '.staticflickr.com/' . $flickr_object->server . '/' . $flickr_object->id . '_' . $flickr_object->secret . '_q.jpg';
1798 $objects[] = $object;
1799 }
1800 }
1801 else if (isset($body->collections) && isset($body->collections->collection)) {
1802 $collections = $body->collections->collection;
1803 foreach ($collections as $flickr_object) {
1804 $object = [];
1805 $object['id'] = $flickr_object->id;
1806 $object['title'] = esc_attr($flickr_object->title);
1807 $object['counters'] = [];
1808 if (!empty($flickr_object->set)) $object['counters'][] = esc_html(sprintf(_n('%s album', '%s albums', count($flickr_object->set), 'photonic'), count($flickr_object->set)));
1809 $object['thumbnail'] = $flickr_object->iconlarge;
1810 $objects[] = $object;
1811 }
1812 }
1813 else if (isset($body->user)) {
1814 return ['success' => ['user_id' => $body->user->id]];
1815 }
1816 else if (isset($body->group)) {
1817 return ['success' => ['group_id' => $body->group->id]];
1818 }
1819
1820 if (!empty($page) && !empty($pages) && $page < $pages && !empty($url)) {
1821 $url = remove_query_arg('page', $url);
1822 $pagination['url'] = add_query_arg(['page' => $page + 1], $url);
1823 }
1824 return $objects;
1825 }
1826
1827 /**
1828 * Processes a response from SmugMug to build it out into a gallery of thumbnails. SmugMug has L1, L2 and L3 displays in the flow.
1829 *
1830 * @param $response
1831 * @param $display_type
1832 * @param null $url
1833 * @param array $pagination
1834 * @return array
1835 */
1836 private function process_smugmug_response($response, $display_type, $url = null, &$pagination = []) {
1837 $objects = [];
1838 $body = json_decode(wp_remote_retrieve_body($response));
1839 if (isset($body->Response) && isset($body->Response->Album)) {
1840 $albums = $body->Response->Album;
1841 foreach ($albums as $album) {
1842 $object = [];
1843 if (isset($album->AlbumKey)) {
1844 $object['id'] = $album->AlbumKey;
1845 }
1846 else {
1847 $uri = $album->Uris->Album->Uri;
1848 $uri = explode('/', $uri);
1849 $object['id'] = $uri[count($uri) - 1];
1850 }
1851 $object['title'] = !empty($album->Name) ? esc_attr($album->Name) : '';
1852 if (isset($album->ImageCount)) {
1853 $object['counters'] = [esc_html(sprintf(_n('%s media item', '%s media items', $album->ImageCount, 'photonic'), $album->ImageCount))];
1854 }
1855
1856 $highlight = $album->Uris->HighlightImage;
1857 if (isset($highlight->Image)) {
1858 $thumbURL = $highlight->Image->Uris->ImageSizes->ImageSizes->ThumbImageUrl;
1859 }
1860 else {
1861 $thumbURL = trailingslashit(PHOTONIC_URL) . 'include/images/placeholder-Th.png';
1862 }
1863
1864 $object['thumbnail'] = $thumbURL;
1865 if (isset($album->SecurityType) && $album->SecurityType == 'Password') {
1866 $object['passworded'] = 1;
1867 }
1868
1869 $objects[] = $object;
1870 }
1871
1872 if (isset($body->Response->Pages)) {
1873 $pages = $body->Response->Pages;
1874 if ($pages->Start + $pages->Count - 1 < $pages->Total) {
1875 $pagination['url'] = add_query_arg(['start' => $pages->Start + $pages->Count, 'count' => $pages->Count], remove_query_arg(['start', 'count'], $url));
1876 }
1877 }
1878 }
1879 else if (isset($body->Response) && isset($body->Response->Node)) {
1880 $nodes = $body->Response->Node;
1881 foreach ($nodes as $node) {
1882 if ($display_type == 'folder' && $node->Type == 'Album') {
1883 continue;
1884 }
1885 else if ($display_type == 'folder') {
1886 $this->get_smugmug_folders($objects, $node);
1887 }
1888 else {
1889 $object = [];
1890 if ($node->Type == 'Album') {
1891 $uri = $node->Uris->Album->Uri;
1892 $uri = explode('/', $uri);
1893 $uri = $uri[count($uri) - 1];
1894 $object['id'] = $uri;
1895 }
1896 else if ($node->Type == 'Folder') {
1897 $object['id'] = $node->NodeID;
1898 }
1899 $object['title'] = !empty($node->Name) ? esc_attr($node->Name) : '';
1900 if ($display_type == 'tree') {
1901 $object['title'] .= " ({$node->Type})";
1902 }
1903 if (isset($node->Uris->NodeCoverImage->Image->Uris->ImageSizes->ImageSizes->ThumbImageUrl)) {
1904 $object['thumbnail'] = $node->Uris->NodeCoverImage->Image->Uris->ImageSizes->ImageSizes->ThumbImageUrl;
1905 }
1906 else {
1907 $object['thumbnail'] = trailingslashit(PHOTONIC_URL) . 'include/images/placeholder-Th.png';
1908 }
1909 $objects[] = $object;
1910 }
1911 }
1912 }
1913 else if (isset($body->Response) && isset($body->Response->User)) {
1914 $user = $body->Response->User;
1915 return ['success' => $user->Uris->Node->Uri];
1916 }
1917 else {
1918 Photonic::log($body);
1919 return ['error' => $this->error_not_found];
1920 }
1921
1922 return $objects;
1923 }
1924
1925 /**
1926 * Processes a response from Google to build it out into a gallery of thumbnails. Google has both, L1 and L2 displays in the flow.
1927 *
1928 * @param $response
1929 * @param $url
1930 * @param array $pagination
1931 * @return array
1932 */
1933 function process_google_response($response, $url, &$pagination = []) {
1934 $objects = [];
1935 $body = json_decode(wp_remote_retrieve_body($response));
1936 if (isset($body->albums)) {
1937 $albums = $body->albums;
1938 foreach ($albums as $album) {
1939 $object = [];
1940 $object['id'] = $album->id;
1941 $object['title'] = !empty($album->title) ? esc_attr($album->title) : '';
1942 $object['counters'] = [esc_html(sprintf(_n('%s media item', '%s media items', $album->mediaItemsCount, 'photonic'), $album->mediaItemsCount))];
1943 $object['thumbnail'] = $album->coverPhotoBaseUrl . "=w150-h150-c";
1944 $objects[] = $object;
1945 }
1946 if (!empty($body->nextPageToken)) {
1947 $pagination['url'] = add_query_arg(['pageToken' => $body->nextPageToken], remove_query_arg(['pageToken'], $url));
1948 }
1949 }
1950 else if (isset($body->mediaItems)) {
1951 $photos = $body->mediaItems;
1952 foreach ($photos as $photo) {
1953 $object = [];
1954 $object['id'] = $photo->id;
1955 $object['title'] = !empty($photo->description) ? esc_attr($photo->description) : '';
1956 $object['thumbnail'] = $photo->baseUrl . "=w150-h150-c";
1957 $objects[] = $object;
1958 }
1959 }
1960 else if (isset($body->error)) {
1961 $objects['error'] = $body->error->message;
1962 }
1963 return $objects;
1964 }
1965
1966 /**
1967 * Processes a response from Zenfolio to build it out into a gallery of thumbnails. Zenfolio has both, L1 and L2 displays in the flow.
1968 *
1969 * @param $response
1970 * @param $display_type
1971 * @return array
1972 */
1973 function process_zenfolio_response($response, $display_type) {
1974 $body = wp_remote_retrieve_body($response);
1975 $body = preg_replace('/"Id":(\d+)/', '"Id":"$1"', $body);
1976 $body = simplexml_load_string($body);
1977 $objects = [];
1978 if ($display_type != 'multi-photo') {
1979 if (!empty($body->Elements)) {
1980 $elements = $body->Elements;
1981 $this->get_zenfolio_groups($objects, $elements, $display_type);
1982 }
1983 else if (!empty($body->PhotoSets)) {
1984 $photosets = $body->PhotoSets;
1985 $this->get_zenfolio_groups($objects, $photosets, $display_type);
1986 }
1987
1988 if ($display_type == 'single-photo') {
1989 $photo_array = [];
1990 global $photonic_zenfolio_gallery;
1991 if (!isset($photonic_zenfolio_gallery)) {
1992 $photonic_zenfolio_gallery = new Photonic_Zenfolio_Processor();
1993 }
1994 $requests = [];
1995 foreach ($objects as $object) {
1996 $parameters = [];
1997 $parameters['photoSetId'] = substr($object['id'], 1);
1998 $parameters['level'] = 'Level1';
1999 $parameters['includePhotos'] = 'true';
2000
2001 $request = $photonic_zenfolio_gallery->prepare_request('LoadPhotoSet', $parameters);
2002 $requests[] = [
2003 'url' => 'https://api.zenfolio.com/api/1.8/zfapi.asmx',
2004 'type' => 'POST',
2005 'headers' => $request['headers'],
2006 'data' => $request['body'],
2007 ];
2008 }
2009 $responses = Requests::request_multiple($requests);
2010 if (!empty($responses)) {
2011 foreach ($responses as $ps_response) {
2012 if (is_a($ps_response, 'Requests_Response')) {
2013 $ps_response = json_decode($ps_response->body);
2014 if (!empty($ps_response->result)) {
2015 $ps_response = $ps_response->result;
2016 if (!empty($ps_response->Photos)) {
2017 $photo_array['psid'.$ps_response->Id] = $ps_response->Title;
2018 foreach ($ps_response->Photos as $ps_photo) {
2019 if (array_key_exists($ps_photo->Id, $photo_array)) {
2020 continue;
2021 }
2022 $photo = [];
2023 $photo['id'] = $ps_photo->Id;
2024 $url_parts = explode('/', $ps_photo->UrlCore);
2025 $photo['alt_id'] = 'h'.dechex((int)substr($url_parts[count($url_parts) - 1], 1));
2026 $photo['alt_id2'] = $url_parts[count($url_parts) - 1];
2027 $photo['title'] = esc_attr($ps_photo->Title);
2028 $photo['thumbnail'] = 'https://' . $ps_photo->UrlHost . $ps_photo->UrlCore . '-1.jpg';
2029 $photo_array[$ps_photo->Id] = $photo;
2030 }
2031 }
2032 }
2033 }
2034 }
2035 }
2036 $objects = array_values($photo_array);
2037 }
2038 return $objects;
2039 }
2040 else {
2041 $photos = $body->Photos;
2042 foreach ($photos->Photo as $photo) {
2043 $object = [];
2044 $object['id'] = $photo->Id;
2045 $object['title'] = esc_attr($photo->Title);
2046 $object['thumbnail'] = 'https://' . $photo->UrlHost . $photo->UrlCore . '-1.jpg';
2047 $objects[] = $object;
2048 }
2049 return $objects;
2050 }
2051 }
2052
2053 /**
2054 * Processes a response from Instagram to build it out into a gallery of thumbnails. Instagram only has L1 displays.
2055 *
2056 * @param $response
2057 * @param $display_type
2058 * @param $url
2059 * @param array $pagination
2060 * @return array
2061 */
2062 function process_instagram_response($response, $display_type, $url, &$pagination = []) {
2063 $objects = [];
2064 $body = json_decode($response['body']);
2065 if (isset($body->data)) {
2066 $data = $body->data;
2067 foreach ($data as $photo) {
2068 if (isset($photo->media_type) && (strtolower($photo->media_type) == 'image' || strtolower($photo->media_type) == 'carousel_album' || strtolower($photo->media_type) == 'video')) {
2069 if (($display_type == 'album-photo' && strtolower($photo->media_type) == 'carousel_album') || $display_type != 'album-photo') {
2070 $object = [];
2071 $link = $photo->permalink;
2072 $link = explode('/', $link);
2073 if (empty($link[count($link) - 1])) {
2074 $link = $link[count($link) - 2];
2075 }
2076 else {
2077 $link = $link[count($link) - 1];
2078 }
2079 $object['alt_id'] = $link;
2080 $object['id'] = $photo->id;
2081 if (isset($photo->caption)) {
2082 $object['title'] = esc_attr($photo->caption);
2083 }
2084 else {
2085 $object['title'] = '';
2086 }
2087 $object['thumbnail'] = empty($photo->thumbnail_url) ? $photo->media_url : $photo->thumbnail_url;
2088 $objects[] = $object;
2089 }
2090 }
2091 }
2092 if (isset($body->pagination) && isset($body->pagination->next_max_id) && $display_type == 'single-photo') {
2093 $pagination['url'] = add_query_arg(['max_id' => $body->pagination->next_max_id], remove_query_arg(['max_id'], $url));
2094 }
2095 }
2096 return $objects;
2097 }
2098 }
2099