# photonic/3.37/Core/AJAX.php

Photonic Gallery &amp; Lightbox for Flickr, SmugMug &amp; Others, version 3.37. 328 lines.

- Page: https://pluginprobe.com/plugins/photonic/3.37/code/Core/AJAX.php
- Raw: https://pluginprobe.com/plugins/photonic/3.37/raw/Core/AJAX.php
- Modified: 2026-07-23T21:21:48+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/photonic/3.37/code/Core/AJAX.php#L10-L20`.

```php
<?php
namespace Photonic_Plugin\Core;

use Photonic_Plugin\Admin\Authentication;
use Photonic_Plugin\Admin\Helper;

class AJAX {
	private Photonic $core;
	private static ?AJAX $instance = null;

	/**
	 * AJAX constructor.
	 *
	 * @param Photonic $photonic
	 */
	private function __construct(Photonic $photonic) {
		$this->core = $photonic;

		add_action('wp_ajax_photonic_display_level_2_contents', [&$this, 'display_level_2_contents']);
		add_action('wp_ajax_nopriv_photonic_display_level_2_contents', [&$this, 'display_level_2_contents']);

		add_action('wp_ajax_photonic_display_level_3_contents', [&$this, 'display_level_3_contents']);
		add_action('wp_ajax_nopriv_photonic_display_level_3_contents', [&$this, 'display_level_3_contents']);

		add_action('wp_ajax_photonic_load_more', [&$this, 'load_more']);
		add_action('wp_ajax_nopriv_photonic_load_more', [&$this, 'load_more']);

		add_action('wp_ajax_photonic_lazy_load', [&$this, 'lazy_load']);
		add_action('wp_ajax_nopriv_photonic_lazy_load', [&$this, 'lazy_load']);

		add_action('wp_ajax_photonic_helper_shortcode_more', [&$this, 'helper_shortcode_more']);
		add_action('wp_ajax_nopriv_photonic_helper_shortcode_more', [&$this, 'helper_shortcode_more']);

		add_action('wp_ajax_photonic_invoke_helper', [&$this, 'invoke_helper']);
		add_action('wp_ajax_photonic_obtain_token', [&$this, 'obtain_token']);
		add_action('wp_ajax_photonic_save_token', [&$this, 'save_token_in_options']);
		add_action('wp_ajax_photonic_delete_token', [&$this, 'delete_token_from_options']);

		add_action('wp_ajax_photonic_dismiss_warning', [&$this, 'dismiss_warning']);
	}

	/**
	 * @param Photonic $photonic
	 * @return AJAX
	 */
	public static function get_instance(Photonic $photonic): AJAX {
		if (null === self::$instance) {
			self::$instance = new AJAX($photonic);
		}
		return self::$instance;
	}


	/**
	 * 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.
	 * This is the hook for an AJAX-invoked call
	 *
	 * @return void
	 */
	public function display_level_2_contents() {
		// 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.
		$panel = sanitize_text_field(wp_unslash($_POST['panel_id'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification
		$components = explode('-', $panel);

		if (count($components) <= 5) {
			die();
		}
		$panel = implode('-', array_slice($components, 4, 10, true));
		$query = sanitize_text_field($_POST['query'] ?? ''); // phpcs:ignore WordPress.Security.NonceVerification
		$query = wp_parse_args($query);

		$popup = sanitize_text_field(wp_unslash($_POST['popup'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification
		if (empty($popup)) {
			$location = 'lightbox';
		}
		elseif ('page' === $popup) {
			$location = 'template';
		}
		else {
			$location = 'modal';
		}

		$args = [
			'display'    => $location,
			'layout'     => 'square',
			'panel'      => $panel,
			'password'   => !empty($_POST['password']) ? sanitize_text_field($_POST['password']) : '', // phpcs:ignore WordPress.Security.NonceVerification
			'count'      => sanitize_text_field($_POST['photo_count']), // phpcs:ignore WordPress.Security.NonceVerification
			'photo_more' => sanitize_text_field($_POST['photo_more']), // phpcs:ignore WordPress.Security.NonceVerification
			'main_size'  => $query['main_size'],
			'type'       => $components[1]
		];

		$provider = $components[1];
		$type = $components[2];
		if (in_array($provider, ['smug', 'smugmug', 'zenfolio', 'google', 'flickr'], true)) {
			if ('smug' === $provider) {
				$args['view'] = 'album';
				$args['album_key'] = $components[4];
			}
			elseif ('zenfolio' === $provider) {
				$args['view'] = 'photosets';
				$args['object_id'] = $components[4];
				$args['thumb_size'] = sanitize_text_field($_POST['overlay_size']); // phpcs:ignore WordPress.Security.NonceVerification
				$args['video_size'] = sanitize_text_field($_POST['overlay_video_size']); // phpcs:ignore WordPress.Security.NonceVerification
				if (isset($_POST['realm_id'])) { // phpcs:ignore WordPress.Security.NonceVerification
					$args['realm_id'] = sanitize_text_field($_POST['realm_id']); // phpcs:ignore WordPress.Security.NonceVerification
				}
			}
			elseif ('google' === $provider) {
				$args['view'] = 'photos';
				$args['album_id'] = implode('-', array_slice($components, 4, (count($components) - 1) - 4));
				$args['thumb_size'] = sanitize_text_field($_POST['overlay_size']); // phpcs:ignore WordPress.Security.NonceVerification
				$args['video_size'] = sanitize_text_field($_POST['overlay_video_size']); // phpcs:ignore WordPress.Security.NonceVerification
				$args['crop_thumb'] = sanitize_text_field($_POST['overlay_crop']); // phpcs:ignore WordPress.Security.NonceVerification
			}
			elseif ('flickr' === $provider) {
				if ('gallery' === $type) {
					$args['gallery_id'] = $components[4] . '-' . $components[5];
					$args['gallery_id_computed'] = true;
				}
				elseif ('set' === $type) {
					$args['photoset_id'] = $components[4];
				}
				$args['thumb_size'] = sanitize_text_field($_POST['overlay_size']); // phpcs:ignore WordPress.Security.NonceVerification
				$args['video_size'] = sanitize_text_field($_POST['overlay_video_size']); // phpcs:ignore WordPress.Security.NonceVerification
			}

			$gallery = new Gallery($args);
			echo wp_kses($gallery->get_contents(), Photonic::$safe_tags);
		}
		die();
	}

	/**
	 * 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.
	 * This is the hook for an AJAX-invoked call.
	 */
	public function display_level_3_contents() {
		// 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.
		$node = sanitize_text_field(wp_unslash($_POST['node'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification
		$components = explode('-', $node);

		if (count($components) <= 3) {
			die();
		}

		$args = [
			'display' => 'local',
			'headers' => '',
			'layout' => sanitize_text_field($_POST['layout'] ?? null), // phpcs:ignore WordPress.Security.NonceVerification
			'stream' => sanitize_text_field(wp_unslash($_POST['stream'] ?? '')) // phpcs:ignore WordPress.Security.NonceVerification
		];

		$provider = $components[0];
		if ('flickr' === $provider) {
			$args['collection_id'] = implode('-', array_slice($components, 2, 2, true));
			$args['user_id'] = $components[4];
			$args['type'] = 'flickr';
			$args['strip_top_level'] = 'remove';
			$gallery = new Gallery($args);
			echo wp_kses($gallery->get_contents(), Photonic::$safe_tags);
		}
		die();
	}

	public function load_more() {
		// 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.
		$provider = sanitize_text_field(wp_unslash($_POST['provider'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification
		$query = sanitize_text_field($_POST['query'] ?? ''); // phpcs:ignore WordPress.Security.NonceVerification
		$attr = wp_parse_args($query);

		$attr['type'] = $provider;
		if ('flickr' === $provider) {
			$attr['page'] = isset($attr['page']) ? $attr['page'] + 1 : 0;
		}
		elseif ('smug' === $provider) {
			$attr['start'] = $attr['start'] + $attr['count'];
		}
		elseif ('zenfolio' === $provider) {
			$attr['offset'] = $attr['offset'] + $attr['limit'];
		}
		elseif ('wp' === $provider) {
			$attr['page'] = $attr['page'] + 1;
		}
		elseif ('google' !== $provider && 'instagram' !== $provider) {
			unset($attr['type']);
		}

		if (!empty($attr['type'])) {
			$gallery = new Gallery($attr);
			echo wp_kses($gallery->get_contents(), Photonic::$safe_tags);
		}
		die();
	}

	public function lazy_load() {
		// $_POST['shortcode'] only contains the parameters of a URL, to be passed to photonic after being broken down. Sanitization functions are killing
		// characters such as "@" (used in Flickr user ids) or its escaped form. So we use esc_url_raw.
		// However, esc_url_raw needs a domain, so we prepend a random one, sanitize it, then pull out only the 'query' part from it.
		$shortcode = esc_url_raw('https://randomurl.com?' . ($_POST['shortcode'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification
		$shortcode_parse = wp_parse_url($shortcode);
		$attr = [];
		parse_str($shortcode_parse['query'], $attr);

		$images = $this->core->get_gallery_images($attr);
		// echo $images;
		echo wp_kses($images, Photonic::$safe_tags);
		die();
	}

	public function helper_shortcode_more() {
		if (!empty($_POST['provider'])) { // phpcs:ignore WordPress.Security.NonceVerification
			$provider = sanitize_text_field(wp_unslash($_POST['provider'])); // phpcs:ignore WordPress.Security.NonceVerification

			$tokenized_pagination_platforms = ['google'];

			if (in_array($provider, $tokenized_pagination_platforms, true)) {
				$attr = ['type' => $provider];
				if ('google' === $provider) {
					$attr['next_page_token'] = sanitize_text_field(wp_unslash($_POST['nextPageToken'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification
					$attr['album_type'] = sanitize_text_field(wp_unslash($_POST['access'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification
					$gallery = new Gallery($attr);
					echo wp_kses($gallery->get_helper_contents(), Photonic::$safe_tags);
				}
			}
		}
		die();
	}

	public function invoke_helper() {
		require_once PHOTONIC_PATH . "/Admin/Helper.php";
		$helper = new Helper();
		$helper->invoke_helper();
	}

	public function obtain_token() {
		require_once PHOTONIC_PATH . "/Admin/Authentication.php";
		$auth = Authentication::get_instance();
		$auth->obtain_token();
		die();
	}

	/**
	 * Invoked via AJAX in the "Authentication" page, when the user clicks on "Save Token"
	 */
	public function save_token_in_options() {
		// 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.
		if (isset($_POST['provider']) && isset($_POST['token']) && check_ajax_referer($_POST['provider'] . '-save-token-' . $_POST['token']) && current_user_can('edit_theme_options')) {
			$provider = strtolower(sanitize_text_field(wp_unslash($_POST['provider'])));
			$token = sanitize_text_field(wp_unslash($_POST['token']));
			$secret = sanitize_text_field(wp_unslash($_POST['secret'] ?? ''));
			if (!empty($_POST['expires_in'])) {
				$expires_in = sanitize_text_field(wp_unslash($_POST['expires_in']));
			}

			if (in_array($provider, ['flickr', 'smug', 'zenfolio', 'google', 'instagram', 'deviantart'], true)) {
				$options = get_option('photonic_options');
				if (empty($options)) {
					$options = [];
				}
				$option_set = false;
				if (in_array($provider, ['flickr', 'smug', 'zenfolio'], true)) {
					$options[$provider . '_access_token'] = $token;
					$options[$provider . '_token_secret'] = $secret;
					$option_set = true;
				}
				elseif ('google' === $provider || 'deviantart' === $provider) {
					$options[str_replace('-', '_', $provider) . '_refresh_token'] = $token;
					$option_set = true;
				}
				elseif ('instagram' === $provider) {
					$client_id = sanitize_text_field(wp_unslash($_POST['client_id'] ?? ''));
					$user = sanitize_text_field(wp_unslash($_POST['user'] ?? ''));

					$options[$provider . '_access_token'] = $token;

					$auth_token = [];
					$auth_token['oauth_token'] = $token;
					$auth_token['oauth_token_created'] = time();
					if (!empty($expires_in)) {
						$auth_token['oauth_token_expires'] = $expires_in;
					}
					$auth_token['client_id'] = $client_id;
					$auth_token['user'] = $user;

					self::save_provider_authentication($provider, $auth_token);

					$option_set = true;
				}

				if ($option_set) {
					update_option('photonic_options', $options);
					echo esc_url(admin_url('admin.php?page=photonic-options-manager')) . '&tab=' . esc_attr($this->core->provider_map[$provider]) . '.php';
				}
			}
		}
		die();
	}

	/**
	 * @param string $provider
	 * @param array  $auth_token
	 */
	private static function save_provider_authentication(string $provider, array $auth_token) {
		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
			$photonic_authentication = get_option('photonic_authentication');
			if (empty($photonic_authentication)) {
				$photonic_authentication = [];
			}
			$photonic_authentication[$provider] = $auth_token;
			update_option('photonic_authentication', $photonic_authentication);
			set_transient('photonic_' . $provider . '_token', $auth_token);
		}
	}

	public function dismiss_warning(): void {
		$user_id = get_current_user_id();
		$response = [];
		if (!empty($_POST['dismissible']) && check_ajax_referer('dismiss-warning-' . $user_id)) {
			add_user_meta($user_id, "photonic_" . sanitize_text_field($_POST['dismissible']), 'true', true);
			$response[$_POST['dismissible']] = 'true';
		}
		echo wp_json_encode($response);
		die();
	}
}

```
