PluginProbe
Photonic Gallery & Lightbox for Flickr, SmugMug & Others / 3.36
Photonic Gallery & Lightbox for Flickr, SmugMug & Others v3.36
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 / Wizard / Wizard.php

Wizard.php in Photonic Gallery & Lightbox for Flickr, SmugMug & Others 3.36, at Admin/Wizard/Wizard.php

1,104 lines 47.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Photonic_Plugin\Admin\Wizard;
4
5 use Photonic_Plugin\Core\Photonic;
6
7 /**
8 * Class Wizard
9 * This is the core module for displaying the gallery builder. This is used with two other files:
10 * - Screen_Flow.php: contains the top-level markup for the gallery builder
11 * - Screen_Fields.php: contains all the fields by screen
12 *
13 * @since 2.00
14 */
15 class Wizard {
16 private Screen_Fields $flow_fields;
17 private array $display_types;
18 private array $shortcode_attributes;
19 private array $aka_attributes;
20 private array $date_parts;
21 private array $date_part_hints;
22 private string $error_mandatory;
23 private string $error_no_response;
24 private string $error_not_found;
25 public string $error_no_data_returned;
26 private string $error_not_permitted;
27 private string $error_authentication;
28 private string $error_missing_api;
29 private int $force_next_screen;
30 private int $force_previous_screen;
31 private bool $is_gutenberg = false;
32
33 public function __construct() {
34 require_once "Screen_Fields.php";
35 $this->flow_fields = new Screen_Fields();
36
37 $this->force_next_screen = -1;
38 $this->force_previous_screen = -1;
39
40 $this->error_mandatory = esc_html__('Please fill the mandatory fields. Mandatory fields are marked with a red "*".', 'photonic');
41 $this->error_no_response = esc_html__('No response from server.', 'photonic');
42 $this->error_not_found = esc_html__('Not found.', 'photonic');
43 $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');
44 $this->error_not_permitted = esc_html__('Incorrect value passed for "%1$s": %2$s', 'photonic');
45 $this->error_missing_api = esc_html__('Please set up your %1$s and secret under %2$s', 'photonic');
46 $this->error_authentication = esc_html__('Please set up your %1$s Authentication from %2$s', 'photonic');
47
48 $this->date_parts = [0 => esc_html__('Year', 'photonic'), 1 => esc_html__('Month', 'photonic'), 2 => esc_html__('Date', 'photonic')];
49 $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')];
50
51 $this->display_types = [
52 'single-photo' => 0,
53 'multi-photo' => 1,
54 'album-photo' => 1,
55 'folder-photo' => 1,
56 'user-photo' => 1,
57 'gallery-photo' => 1,
58 'collection-photo' => 1,
59 'current-post' => 1,
60 'another-post' => 1,
61 'shared-album-photo' => 1,
62 'multi-album' => 2,
63 'multi-gallery' => 2,
64 'multi-collection' => 2,
65 'multi-gallery-collection' => 2,
66 'collection' => 3,
67 'collections' => 3,
68 'folder' => 3,
69 'tree' => 3,
70 'group' => 3,
71 'group-hierarchy' => 3,
72 ];
73
74 $this->shortcode_attributes = [
75 'common' => [
76 'columns', 'count', 'more', 'photo_count', 'photo_more', 'photo_layout', 'title_position', 'caption', 'media', 'main_size', 'thumb_size', 'tile_size', 'video_size', 'popup', 'thumbnail_effect', 'show_gallery', 'load_mode', 'layout_engine', 'headers', // All lightbox
77 'speed', 'timeout', 'fx', 'pause', 'strip-style', 'controls', // Slideshow
78 ],
79 'flickr' => ['user_id', 'group_id', 'collections_display', 'tags', 'tag_mode', 'text', 'sort', 'privacy_filter'],
80 'smugmug' => ['nick_name', 'password', 'text', 'keywords', 'sort_order', 'sort_method', 'album_sort_order'],
81 'google' => ['user_id', 'access', 'protection', 'crop_thumb', 'content_filters'],
82 'zenfolio' => ['login_name', 'text', 'category_code', 'sort_order', 'structure', 'password'],
83 'instagram' => ['embed_type', 'carousel_handling', 'carousel_position'],
84 'wp' => ['order', 'orderby', 'link'],
85 ];
86 $this->aka_attributes = [
87 'flickr' => ['per_page' => 'count'],
88 'zenfolio' => ['limit' => 'count'],
89 'wp' => ['slide_size' => 'main_size'],
90 ];
91 }
92
93 /**
94 * 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.
95 * The function performs validations behind the scenes and if everything looks OK it returns the content for the next screen, otherwise
96 * an error is returned.
97 *
98 * @return string
99 */
100 public function get_screen(): string {
101 $ret = '';
102
103 if (check_ajax_referer('photonic-wizard-next-' . get_current_user_id())) {
104 $screen = sanitize_text_field(wp_unslash($_POST['screen'] ?? 0));
105 $provider = sanitize_text_field(wp_unslash($_POST['provider'] ?? ''));
106 $display_type = sanitize_text_field(wp_unslash($_POST['display_type'] ?? ''));
107
108 $raw_shortcode = !empty($_POST['photonic-editor-shortcode-raw']) ? sanitize_text_field($_POST['photonic-editor-shortcode-raw']) : '';
109 if (!empty($raw_shortcode)) {
110 $input = base64_decode($raw_shortcode); // The in-flight shortcode is passed from screen to screen using the JS function `btoa`, in wizard.js, which encodes it
111 $input = json_decode($input);
112 if (!empty($input->shortcode) && !empty($input->shortcode->attrs) && !empty($input->shortcode->attrs->named)) {
113 $input = $input->shortcode->attrs->named;
114 }
115 }
116 else {
117 $raw_shortcode = !empty($_POST['photonic-editor-json']) ? stripslashes_deep(sanitize_text_field($_POST['photonic-editor-json'])) : '';
118 $input = json_decode($raw_shortcode);
119 if (!empty($_POST['photonic-gutenberg-active'])) {
120 $this->is_gutenberg = true;
121 }
122 }
123
124 $deconstructed = $this->deconstruct_shortcode($input);
125
126 $output = $this->validate($screen, $provider, $deconstructed);
127 if (!empty($output['error'])) {
128 return '<div class="photonic-flow-error">' . wp_kses_post($output['error']) . "</div>\n";
129 }
130
131 $screen = ((int) $screen) + 1;
132 if ($this->force_next_screen > -1) {
133 $screen = $this->force_next_screen;
134 }
135
136 if (2 === $screen || '2' === $screen) {
137 /*
138 * The display_type screen. This gathers inputs about what the user wants to show: photos from an album, a collection of
139 * albums, user trees, single photos etc., along with who the photos are from - the default user, a different user, any user etc.
140 */
141 $screen_fields = $this->flow_fields->get_screen_2_fields($provider);
142 $fields = $screen_fields['display'];// $screen_fields[$provider]['display'];
143 $ret .= $this->render_all_fields($fields, $deconstructed);
144 $ret = (empty($screen_fields['header']) ? '' : "<h1>" . wp_kses_post($screen_fields['header']) . "</h1>\n") .
145 (empty($screen_fields['notice']) ? '' : "<section class='photonic-flow-notice'>" . wp_kses_post($screen_fields['notice']) . "</section>\n") .
146 (empty($screen_fields['desc']) ? '' : "<p>" . wp_kses_post($screen_fields['desc']) . "</p>\n") .
147 $ret;
148 }
149 elseif (3 === $screen) {
150 /*
151 * The Gallery Builder screen, where all the photos / albums / folders are displayed
152 */
153 $screen_fields = $this->flow_fields->get_screen_3_fields($provider);
154 $fields = $screen_fields[$display_type]['display'];
155 $ret .= $this->render_all_fields($fields, $deconstructed);
156
157 $ret = (empty($screen_fields[$display_type]['header']) ? '' : "<h1>" . wp_kses_post($screen_fields[$display_type]['header']) . "</h1>\n") .
158 (empty($screen_fields[$display_type]['notice']) ? '' : "<section class='photonic-flow-notice'>" . wp_kses_post($screen_fields[$display_type]['notice']) . "</section>\n") .
159 (empty($screen_fields[$display_type]['desc']) ? '' : "<p>" . wp_kses_post($screen_fields[$display_type]['desc']) . "</p>\n") .
160 str_replace('{{placeholder_value}}', $output['success'], $ret);
161 }
162 elseif (4 === $screen) {
163 /*
164 * The layout selection screen
165 */
166 $ret .= $output['success'];
167 }
168 else {
169 $ret .= $output['success'];
170 }
171 }
172
173 return $ret;
174 }
175
176 /**
177 * Checks all inputs amongst themselves. If the inputs are valid, then the content for the next screen is generated by this call.
178 * All heavy logic, including API calls and processing of the responses happens here.
179 *
180 * @param $screen
181 * @param $provider
182 * @param array $existing
183 * @return array|string
184 */
185 public function validate($screen, $provider, $existing = []) {
186 if (check_ajax_referer('photonic-wizard-next-' . get_current_user_id())) {
187 if (empty($screen) || !is_numeric($screen) || (is_numeric($screen) && (intval($screen) <= 0 || intval($screen) > 5))) {
188 return ['error' => sprintf(esc_html__('Invalid screen value: %s', 'photonic'), $screen)];
189 }
190 if (!in_array($provider, ['wp', 'flickr', 'picasa', 'google', 'smugmug', 'zenfolio', 'instagram'], true)) {
191 return ['error' => sprintf(esc_html__('Invalid photo provider: %s', 'photonic'), $provider)];
192 }
193
194 $screen = intval($screen);
195 $display_type = sanitize_text_field($_POST['display_type'] ?? '');
196
197 if (1 === $screen) {
198 switch ($provider) {
199 case 'flickr':
200 global $photonic_flickr_api_key, $photonic_flickr_api_secret;
201 if (empty($photonic_flickr_api_key) || empty($photonic_flickr_api_secret)) {
202 return ['error' => sprintf($this->error_missing_api, 'Flickr API key', '<em>Photonic &rarr; Settings &rarr; Flickr &rarr; Flickr Settings</em>')];
203 }
204 break;
205
206 case 'google':
207 global $photonic_google_client_id, $photonic_google_client_secret, $photonic_google_refresh_token;
208 // if (!empty($photonic_google_use_own_keys) && (empty($photonic_google_client_id) || empty($photonic_google_client_secret))) {
209 if (empty($photonic_google_client_id) || empty($photonic_google_client_secret)) {
210 return ['error' => sprintf($this->error_missing_api, 'Google Client ID', '<em>Photonic &rarr; Settings &rarr; Google Photos &rarr; Google Photos Settings</em>')];
211 }
212 elseif (empty($photonic_google_refresh_token)) {
213 return ['error' => sprintf($this->error_authentication, 'Google Photos', '<em>Photonic &rarr; Authentication</em>')];
214 }
215 break;
216
217 case 'smugmug':
218 case 'zenfolio':
219 return '';
220
221 case 'instagram':
222 return ['error' => esc_html__('In September 2022, Meta changed the terms of the Instagram API, blocking its use by individual developers. Please consider using a business-backed plugin for Instagram, instead of an individual-backed plugin such as Photonic.', 'photonic')];
223 // global $photonic_instagram_access_token;
224 // if (empty($photonic_instagram_access_token)) {
225 // return ['error' => sprintf($this->error_authentication, 'Instagram', '<em>Photonic &rarr; Authentication</em>')];
226 // }
227 // break;
228
229 default: // wp
230 return '';
231 }
232 }
233 elseif (2 === $screen) {
234 $screen_fields = $this->flow_fields->get_screen_2_fields($provider);
235
236 $fields = $screen_fields['display'];
237 $flattened_fields = [];
238 foreach ($fields as $id => $field) {
239 if (!empty($field['type']) && 'field_list' !== $field['type']) {
240 $flattened_fields[$id] = $field;
241 }
242 elseif (!empty($field['type']) && 'field_list' === $field['type']) {
243 $flattened_fields = array_merge($field['list'], $flattened_fields);
244 }
245 }
246
247 $for = !empty($_POST['for']) ? sanitize_text_field($_POST['for']) : null;
248
249 if (empty($display_type) || (empty($for) && in_array($provider, ['flickr', 'smugmug', 'zenfolio'], true) && empty($existing))) {
250 return ['error' => $this->error_mandatory];
251 }
252
253 if (empty($for) && in_array($provider, ['flickr', 'smugmug', 'zenfolio'], true) && !empty($existing)) {
254 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')];
255 }
256
257 if (!in_array($display_type, array_keys($flattened_fields['display_type']['options']), true)) {
258 return ['error' => sprintf(esc_html__('Invalid display type: %s', 'photonic'), $display_type)];
259 }
260
261 $gallery = $this->flow_fields->get_source($provider);
262 $response = $gallery->make_request($display_type, $for, $flattened_fields);
263 if (empty($response)) { // WP
264 $this->force_next_screen = 4;
265 $this->force_previous_screen = 2;
266 return ['success' => $this->get_layout_selector($display_type, $existing)];
267 }
268 elseif (!empty($response['error'])) { // Error occurred
269 return $response;
270 }
271 else {
272 list($https, $form_parameters, $url) = $response;
273 return $this->process_response($https, $provider, $display_type, $form_parameters, $existing, $url);
274 }
275 }
276 elseif (3 === $screen) {
277 // Check for display_type
278 $screen_fields = $this->flow_fields->get_screen_3_fields($provider); // $this->field_list['screen-' . $screen];
279 $provider_fields = $screen_fields; // $screen_fields[$provider];
280 $fields = $provider_fields[$display_type]['display'];
281 foreach ($fields as $id => $field) {
282 $checks = $this->do_basic_option_check($id, $field, true);
283 if (!empty($checks)) {
284 return $checks;
285 }
286
287 if ('selection' === $id && 'selected' === sanitize_text_field($_POST['selection']) && empty($_POST['selected_data'])) {
288 return ['error' => esc_html__('Please select what you want to show.', 'photonic')];
289 }
290
291 if (in_array($display_type, ['single-photo', 'album-photo', 'gallery-photo', 'folder-photo', 'collection-photo'], true) && empty($_POST['selected_data'])) {
292 return ['error' => esc_html__('Please select what you want to show.', 'photonic')];
293 }
294 }
295
296 // All OK? Get next screen
297 if ('single-photo' !== $display_type) {
298 $output = $this->get_layout_selector($display_type, $existing, $provider);
299 }
300 else {
301 $this->force_next_screen = 6;
302 $this->force_previous_screen = 3;
303 $output = $this->construct_shortcode();
304 }
305 return ['success' => $output];
306 }
307 elseif (4 === $screen) {
308 $layout = sanitize_text_field($_POST['layout'] ?? '');
309 if (empty($layout)) {
310 return ['error' => $this->error_mandatory];
311 }
312 $layout_options = $this->flow_fields->get_layout_options();
313 if (!array_key_exists($layout, $layout_options)) {
314 return ['error' => sprintf(esc_html__('Invalid layout: %s', 'photonic'), $layout)];
315 }
316
317 // All good. Next screen:
318 return ['success' => $this->get_layout_options($provider, $display_type, $layout, $existing)];
319 }
320 else {
321 $passworded = sanitize_text_field($_POST['selection_passworded'] ?? '');
322 $password = sanitize_text_field($_POST['password'] ?? '');
323 if (!empty($passworded) && empty($password)) {
324 return ['error' => $this->error_mandatory];
325 }
326 return ['success' => $this->construct_shortcode()];
327 }
328 }
329 return '';
330 }
331
332 /**
333 * Utility method that takes an array of fields, determines if each member is a "field list" or a "field". If it is a field
334 * list it processes it as a field list, otherwise it processes it as a field.
335 *
336 * @param array $fields
337 * @param array $existing
338 * @return String
339 */
340 private function render_all_fields($fields, $existing = []): string {
341 $output = '';
342 foreach ($fields as $id => $field) {
343 if (!empty($field['type']) && 'field_list' === $field['type']) {
344 $output .= $this->process_field_list($id, $field, $existing);
345 }
346 elseif (!empty($field['type'])) {
347 $output .= $this->process_field($id, $field, 0, null, $existing);
348 }
349 }
350 return $output;
351 }
352
353 /**
354 * Takes a "field list" and processes each field in it individually. A "field list" can contain an interdependent sequence
355 * of fields, in which case each member gets a sequential number assigned to it. The actual logic of sequencing is handled on the
356 * front-end.
357 * E.g. If, for Flickr, you select "Multiple Photos", then "Another User", the front-end will show a "User" text field.
358 * But if "Group" is selected, it shows a "Group" text field
359 *
360 * @param $field_list_name
361 * @param array $field_list
362 * @param array $existing
363 * @return string
364 */
365 private function process_field_list($field_list_name, $field_list, $existing = []): string {
366 if (!is_array($field_list) || empty($field_list['type']) || 'field_list' !== $field_list['type'] || empty($field_list['list'])) {
367 return '';
368 }
369 else {
370 $ret = '';
371 $counter = 0;
372 $sequence_group = null;
373 foreach ($field_list['list'] as $id => $field) {
374 if ('sequence' === $field_list['list_type']) {
375 $counter++;
376 $sequence_group = $field_list_name;
377 }
378 $ret .= $this->process_field($id, $field, $counter, $sequence_group, $existing);
379 }
380 return $ret;
381 }
382 }
383
384 /**
385 * Main code to render an input element on the flow-screen. Almost all types of inputs have switches for display here.
386 *
387 * @param string $id
388 * @param array $field
389 * @param $sequence
390 * @param null $sequence_group
391 * @param array $existing
392 * @return string
393 */
394 private function process_field($id, $field, $sequence, $sequence_group = null, $existing = []): string {
395 if (!is_array($field) || empty($field['type'])) {
396 return '';
397 }
398
399 $ret = '';
400
401 if (check_ajax_referer('photonic-wizard-next-' . get_current_user_id())) {
402 if (!empty($field['post_condition']) && is_array($field['post_condition'])) {
403 foreach ($field['post_condition'] as $var => $permitted_values) {
404 $pass = false;
405 foreach ($permitted_values as $permitted) {
406 if (isset($_POST[$var]) && $_POST[$var] === $permitted) {
407 $pass = true;
408 break;
409 }
410 }
411 if (!$pass) {
412 // Variable has not been set in a different screen. Hide this field.
413 return '';
414 }
415 }
416 }
417
418 $id = esc_attr($id);
419 $req = empty($field['req']) ? '' : '<span class="photonic-required"><abbr title="' . esc_html__('Required', 'photonic') . '">*</abbr></span>';
420 $default = esc_attr(!empty($existing[$id]) ? $existing[$id] : ($field['std'] ?? ''));
421 $hint = '';
422 $hint_in = '';
423 if (!empty($field['hint'])) {
424 $hint = "<div class='photonic-flow-hint' role='tooltip' id='{$id}-hint'>{$field['hint']}</div>\n";
425 $hint_in = "aria-describedby='" . esc_attr($id) . "-hint'";
426 }
427
428 switch ($field['type']) {
429 case 'text':
430 $ret = "<label class='photonic-wizard-option-name'>" . wp_kses_post($field['desc']) . $req . "<input type='text' name='$id' value='" . $default . "' $hint_in/>" . wp_kses_post($hint) . "</label>";
431 break;
432
433 case 'radio':
434 $ret = !empty($field['desc']) ? '<div class="photonic-wizard-option-name">' . wp_kses_post($field['desc']) . $req . '</div>' : '';
435 $ret .= "<div class='photonic-flow-field-radio-group'>\n";
436 foreach ($field['options'] as $option_value => $option_description) {
437 $option_condition = (empty($field['option-conditions']) || empty($field['option-conditions'][$option_value])) ? '' :
438 "data-photonic-option-condition='" . wp_json_encode($field['option-conditions'][$option_value]) . "'";
439 $checked = checked($default, $option_value, false);
440 $ret .= "\t<div class='photonic-flow-field-radio'><label><input type='radio' name='$id' value='" . esc_attr($option_value) . "' $checked $option_condition />" . wp_kses_post($option_description) . "</label></div>\n";
441 }
442 $ret .= "</div>\n";
443 break;
444
445 case 'select':
446 $ret = "<label class='photonic-wizard-option-name'>" . wp_kses_post($field['desc']) . $req . "\n\t<select name='$id' $hint_in>\n";
447 foreach ($field['options'] as $option_value => $option_description) {
448 $option_condition = (empty($field['option-conditions']) || empty($field['option-conditions'][$option_value])) ? '' :
449 "data-photonic-option-condition='" . wp_json_encode($field['option-conditions'][$option_value]) . "'";
450 $selected = selected($default, $option_value, false);
451 $ret .= "\t\t<option value='" . esc_attr($option_value) . "' $selected $option_condition>" . esc_attr($option_description) . "</option>\n";
452 }
453 $ret .= "\t</select>\n" . wp_kses_post($hint) . "</label>\n";
454 break;
455
456 case 'image-select':
457 if (!empty($default)) {
458 $selection = !in_array($default, array_keys($field['options']), true) ? array_keys($field['options'])[0] : $default;
459 }
460 else {
461 $selection = array_keys($field['options'])[0];
462 }
463
464 $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";
465 $ret .= '<div class="photonic-wizard-option-name">' . wp_kses_post($field['desc']) . '</div>';
466 foreach ($field['options'] as $option_name => $desc) {
467 $option_name = esc_attr($option_name);
468 $esc_desc = esc_attr($desc);
469 $selected = ($option_name === $selection) ? 'selected' : '';
470 $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'>" . wp_kses_post($desc) . "</div>\n</div>\n";
471 }
472 $ret .= "</div>\n";
473 break;
474
475 case 'multi-select':
476 $ret = "<div class='photonic-flow-multi-select-container'>\n";
477 $ret .= '<div class="photonic-wizard-option-name">' . wp_kses_post($field['desc']) . '</div>';
478 $selection = explode(',', $default);
479 foreach ($field['options'] as $option_value => $desc) {
480 $checked = in_array($option_value, $selection, true) ? 'checked' : '';
481 $ret .= "\t<label class='photonic-multi-select-item'><input type='checkbox' name='{$id}[]' value=\"" . esc_attr($option_value) . "\" $checked />" . wp_kses_post($desc) . "</label>\n";
482 }
483 $ret .= "</div>\n";
484 break;
485
486 case 'date-filter':
487 $ret = '';
488 $ret .= '<div class="photonic-wizard-option-name">' . wp_kses_post($field['desc']) . '</div>';
489 $dates = !empty($default) ? explode(',', $default) : [];
490 $count = isset($field['count']) && is_numeric($field['count']) ? intval($field['count']) : 1;
491 $ret .= "<ol data-photonic-date-filter='$id' data-photonic-filter-count='$count'>\n";
492 $ctr = 0;
493 foreach ($dates as $didx => $date) {
494 $y_m_d = explode('/', $date);
495 $ret .= "\t<li>\n";
496 $ret .= "\t\t<div class='photonic-single-date'>\n";
497 for ($pidx = 0; $pidx < 3; $pidx++) {
498 $lower = esc_attr(strtolower($this->date_parts[$pidx]));
499 $ret .= "\t\t\t<label class='photonic-date-filter'>\n" .
500 "\t\t\t\t" . wp_kses_post(substr($this->date_parts[$pidx], 0, 1)) . "<input type='text' class='photonic-date-$lower' name='{$id}_{$lower}[]' value='" . esc_attr($y_m_d[$pidx] ?? '') . "' aria-describedby='{$id}-{$didx}_{$lower}-hint'/>\n" .
501 "\t\t\t\t<div class='photonic-flow-hint' role='tooltip' id='{$id}-{$didx}_{$lower}-hint'>" . wp_kses_post($this->date_part_hints[$pidx]) . "</div>\n" .
502 "\t\t\t</label> \n";
503 }
504 $ret .= "\t\t</div>\n";
505 $ret .= "\t\t<a href='#' class='photonic-remove-date-filter' title='Remove filter'><span class=\"dashicons dashicons-no\"> </span></a>\n";
506 $ret .= "\t</li>\n";
507 $ctr++;
508 if ($ctr >= $count) {
509 break;
510 }
511 }
512 $ret .= "</ol>\n";
513 $ret .= "<input type='hidden' name='$id' value='" . esc_attr($default) . "'/>\n";
514 if ($ctr < $count) {
515 $ret .= "<a href='#' class='photonic-add-date-filter' data-photonic-add-date='$id'><span class=\"dashicons dashicons-plus-alt\"> </span> Add filter</a>\n";
516 }
517
518 break;
519
520 case 'date-range-filter':
521 $ret = '';
522 $ret .= '<div class="photonic-wizard-option-name">' . wp_kses_post($field['desc']) . '</div>';
523 $date_ranges = !empty($default) ? explode(',', $default) : [];
524 $count = esc_attr(isset($field['count']) && is_numeric($field['count']) ? intval($field['count']) : 1);
525 $ret .= "<ol data-photonic-date-range-filter='$id' data-photonic-filter-count='$count'>\n";
526 $ctr = 0;
527 foreach ($date_ranges as $date_range) {
528 $from_to = explode('-', $date_range);
529 if (count($from_to) !== 2) {
530 continue;
531 }
532 $ret .= "\t<li>\n";
533 foreach ($from_to as $didx => $date) {
534 $ret .= "\t\t<div class='photonic-single-date'>\n";
535 $y_m_d = explode('/', $date);
536 $from_or_to = 0 === $didx ? 'start' : 'end';
537 for ($pidx = 0; $pidx < 3; $pidx++) {
538 $lower = strtolower($this->date_parts[$pidx]);
539 $ret .= "\t\t\t<label class='photonic-date-filter'>\n" .
540 "\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=\"" . ($y_m_d[$pidx] ?? '') . "\" aria-describedby='{$id}-{$didx}_{$from_or_to}_{$lower}-hint'/>\n" .
541 "\t\t\t\t<div class='photonic-flow-hint' role='tooltip' id='{$id}-{$didx}_{$from_or_to}_{$lower}-hint'>" . wp_kses_post($this->date_part_hints[$pidx]) . "</div>\n" .
542 "\t\t\t</label> \n";
543 }
544 $ret .= "\t\t</div>\n";
545 }
546 $ret .= "\t\t<a href='#' class='photonic-remove-date-range-filter' title='Remove filter'><span class=\"dashicons dashicons-no\"> </span></a>\n";
547 $ret .= "\t</li>\n";
548 $ctr++;
549 if ($ctr >= $count) {
550 break;
551 }
552 }
553 $ret .= "</ol>\n";
554 $ret .= "<input type='hidden' name='$id' value='$default'/>\n";
555 if ($ctr < $count) {
556 $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";
557 }
558
559 break;
560
561 case 'thumbnail-selector':
562 $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";
563
564 $controls = "<div class='thumb-controls'>\n";
565 if ('none' !== $field['mode']) {
566 $controls .= "<input type='text' class='search-thumbs' name='thumb-search' id='thumb-search'/>\n";
567 }
568
569 if ('multi' === $field['mode']) {
570 $controls .= esc_html__('Mark:', 'photonic') .
571 sprintf(esc_html__('%1$sAll%2$s', 'photonic'), "<a href='#' class='photonic-mark photonic-mark-all' data-photonic-mark-for='{$field['for']}'>", '</a>') . '|' .
572 sprintf(esc_html__('%1$sNone%2$s', 'photonic'), "<a href='#' class='photonic-mark photonic-mark-none' data-photonic-mark-for='{$field['for']}'>", '</a>');
573 }
574 $controls .= "</div>\n";
575 $ret = $controls . $ret;
576 break;
577
578 default:
579 return '';
580 }
581
582 if (!empty($ret)) {
583 $sequence_str = '';
584 if (0 !== $sequence) {
585 $sequence_str = 'data-photonic-flow-sequence="' . esc_attr($sequence) . '"';
586 }
587
588 $sequence_group_str = '';
589 if (!is_null($sequence_group)) {
590 $sequence_group_str = 'data-photonic-flow-sequence-group="' . esc_attr($sequence_group) . '"';
591 }
592
593 $condition = '';
594 if (!empty($field['conditions'])) {
595 $condition = "data-photonic-condition='" . wp_json_encode($field['conditions']) . "'";
596 }
597
598 $ret = "<div class='photonic-flow-field' $sequence_str $condition $sequence_group_str>\n" . $ret . "</div>\n";
599 }
600 }
601 return $ret;
602 }
603
604 /**
605 * Performs basic checks against whitelists. More advanced checks are handled in the <code>validate</code> function
606 *
607 * @param $id
608 * @param $field
609 * @param bool $check_required
610 * @return array|bool
611 */
612 private function do_basic_option_check($id, $field, $check_required = false) {
613 if (check_ajax_referer('photonic-wizard-next-' . get_current_user_id())) {
614 if (empty($field['type']) || ('select' !== $field['type'] && 'radio' !== $field['type'])) {
615 return false;
616 }
617
618 if ($check_required && !empty($field['req']) && (!isset($_POST[$id]) || '' === trim($_POST[$id]))) {
619 return ['error' => $this->error_mandatory];
620 }
621
622 if (isset($_POST[$id]) && !in_array(sanitize_text_field($_POST[$id]), array_keys($field['options']), false)) { // Need strict = false, otherwise if $field['options'] returns integers, string comparison fails. E.g. Zenfolio categories
623 return ['error' => sprintf($this->error_not_permitted, $id, sanitize_text_field($_POST[$id]))];
624 }
625 }
626 return false;
627 }
628
629 /**
630 * A special type of selector not handled by the <code>process_field</code> call. Layouts are used only on one screen,
631 * and are used by almost all types of providers. This displays the available layouts as icons to pick from.
632 *
633 * @param $display_type
634 * @param array $existing
635 * @param null $provider
636 * @return string
637 */
638 private function get_layout_selector($display_type, $existing = [], $provider = null): string {
639 global $photonic_thumbnail_style;
640 $output = '';
641 $level = empty($this->display_types[$display_type]) ? -1 : $this->display_types[$display_type];
642 if (empty($existing['layout'])) {
643 if (in_array($photonic_thumbnail_style, ['strip-below', 'strip-above', 'strip-right', 'no-strip'], true)) {
644 $layout_from_option = 'slideshow';
645 }
646 elseif ('square' === $photonic_thumbnail_style && !empty($provider) && 'instagram' === $provider) {
647 $layout_from_option = 'random';
648 }
649 else {
650 $layout_from_option = sanitize_text_field($photonic_thumbnail_style);
651 }
652 }
653 else {
654 $layout_from_option = sanitize_text_field($existing['layout']);
655 }
656
657 if ($level > 0) {
658 $layout_options = $this->flow_fields->get_layout_options();
659 foreach ($layout_options as $layout => $desc) {
660 $selected = $layout === $layout_from_option ? 'selected' : '';
661 if (('slideshow' === $layout && 1 === $level) || 'slideshow' !== $layout) {
662 $esc_desc = esc_attr($desc);
663 $output .= "<div class=\"photonic-flow-selector photonic-flow-layout-$layout $selected\" title=\"$esc_desc\">\n
664 \t<span class=\"photonic-flow-selector-inner photonic-layout\" data-photonic-selection-id=\"$layout\">&nbsp;</span>\n
665 \t<div class='photonic-flow-selector-info'>$desc</div>\n
666 </div>\n";
667 }
668 }
669 if (!empty($output)) {
670 $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='" . esc_attr($layout_from_option) . "'/>\n$output</div>\n";
671 }
672 }
673 $output = '<h1>' . esc_html__('Pick Your Layout', 'photonic') . '</h1>' .
674 "<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" .
675 $output;
676
677 if ($this->force_next_screen > -1) {
678 $output .= "\n<input type='hidden' name='force_next_screen' value='{$this->force_next_screen}'/>\n";
679 }
680 if ($this->force_previous_screen > -1) {
681 $output .= "\n<input type='hidden' name='force_previous_screen' value='{$this->force_previous_screen}'/>\n";
682 }
683
684 return $output;
685 }
686
687 /**
688 * Layouts use similar constructs such as <code>count</code> and <code>more</code> but also have differences.
689 * E.g. <code>columns</code> are not applicable to the justified grid or mosaic layouts etc. Similarly size options
690 * vary from provider to provider. So in the Photonic_Screen_Fields we have a hierarchy for this screen, by level, provider and layout
691 *
692 * @param $provider
693 * @param $display_type
694 * @param $layout
695 * @param array $existing
696 * @return string
697 */
698 private function get_layout_options($provider, $display_type, $layout, $existing = []): string {
699 // All levels, all layouts - media
700 // L1, L2 All layouts - title position
701 // L1 All layouts - count, more
702 // L1, L2, L3 basic lightbox layouts - # of columns, constrain by etc., thumbnail size, full size
703 // L3 Flickr - auto-expand
704 $level = $this->display_types[$display_type];
705 $output = '<h1>' . esc_html__('Configure Your Layout', 'photonic') . '</h1>';
706
707 $extract = [];
708 $screen_fields = $this->flow_fields->get_screen_5_fields($provider);
709
710 if (!empty($screen_fields[$provider]['L' . $level])) {
711 $extract = array_merge($screen_fields[$provider]['L' . $level], $extract);
712 }
713
714 if (!empty($screen_fields[$layout][$provider])) {
715 $extract = array_merge($screen_fields[$layout][$provider], $extract);
716 }
717
718 if (!empty($screen_fields[$provider])) {
719 $extract = array_merge($screen_fields[$provider], $extract);
720 }
721
722 if (!empty($screen_fields['L' . $level])) {
723 $extract = array_merge($screen_fields['L' . $level], $extract);
724 }
725
726 if (!empty($screen_fields[$layout])) {
727 $extract = array_merge($screen_fields[$layout], $extract);
728 }
729
730 /* if ('wp' === $provider) {
731 unset($extract['count']);
732 unset($extract['more']);
733 }*/
734
735 $output .= $this->render_all_fields($extract, $existing);
736 return $output;
737 }
738
739 /**
740 * Builds out the shortcode based on inputs from all previous screens. Some attributes are passed as they are, e.g. <code>count</code>.
741 * Others are used to determine other attributes, e.g. <code>display_type</code>
742 *
743 * @return string
744 */
745 private function construct_shortcode(): string {
746 $output = '';
747
748 if (check_ajax_referer('photonic-wizard-next-' . get_current_user_id())) {
749 global $photonic_alternative_shortcode;
750
751 $provider = sanitize_text_field(wp_unslash($_POST['provider']));
752 $display_type = sanitize_text_field($_POST['display_type']);
753
754 $short_code = [];
755 if ('wp' !== $provider) {
756 $short_code['type'] = $provider;
757 }
758
759 // Get specific attributes for a provider
760 $gallery = $this->flow_fields->get_source($provider);
761 $gallery_attr = $gallery->construct_shortcode_from_screen_selections($display_type);
762 $short_code = array_merge($short_code, $gallery_attr);
763
764 if (!empty($_POST['selection'])) {
765 if ('all' !== $_POST['selection']) {
766 $short_code['filter'] = sanitize_text_field($_POST['selected_data']);
767 }
768 if ('not-selected' === $_POST['selection']) {
769 $short_code['filter_type'] = 'exclude';
770 }
771 }
772
773 if (isset($_POST['headers']) && '' !== $_POST['headers']) {
774 if ('none' === trim($_POST['headers'])) {
775 $short_code['headers'] = '';
776 }
777 else {
778 $short_code['headers'] = sanitize_text_field($_POST['headers']);
779 }
780 }
781
782 $additional_attrs = array_merge(
783 $this->shortcode_attributes[$provider],
784 $this->shortcode_attributes['common']
785 );
786 foreach ($additional_attrs as $attr) {
787 if (!empty($_POST[$attr]) && is_array($_POST[$attr])) {
788 $short_code[$attr] = sanitize_text_field(implode(',', $_POST[$attr]));
789 }
790 elseif (!empty($_POST[$attr])) {
791 $short_code[$attr] = sanitize_text_field($_POST[$attr]);
792 }
793 }
794
795 if (!empty($_POST['layout'])) {
796 $key = 'wp' === $provider ? 'style' : 'layout';
797 if ('slideshow' !== $_POST['layout']) {
798 $short_code[$key] = sanitize_text_field($_POST['layout']);
799 }
800 else {
801 $short_code[$key] = sanitize_text_field($_POST['slideshow-style']);
802 }
803 }
804 // layout
805
806 $raw_shortcode = !empty($_POST['photonic-editor-shortcode-raw']) ? sanitize_text_field($_POST['photonic-editor-shortcode-raw']) : '';
807 if (!empty($raw_shortcode)) {
808 $input = base64_decode($raw_shortcode); // The in-flight shortcode is passed from screen to screen using the JS function `btoa`, in wizard.js, which encodes it
809 $input = json_decode($input);
810 if (!empty($input->shortcode) && !empty($input->shortcode->attrs) && !empty($input->shortcode->attrs->named)) {
811 $input = $input->shortcode->attrs->named;
812 }
813 }
814 else {
815 $raw_shortcode = !empty($_POST['photonic-editor-json']) ? stripslashes_deep(sanitize_text_field($_POST['photonic-editor-json'])) : '';
816 $input = json_decode($raw_shortcode);
817 }
818
819 if (!empty($input)) {
820 $attr_array = (array) $input;
821
822 // If the type changes, regardless of everything else blank out the attributes
823 if (!(empty($short_code['type']) && (empty($attr_array['type']) || in_array($attr_array['type'], ['wp', 'default'], true))) &&
824 ($attr_array['type'] !== $short_code['type'])) {
825 $attr_array = [];
826 }
827 else {
828 foreach ($short_code as $key => $value) {
829 unset($attr_array[$key]);
830 }
831 }
832
833 if (!empty($this->aka_attributes[$provider])) {
834 $aka = $this->aka_attributes[$provider];
835 foreach ($aka as $key => $value) {
836 if (isset($short_code[$key]) || isset($short_code[$value])) {
837 unset($attr_array[$key]);
838 }
839 }
840 }
841
842 // Others ...
843 if (!empty($short_code['type']) && 'instagram' === $short_code['type']) {
844 if (!empty($short_code['media_id'])) {
845 unset($attr_array['view']);
846 unset($attr_array['media']);
847 }
848 elseif (!empty($short_code['media'])) {
849 unset($attr_array['media_id']);
850 }
851 }
852
853 if (!empty($short_code['type']) && 'flickr' === $short_code['type'] && in_array($display_type, ['multi-album', 'multi-gallery', 'collections'], true)) {
854 unset($attr_array['photoset_id']);
855 unset($attr_array['gallery_id']);
856 unset($attr_array['collection_id']);
857 }
858
859 foreach ($attr_array as $key => $value) {
860 if (in_array($key, ['more', 'photo_more', 'count', 'photo_count', 'photo_layout', 'show_gallery', 'order', 'orderby', 'link'], true) && empty($short_code[$key])) {
861 unset($attr_array[$key]);
862 }
863 }
864 $short_code = array_merge($short_code, $attr_array);
865 }
866
867 if (!$this->is_gutenberg) {
868 $output = '<h1>' . esc_html__('Your shortcode', 'photonic') . '</h1>';
869 $output .= "<code id='photonic_shortcode'>[" . esc_html($photonic_alternative_shortcode ?: 'gallery') . ' ';
870 $shortcode_attrs = [];
871 foreach ($short_code as $attr => $value) {
872 $shortcode_attrs[] = $attr . "='" . esc_attr($value) . "'";
873 }
874
875 $output .= implode(' ', $shortcode_attrs);
876 $output .= ']</code>';
877 $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>';
878 }
879 else {
880 $output = '<h1>' . esc_html__('Your Gallery', 'photonic') . '</h1>';
881 $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>';
882 $shortcode_attrs = [];
883 foreach ($short_code as $attr => $value) {
884 $shortcode_attrs[] = '<code>' . $attr . ": " . esc_attr($value) . '</code>';
885 }
886 $output .= implode("<br/>\n", $shortcode_attrs);
887
888 $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>';
889 $output .= "<code>[" . (empty($photonic_alternative_shortcode) ? 'gallery' : $photonic_alternative_shortcode) . ' ';
890 $shortcode_attrs = [];
891 foreach ($short_code as $attr => $value) {
892 $shortcode_attrs[] = $attr . "='" . esc_attr($value) . "'";
893 }
894
895 $output .= implode(' ', $shortcode_attrs);
896 $output .= ']</code>';
897
898 $output .= "<input id='photonic_shortcode' name='photonic_shortcode' type='hidden' value='" . esc_attr(wp_json_encode($short_code)) . "' />\n";
899 }
900
901 if ($this->force_next_screen > -1) {
902 $output .= "\n<input type='hidden' name='force_next_screen' value='{$this->force_next_screen}'/>\n";
903 }
904 if ($this->force_previous_screen > -1) {
905 $output .= "\n<input type='hidden' name='force_previous_screen' value='{$this->force_previous_screen}'/>\n";
906 }
907 }
908 return $output;
909 }
910
911 /**
912 * This is the inverse of the <code>construct_shortcode</code> method. If the Editor has a shortcode selected, this method
913 * splits it out into the relevant screens.
914 *
915 * @param $input
916 * @return array
917 */
918 private function deconstruct_shortcode($input): array {
919 $deconstructed = [];
920 if (!empty($input)) {
921 global $photonic_alternative_shortcode;
922 $shortcode_tag = $photonic_alternative_shortcode ?: 'gallery';
923
924 if ((!empty($input->type) && in_array($input->type, ['wp', 'default', 'flickr', 'smugmug', 'zenfolio'], true)) ||
925 ((empty($input->type) && !empty($input->style)) && in_array($input->style, ['square', 'circle', 'random', 'masonry', 'masonry-horizontal', 'mosaic', 'strip-above', 'strip-below', 'strip-right', 'no-strip'], true)) ||
926 (empty($input->type) && empty($input->style) && 'gallery' !== $shortcode_tag)
927 ) {
928 $deconstructed['provider'] = !empty($input->type) ? $input->type : 'wp';
929
930 $gallery = $this->flow_fields->get_source($deconstructed['provider']);
931 $deconstructed_selections = $gallery->deconstruct_shortcode_to_screen_selections($input);
932 $deconstructed = array_merge($deconstructed, $deconstructed_selections);
933
934 if (!empty($input->filter)) {
935 $deconstructed['selected_data'] = $input->filter;
936 if (!empty($input->filter_type) && in_array($input->filter_type, ['include', 'exclude'], true)) {
937 $deconstructed['selection'] = $input->filter_type;
938 }
939 else {
940 $deconstructed['selection'] = 'selected';
941 }
942 }
943 else {
944 $deconstructed['selection'] = 'all';
945 }
946
947 if (isset($this->aka_attributes[$deconstructed['provider']])) {
948 $aka_attributes = $this->aka_attributes[$deconstructed['provider']];
949 foreach ($aka_attributes as $attr => $aka) {
950 if (isset($input->{$attr}) && !isset($deconstructed[$aka])) {
951 $deconstructed[$aka] = sanitize_text_field($input->{$attr});
952 }
953 }
954 }
955
956 $layout = empty($input->layout) ? (empty($input->style) ? '' : $input->style) : $input->layout;
957 if (!empty($layout)) {
958 if (in_array($layout, ['square', 'circle', 'random', 'masonry', 'masonry-horizontal', 'mosaic'], true)) {
959 $deconstructed['layout'] = $layout;
960 }
961 elseif (in_array($layout, ['strip-above', 'strip-below', 'strip-right', 'no-strip'], true)) {
962 $deconstructed['layout'] = 'slideshow';
963 $deconstructed['slideshow-style'] = $layout;
964 }
965 }
966
967 $same_name_attrs = array_merge($this->shortcode_attributes['common'], $this->shortcode_attributes[$deconstructed['provider']]);
968 foreach ($same_name_attrs as $attr) {
969 if (isset($input->{$attr}) && !isset($deconstructed[$attr])) {
970 $deconstructed[$attr] = $input->{$attr};
971 }
972 }
973 }
974 }
975 return $deconstructed;
976 }
977
978 /**
979 * Displays an array of L1, L2 or L3 objects as a series of selectable thumbnails. Titles are deliberately not displayed because they mess
980 * with the layout if they are too long.
981 *
982 * @param array $objects
983 * @param bool $provider
984 * @param array $existing
985 * @param array $present
986 * @param bool $more
987 * @return string
988 */
989 private function get_thumbnail_display($objects, $provider = false, $existing = [], &$present = [], $more = false): string {
990 $output = '';
991 $selected_data = empty($existing['selected_data']) ? [] : explode(',', $existing['selected_data']);
992 foreach ($objects as $object) {
993 if (!is_array($object)) {
994 $output .= '<h4>' . $object . "</h4>\n";
995 continue;
996 }
997 if (!isset($object['id'])) {
998 continue;
999 }
1000 $selected = '';
1001 if (in_array($object['id'], $selected_data, true) || (!empty($object['alt_id']) && in_array($object['alt_id'], $selected_data, true))
1002 || (!empty($object['alt_id2']) && in_array($object['alt_id2'], $selected_data, true))) {
1003 $selected = 'selected';
1004 $present[] = $object['id'];
1005 }
1006 $passworded = !empty($object['passworded']) ? 'passworded' : '';
1007
1008 $title = !empty($object['title']) ? esc_attr($object['title']) : '';
1009 $counts = !empty($object['counters']) ? ' (' . esc_attr(implode(', ', $object['counters'])) . ')' : '';
1010 $alt = !empty($object['alt_id']) ? "data-photonic-selection-alt-id='{$object['alt_id']}'" : '';
1011 $alt_2 = !empty($object['alt_id2']) ? "data-photonic-selection-alt-id-2='{$object['alt_id2']}'" : '';
1012
1013 $output .= "<div class='photonic-flow-selector $provider $selected $passworded'>\n";
1014 $output .= "\t<div class='photonic-flow-selector-inner' data-photonic-selection-id='{$object['id']}' $alt $alt_2>\n";
1015 $output .= "\t\t<img src='{$object['thumbnail']}' alt='$title$counts' title='$title$counts' />\n";
1016 $output .= "\t</div>\n";
1017 $output .= "</div>\n";
1018 }
1019 if ($more) {
1020 return $output;
1021 }
1022
1023 $output .= "<input type='hidden' name='existing_selection' id='existing_selection' value='" . (!empty($present) ? implode(',', $present) : '') . "'/>\n";
1024 return $output;
1025 }
1026
1027 /**
1028 * A wrapper function that invokes the individual <code>process_response</code> calls by provider.
1029 * - If the call is successful the results are passed as a thumbnail grid in an array with a "success" key
1030 * - If the call is unsuccessful an error message is passed in an array with an "error" key
1031 *
1032 * @param $response
1033 * @param $provider
1034 * @param null $display_type
1035 * @param array $form_parameters
1036 * @param array $existing
1037 * @param null $url
1038 * @param bool $more
1039 * @return array
1040 */
1041 public function process_response($response, $provider, $display_type = null, $form_parameters = [], $existing = [], $url = null, $more = false): array {
1042 if (!is_wp_error($response)) {
1043 if (isset($response['response']['code'])) {
1044 if (200 === $response['response']['code']) {
1045 $pagination = [];
1046 $source = $this->flow_fields->get_source($provider);
1047 $objects = $source->process_response($response, $display_type, $url, $pagination);
1048
1049 if (empty($objects)) {
1050 return ['error' => $this->error_no_data_returned];
1051 }
1052 elseif (!empty($objects['error']) || !empty($objects['success'])) {
1053 // Happens for "Find user" kind of calls
1054 return $objects;
1055 }
1056
1057 $present = [];
1058 $output = $this->get_thumbnail_display($objects, $provider, $existing, $present, $more);
1059
1060 if (!empty($pagination['url'])) {
1061 $user = get_current_user_id();
1062 if (0 === $user) {
1063 $user = wp_rand(1);
1064 }
1065
1066 $data_display_type = empty($display_type) ? '' : $display_type;
1067 $output .= "<div class='photonic-more-wrapper'>\n" .
1068 "\t<a href='#' class='photonic-flow-more' data-photonic-more-link='{$pagination['url']}' data-photonic-display-type='$data_display_type' data-photonic-provider='$provider' data-photonic-nonce='" . wp_create_nonce('photonic-wizard-more-' . $user) . "'>" . esc_html__('Load More', 'photonic') . "</a>\n" .
1069 "</div>";
1070 }
1071
1072 if (!$more) {
1073 foreach ($form_parameters as $id => $value) {
1074 $output .= "<input type='hidden' name='$id' value='" . esc_attr($value) . "' />\n";
1075 }
1076 }
1077 return ['success' => $output];
1078 }
1079 else {
1080 Photonic::log($response['response']);
1081 return ['error' => sprintf(esc_html__('No data returned. Error code %s', 'photonic'), $response['response']['code'])];
1082 }
1083 }
1084 else {
1085 Photonic::log($response);
1086 return ['error' => esc_html__('No data returned. Empty response, or empty error code.', 'photonic')];
1087 }
1088 }
1089 else {
1090 return ['error' => $response->get_error_message()];
1091 }
1092 }
1093
1094 public static function base_apis(): array {
1095 return [
1096 'flickr' => 'api.flickr.com',
1097 // 'google' => 'photoslibrary.googleapis.com',
1098 'smugmug' => 'api.smugmug.com',
1099 // 'instagram' => 'graph.instagram.com',
1100 'zenfolio' => 'api.zenfolio.com',
1101 ];
1102 }
1103 }
1104