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 / Core / AJAX.php

AJAX.php in Photonic Gallery & Lightbox for Flickr, SmugMug & Others 3.36, at Core/AJAX.php

328 lines 13.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Photonic_Plugin\Core;
3
4 use Photonic_Plugin\Admin\Authentication;
5 use Photonic_Plugin\Admin\Helper;
6
7 class AJAX {
8 private Photonic $core;
9 private static ?AJAX $instance = null;
10
11 /**
12 * AJAX constructor.
13 *
14 * @param Photonic $photonic
15 */
16 private function __construct(Photonic $photonic) {
17 $this->core = $photonic;
18
19 add_action('wp_ajax_photonic_display_level_2_contents', [&$this, 'display_level_2_contents']);
20 add_action('wp_ajax_nopriv_photonic_display_level_2_contents', [&$this, 'display_level_2_contents']);
21
22 add_action('wp_ajax_photonic_display_level_3_contents', [&$this, 'display_level_3_contents']);
23 add_action('wp_ajax_nopriv_photonic_display_level_3_contents', [&$this, 'display_level_3_contents']);
24
25 add_action('wp_ajax_photonic_load_more', [&$this, 'load_more']);
26 add_action('wp_ajax_nopriv_photonic_load_more', [&$this, 'load_more']);
27
28 add_action('wp_ajax_photonic_lazy_load', [&$this, 'lazy_load']);
29 add_action('wp_ajax_nopriv_photonic_lazy_load', [&$this, 'lazy_load']);
30
31 add_action('wp_ajax_photonic_helper_shortcode_more', [&$this, 'helper_shortcode_more']);
32 add_action('wp_ajax_nopriv_photonic_helper_shortcode_more', [&$this, 'helper_shortcode_more']);
33
34 add_action('wp_ajax_photonic_invoke_helper', [&$this, 'invoke_helper']);
35 add_action('wp_ajax_photonic_obtain_token', [&$this, 'obtain_token']);
36 add_action('wp_ajax_photonic_save_token', [&$this, 'save_token_in_options']);
37 add_action('wp_ajax_photonic_delete_token', [&$this, 'delete_token_from_options']);
38
39 add_action('wp_ajax_photonic_dismiss_warning', [&$this, 'dismiss_warning']);
40 }
41
42 /**
43 * @param Photonic $photonic
44 * @return AJAX
45 */
46 public static function get_instance(Photonic $photonic): AJAX {
47 if (null === self::$instance) {
48 self::$instance = new AJAX($photonic);
49 }
50 return self::$instance;
51 }
52
53
54 /**
55 * Clicking on a level 2 object (i.e. an Album / Set / Gallery) triggers this. This will fetch the contents of the level 2 object and generate the markup for it.
56 * This is the hook for an AJAX-invoked call
57 *
58 * @return void
59 */
60 public function display_level_2_contents() {
61 // Cannot use a nonce here. Users often cache the gallery markup, which would cache the nonce. This would make it impossible to run this call after a certain amount of time.
62 $panel = sanitize_text_field(wp_unslash($_POST['panel_id'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification
63 $components = explode('-', $panel);
64
65 if (count($components) <= 5) {
66 die();
67 }
68 $panel = implode('-', array_slice($components, 4, 10, true));
69 $query = sanitize_text_field($_POST['query'] ?? ''); // phpcs:ignore WordPress.Security.NonceVerification
70 $query = wp_parse_args($query);
71
72 $popup = sanitize_text_field(wp_unslash($_POST['popup'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification
73 if (empty($popup)) {
74 $location = 'lightbox';
75 }
76 elseif ('page' === $popup) {
77 $location = 'template';
78 }
79 else {
80 $location = 'modal';
81 }
82
83 $args = [
84 'display' => $location,
85 'layout' => 'square',
86 'panel' => $panel,
87 'password' => !empty($_POST['password']) ? sanitize_text_field($_POST['password']) : '', // phpcs:ignore WordPress.Security.NonceVerification
88 'count' => sanitize_text_field($_POST['photo_count']), // phpcs:ignore WordPress.Security.NonceVerification
89 'photo_more' => sanitize_text_field($_POST['photo_more']), // phpcs:ignore WordPress.Security.NonceVerification
90 'main_size' => $query['main_size'],
91 'type' => $components[1]
92 ];
93
94 $provider = $components[1];
95 $type = $components[2];
96 if (in_array($provider, ['smug', 'smugmug', 'zenfolio', 'google', 'flickr'], true)) {
97 if ('smug' === $provider) {
98 $args['view'] = 'album';
99 $args['album_key'] = $components[4];
100 }
101 elseif ('zenfolio' === $provider) {
102 $args['view'] = 'photosets';
103 $args['object_id'] = $components[4];
104 $args['thumb_size'] = sanitize_text_field($_POST['overlay_size']); // phpcs:ignore WordPress.Security.NonceVerification
105 $args['video_size'] = sanitize_text_field($_POST['overlay_video_size']); // phpcs:ignore WordPress.Security.NonceVerification
106 if (isset($_POST['realm_id'])) { // phpcs:ignore WordPress.Security.NonceVerification
107 $args['realm_id'] = sanitize_text_field($_POST['realm_id']); // phpcs:ignore WordPress.Security.NonceVerification
108 }
109 }
110 elseif ('google' === $provider) {
111 $args['view'] = 'photos';
112 $args['album_id'] = implode('-', array_slice($components, 4, (count($components) - 1) - 4));
113 $args['thumb_size'] = sanitize_text_field($_POST['overlay_size']); // phpcs:ignore WordPress.Security.NonceVerification
114 $args['video_size'] = sanitize_text_field($_POST['overlay_video_size']); // phpcs:ignore WordPress.Security.NonceVerification
115 $args['crop_thumb'] = sanitize_text_field($_POST['overlay_crop']); // phpcs:ignore WordPress.Security.NonceVerification
116 }
117 elseif ('flickr' === $provider) {
118 if ('gallery' === $type) {
119 $args['gallery_id'] = $components[4] . '-' . $components[5];
120 $args['gallery_id_computed'] = true;
121 }
122 elseif ('set' === $type) {
123 $args['photoset_id'] = $components[4];
124 }
125 $args['thumb_size'] = sanitize_text_field($_POST['overlay_size']); // phpcs:ignore WordPress.Security.NonceVerification
126 $args['video_size'] = sanitize_text_field($_POST['overlay_video_size']); // phpcs:ignore WordPress.Security.NonceVerification
127 }
128
129 $gallery = new Gallery($args);
130 echo wp_kses($gallery->get_contents(), Photonic::$safe_tags);
131 }
132 die();
133 }
134
135 /**
136 * Clicking on the expander for a level 3 object (e.g. a Flickr Collection etc.) triggers this. This will fetch the nested level 2 objects and generate the corresponding markup.
137 * This is the hook for an AJAX-invoked call.
138 */
139 public function display_level_3_contents() {
140 // Cannot use a nonce here. Users often cache the gallery markup, which would cache the nonce. This would make it impossible to run this call after a certain amount of time.
141 $node = sanitize_text_field(wp_unslash($_POST['node'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification
142 $components = explode('-', $node);
143
144 if (count($components) <= 3) {
145 die();
146 }
147
148 $args = [
149 'display' => 'local',
150 'headers' => '',
151 'layout' => sanitize_text_field($_POST['layout'] ?? null), // phpcs:ignore WordPress.Security.NonceVerification
152 'stream' => sanitize_text_field(wp_unslash($_POST['stream'] ?? '')) // phpcs:ignore WordPress.Security.NonceVerification
153 ];
154
155 $provider = $components[0];
156 if ('flickr' === $provider) {
157 $args['collection_id'] = implode('-', array_slice($components, 2, 2, true));
158 $args['user_id'] = $components[4];
159 $args['type'] = 'flickr';
160 $args['strip_top_level'] = 'remove';
161 $gallery = new Gallery($args);
162 echo wp_kses($gallery->get_contents(), Photonic::$safe_tags);
163 }
164 die();
165 }
166
167 public function load_more() {
168 // Cannot use a nonce here. Users often cache the gallery markup, which would cache the nonce. This would make it impossible to run this call after a certain amount of time.
169 $provider = sanitize_text_field(wp_unslash($_POST['provider'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification
170 $query = sanitize_text_field($_POST['query'] ?? ''); // phpcs:ignore WordPress.Security.NonceVerification
171 $attr = wp_parse_args($query);
172
173 $attr['type'] = $provider;
174 if ('flickr' === $provider) {
175 $attr['page'] = isset($attr['page']) ? $attr['page'] + 1 : 0;
176 }
177 elseif ('smug' === $provider) {
178 $attr['start'] = $attr['start'] + $attr['count'];
179 }
180 elseif ('zenfolio' === $provider) {
181 $attr['offset'] = $attr['offset'] + $attr['limit'];
182 }
183 elseif ('wp' === $provider) {
184 $attr['page'] = $attr['page'] + 1;
185 }
186 elseif ('google' !== $provider && 'instagram' !== $provider) {
187 unset($attr['type']);
188 }
189
190 if (!empty($attr['type'])) {
191 $gallery = new Gallery($attr);
192 echo wp_kses($gallery->get_contents(), Photonic::$safe_tags);
193 }
194 die();
195 }
196
197 public function lazy_load() {
198 // $_POST['shortcode'] only contains the parameters of a URL, to be passed to photonic after being broken down. Sanitization functions are killing
199 // characters such as "@" (used in Flickr user ids) or its escaped form. So we use esc_url_raw.
200 // However, esc_url_raw needs a domain, so we prepend a random one, sanitize it, then pull out only the 'query' part from it.
201 $shortcode = esc_url_raw('https://randomurl.com?' . ($_POST['shortcode'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification
202 $shortcode_parse = wp_parse_url($shortcode);
203 $attr = [];
204 parse_str($shortcode_parse['query'], $attr);
205
206 $images = $this->core->get_gallery_images($attr);
207 // echo $images;
208 echo wp_kses($images, Photonic::$safe_tags);
209 die();
210 }
211
212 public function helper_shortcode_more() {
213 if (!empty($_POST['provider'])) { // phpcs:ignore WordPress.Security.NonceVerification
214 $provider = sanitize_text_field(wp_unslash($_POST['provider'])); // phpcs:ignore WordPress.Security.NonceVerification
215
216 $tokenized_pagination_platforms = ['google'];
217
218 if (in_array($provider, $tokenized_pagination_platforms, true)) {
219 $attr = ['type' => $provider];
220 if ('google' === $provider) {
221 $attr['next_page_token'] = sanitize_text_field(wp_unslash($_POST['nextPageToken'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification
222 $attr['album_type'] = sanitize_text_field(wp_unslash($_POST['access'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification
223 $gallery = new Gallery($attr);
224 echo wp_kses($gallery->get_helper_contents(), Photonic::$safe_tags);
225 }
226 }
227 }
228 die();
229 }
230
231 public function invoke_helper() {
232 require_once PHOTONIC_PATH . "/Admin/Helper.php";
233 $helper = new Helper();
234 $helper->invoke_helper();
235 }
236
237 public function obtain_token() {
238 require_once PHOTONIC_PATH . "/Admin/Authentication.php";
239 $auth = Authentication::get_instance();
240 $auth->obtain_token();
241 die();
242 }
243
244 /**
245 * Invoked via AJAX in the "Authentication" page, when the user clicks on "Save Token"
246 */
247 public function save_token_in_options() {
248 // The $_POST[...] checks in the next line must NEVER be sanitized or un-escaped. The intent of this code is to verify that the source is who they claim to be. Sanitizing here will defeat the purpose of the security check.
249 if (isset($_POST['provider']) && isset($_POST['token']) && check_ajax_referer($_POST['provider'] . '-save-token-' . $_POST['token']) && current_user_can('edit_theme_options')) {
250 $provider = strtolower(sanitize_text_field(wp_unslash($_POST['provider'])));
251 $token = sanitize_text_field(wp_unslash($_POST['token']));
252 $secret = sanitize_text_field(wp_unslash($_POST['secret'] ?? ''));
253 if (!empty($_POST['expires_in'])) {
254 $expires_in = sanitize_text_field(wp_unslash($_POST['expires_in']));
255 }
256
257 if (in_array($provider, ['flickr', 'smug', 'zenfolio', 'google', 'instagram', 'deviantart'], true)) {
258 $options = get_option('photonic_options');
259 if (empty($options)) {
260 $options = [];
261 }
262 $option_set = false;
263 if (in_array($provider, ['flickr', 'smug', 'zenfolio'], true)) {
264 $options[$provider . '_access_token'] = $token;
265 $options[$provider . '_token_secret'] = $secret;
266 $option_set = true;
267 }
268 elseif ('google' === $provider || 'deviantart' === $provider) {
269 $options[str_replace('-', '_', $provider) . '_refresh_token'] = $token;
270 $option_set = true;
271 }
272 elseif ('instagram' === $provider) {
273 $client_id = sanitize_text_field(wp_unslash($_POST['client_id'] ?? ''));
274 $user = sanitize_text_field(wp_unslash($_POST['user'] ?? ''));
275
276 $options[$provider . '_access_token'] = $token;
277
278 $auth_token = [];
279 $auth_token['oauth_token'] = $token;
280 $auth_token['oauth_token_created'] = time();
281 if (!empty($expires_in)) {
282 $auth_token['oauth_token_expires'] = $expires_in;
283 }
284 $auth_token['client_id'] = $client_id;
285 $auth_token['user'] = $user;
286
287 self::save_provider_authentication($provider, $auth_token);
288
289 $option_set = true;
290 }
291
292 if ($option_set) {
293 update_option('photonic_options', $options);
294 echo esc_url(admin_url('admin.php?page=photonic-options-manager')) . '&tab=' . esc_attr($this->core->provider_map[$provider]) . '.php';
295 }
296 }
297 }
298 die();
299 }
300
301 /**
302 * @param string $provider
303 * @param array $auth_token
304 */
305 private static function save_provider_authentication(string $provider, array $auth_token) {
306 if (current_user_can('edit_theme_options')) { // Method is private, and is only called from save_token_in_options, where there is a nonce check
307 $photonic_authentication = get_option('photonic_authentication');
308 if (empty($photonic_authentication)) {
309 $photonic_authentication = [];
310 }
311 $photonic_authentication[$provider] = $auth_token;
312 update_option('photonic_authentication', $photonic_authentication);
313 set_transient('photonic_' . $provider . '_token', $auth_token);
314 }
315 }
316
317 public function dismiss_warning(): void {
318 $user_id = get_current_user_id();
319 $response = [];
320 if (!empty($_POST['dismissible']) && check_ajax_referer('dismiss-warning-' . $user_id)) {
321 add_user_meta($user_id, "photonic_" . sanitize_text_field($_POST['dismissible']), 'true', true);
322 $response[$_POST['dismissible']] = 'true';
323 }
324 echo wp_json_encode($response);
325 die();
326 }
327 }
328