PluginProbe
Photonic Gallery & Lightbox for Flickr, SmugMug & Others / 2.41
Photonic Gallery & Lightbox for Flickr, SmugMug & Others v2.41
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 2.41, at Admin/Wizard/Wizard.php

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