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

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