PluginProbe
Photonic Gallery & Lightbox for Flickr, SmugMug & Others / 3.33
Photonic Gallery & Lightbox for Flickr, SmugMug & Others v3.33
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.33, at Core/AJAX.php

325 lines 13.3 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 if (in_array($provider, ['google'], true)) {
216 $attr = ['type' => $provider];
217 if ('google' === $provider) {
218 $attr['nextPageToken'] = sanitize_text_field(wp_unslash($_POST['nextPageToken'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification
219 $attr['album_type'] = sanitize_text_field(wp_unslash($_POST['access'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification
220 $gallery = new Gallery($attr);
221 echo wp_kses($gallery->get_helper_contents(), Photonic::$safe_tags);
222 }
223 }
224 }
225 die();
226 }
227
228 public function invoke_helper() {
229 require_once PHOTONIC_PATH . "/Admin/Helper.php";
230 $helper = new Helper();
231 $helper->invoke_helper();
232 }
233
234 public function obtain_token() {
235 require_once PHOTONIC_PATH . "/Admin/Authentication.php";
236 $auth = Authentication::get_instance();
237 $auth->obtain_token();
238 die();
239 }
240
241 /**
242 * Invoked via AJAX in the "Authentication" page, when the user clicks on "Save Token"
243 */
244 public function save_token_in_options() {
245 // 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.
246 if (isset($_POST['provider']) && isset($_POST['token']) && check_ajax_referer($_POST['provider'] . '-save-token-' . $_POST['token']) && current_user_can('edit_theme_options')) {
247 $provider = strtolower(sanitize_text_field(wp_unslash($_POST['provider'])));
248 $token = sanitize_text_field(wp_unslash($_POST['token']));
249 $secret = sanitize_text_field(wp_unslash($_POST['secret'] ?? ''));
250 if (!empty($_POST['expires_in'])) {
251 $expires_in = sanitize_text_field(wp_unslash($_POST['expires_in']));
252 }
253
254 if (in_array($provider, ['flickr', 'smug', 'zenfolio', 'google', 'instagram', 'deviantart'], true)) {
255 $options = get_option('photonic_options');
256 if (empty($options)) {
257 $options = [];
258 }
259 $option_set = false;
260 if (in_array($provider, ['flickr', 'smug', 'zenfolio'], true)) {
261 $options[$provider . '_access_token'] = $token;
262 $options[$provider . '_token_secret'] = $secret;
263 $option_set = true;
264 }
265 elseif ('google' === $provider || 'deviantart' === $provider) {
266 $options[str_replace('-', '_', $provider) . '_refresh_token'] = $token;
267 $option_set = true;
268 }
269 elseif ('instagram' === $provider) {
270 $client_id = sanitize_text_field(wp_unslash($_POST['client_id'] ?? ''));
271 $user = sanitize_text_field(wp_unslash($_POST['user'] ?? ''));
272
273 $options[$provider . '_access_token'] = $token;
274
275 $auth_token = [];
276 $auth_token['oauth_token'] = $token;
277 $auth_token['oauth_token_created'] = time();
278 if (!empty($expires_in)) {
279 $auth_token['oauth_token_expires'] = $expires_in;
280 }
281 $auth_token['client_id'] = $client_id;
282 $auth_token['user'] = $user;
283
284 self::save_provider_authentication($provider, $auth_token);
285
286 $option_set = true;
287 }
288
289 if ($option_set) {
290 update_option('photonic_options', $options);
291 echo esc_url(admin_url('admin.php?page=photonic-options-manager')) . '&tab=' . esc_attr($this->core->provider_map[$provider]) . '.php';
292 }
293 }
294 }
295 die();
296 }
297
298 /**
299 * @param string $provider
300 * @param array $auth_token
301 */
302 private static function save_provider_authentication($provider, $auth_token) {
303 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
304 $photonic_authentication = get_option('photonic_authentication');
305 if (empty($photonic_authentication)) {
306 $photonic_authentication = [];
307 }
308 $photonic_authentication[$provider] = $auth_token;
309 update_option('photonic_authentication', $photonic_authentication);
310 set_transient('photonic_' . $provider . '_token', $auth_token);
311 }
312 }
313
314 public function dismiss_warning(): void {
315 $user_id = get_current_user_id();
316 $response = [];
317 if (!empty($_POST['dismissible']) && check_ajax_referer('dismiss-warning-' . $user_id)) {
318 add_user_meta($user_id, "photonic_" . sanitize_text_field($_POST['dismissible']), 'true', true);
319 $response[$_POST['dismissible']] = 'true';
320 }
321 echo wp_json_encode($response);
322 die();
323 }
324 }
325