HEX
Server: Apache/2.4.59 (Debian)
System: Linux emory.shared.1984.is 6.1.0-27-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.115-1 (2024-11-01) x86_64
User: u11574 (1020)
PHP: 7.4.33
Disabled: exec,system,passthru,shell_exec,popen,show_source,shell,symlink,proc_open,pcntl_exec,pcntl_fork,pcntl_wait,pcntl_alarm,pcntl_signal,pcntl_signal_dispatch,pcntl_getpriority,proc_get_status,expect_popen,dl,putenv,mail
Upload Files
File: /var/www/virtual/mariaellingsen.com/htdocs/wp-content/plugins/soliloquy/includes/admin/ajax.php
<?php
/**
 * Handles all admin ajax interactions for the Soliloquy plugin.
 *
 * @since 1.0.0
 *
 * @package Soliloquy
 * @author SoliloquyWP Team <support@soliloquywp.com>
 */

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Soliloquy_Ajax {

	public static $instance;
	public $base;

	/**
	 * Primary class constructor.
	 *
	 * @since 2.5
	 */
	public function __construct() {

		$this->base = Soliloquy::get_instance();

		add_action( 'wp_ajax_soliloquy_is_hosted_video', array( $this, 'is_hosted_video' ) );
		add_action( 'wp_ajax_soliloquy_upgrade_sliders', array( $this, 'upgrade_sliders' ) );
		add_action( 'wp_ajax_soliloquy_change_type', array( $this, 'change_type' ) );
		add_action( 'wp_ajax_soliloquy_load_image', array( $this, 'load_image' ) );
		add_action( 'wp_ajax_soliloquy_insert_slides', array( $this, 'insert_slides' ) );
		add_action( 'wp_ajax_soliloquy_sort_images', array( $this, 'sort_images' ) );
		add_action( 'wp_ajax_soliloquy_remove_slides', array( $this, 'remove_slides' ) );
		add_action( 'wp_ajax_soliloquy_remove_slide', array( $this, 'remove_slide' ) );
		add_action( 'wp_ajax_soliloquy_save_meta', array( $this, 'save_meta' ) );
		add_action( 'wp_ajax_soliloquy_bulk_save_meta', array( $this, 'bulk_save_meta' ) );
		add_action( 'wp_ajax_soliloquy_refresh', array( $this, 'refresh' ) );
		add_action( 'wp_ajax_soliloquy_load_slider_data', array( $this, 'load_slider_data' ) );
		add_action( 'wp_ajax_soliloquy_install_addon', array( $this, 'install_addon' ) );
		add_action( 'wp_ajax_soliloquy_activate_addon', array( $this, 'activate_addon' ) );
		add_action( 'wp_ajax_soliloquy_deactivate_addon', array( $this, 'deactivate_addon' ) );
		add_action( 'wp_ajax_soliloquy_init_sliders', array( $this, 'init_sliders' ) );
		add_action( 'wp_ajax_nopriv_soliloquy_init_sliders', array( $this, 'init_sliders' ) );
		add_action( 'wp_ajax_soliloquy_sort_addons', array( $this, 'sort_addons' ) );
		add_action( 'wp_ajax_soliloquy_change_slide_status', array( $this, 'change_slide_status' ) );
		add_action( 'wp_ajax_soliloquy_slider_view', array( $this, 'slider_view' ) );
		add_action( 'wp_ajax_soliloquy_sort_publish', array( $this, 'sort_slides' ) );
		add_action( 'wp_ajax_soliloquy_get_attachment_links', array( $this, 'get_attachment_links' ) );
	}

	/**
	 * Returns the media link (direct image URL) for the given attachment ID
	 *
	 * @since
	 */
	public function get_attachment_links() {

		// Check nonce
		check_admin_referer( 'soliloquy-save-meta', 'nonce' );

		// Check permissions
		if ( ! current_user_can( 'edit_others_posts' ) ) {
			wp_send_json_error( [ 'message' => esc_html__( 'You are not allowed to edit sliders.', 'soliloquy' ) ] );
		}

		// Get required inputs
		$attachment_id = absint( $_POST['attachment_id'] );

		// Return the attachment's links
		wp_send_json_success(
			array(
				'media_link'      => wp_get_attachment_url( $attachment_id ),
				'attachment_page' => get_attachment_link( $attachment_id ),
			)
		);
	}
	/**
	 * Upgrades sliders from v1 to v2. This also upgrades any current v2 users to the
	 * proper post type. This is a mess and it was my fault. :-( I apologize to my customers
	 * for making this so rough. You deserve better, and I will work hard to do better by
	 * you! Thanks for hanging in there faithfully with me!
	 *
	 * @since 1.0.0
	 */
	public function upgrade_sliders() {

		// Run a security check first.
		check_admin_referer( 'soliloquy-upgrade', 'nonce' );

		if ( ! current_user_can( 'edit_others_posts' ) ) {
			wp_send_json_error( [ 'message' => esc_html__( 'You are not allowed to edit sliders.', 'soliloquy' ) ] );
		}

		// Increase the time limit to account for large slider sets and suspend cache invalidations.
		set_time_limit( Soliloquy_Common::get_instance()->get_max_execution_time() );
		wp_suspend_cache_invalidation( true );

		// Update the license key from v1 to v2 if necessary.
		$v2_license = get_option( 'soliloquy' );

		if ( ! $v2_license || empty( $v2_license['key'] ) || empty( $v2_license['type'] ) ) {
			$v1_license  = get_option( 'soliloquy_license_key' );
			$new_license = Soliloquy::default_options();

			if ( ! empty( $v1_license['license'] ) ) {
				$new_license['key'] = $v1_license['license'];
			}

			update_option( 'soliloquy', $new_license );

			// Force the new key to be validated.
			Soliloquy_License::get_instance()->validate_key( true );
		}

		// Grab all sliders from v1 and convert them to the new system.
		$sliders = get_posts(
			array(
				'post_type'      => 'soliloquy',
				'posts_per_page' => -1,
			)
		);

		// Loop through sliders and convert them.
		foreach ( (array) $sliders as $slider ) {
			// Grab meta from the v1 and v2 sliders.
			$meta    = get_post_meta( $slider->ID, '_soliloquy_settings', true );
			$v2_meta = get_post_meta( $slider->ID, '_sol_slider_data', true );

			// Move meta from v1 to v2, or if already using v2, use v2 as a starting point.
			if ( empty( $v2_meta ) ) {
				$new_meta = array(
					'id'     => $slider->ID,
					'config' => array(),
					'slider' => array(),
					'status' => 'active',
				);
			} else {
				$new_meta = $v2_meta;
			}

			// Splice meta from v1 to v2.
			if ( ! empty( $new_meta['config']['gutter'] ) ) {
				$new_meta['config']['gutter'] = 0;
			}

			if ( ! empty( $new_meta['config']['position'] ) ) {
				$new_meta['config']['position'] = 'none';
			}

			if ( ! empty( $new_meta['config']['mobile'] ) ) {
				$new_meta['config']['mobile'] = 0;
			}

			$new_meta['config']['title'] = $slider->post_title;
			$new_meta['config']['slug']  = $slider->post_name;

			if ( ! empty( $meta['width'] ) ) {
				$new_meta['config']['slider_width'] = absint( $meta['width'] );
			}

			if ( ! empty( $meta['height'] ) ) {
				$new_meta['config']['slider_height'] = absint( $meta['height'] );
			}

			if ( ! empty( $meta['default'] ) && 'cropped' !== $meta['default'] ) {
				$new_meta['config']['slider'] = 0;
			}

			if ( ! empty( $meta['custom'] ) ) {
				if ( $meta['custom'] ) {
					if ( 'full' == $meta['custom'] ) {
						$new_meta['config']['slider_size'] = 'default';
					} else {
						$new_meta['config']['slider_size'] = $meta['custom'];

						global $_wp_additional_image_sizes;
						if ( isset( $_wp_additional_image_sizes[ $meta['custom'] ] ) ) {
							$width  = absint( $_wp_additional_image_sizes[ $meta['custom'] ]['width'] );
							$height = absint( $_wp_additional_image_sizes[ $meta['custom'] ]['height'] );
						} else {
							$width  = absint( get_option( $meta['custom'] . '_size_w' ) );
							$height = absint( get_option( $meta['custom'] . '_size_h' ) );
						}

						if ( $width ) {
							$new_meta['config']['slider_width'] = $width;
						}

						if ( $height ) {
							$new_meta['config']['slider_height'] = $height;
						}
					}
				}
			}

			if ( ! empty( $meta['transition'] ) ) {
				if ( 'slide-horizontal' == $meta['transition'] ) {
					$new_meta['config']['transition'] = 'horizontal';
				} elseif ( 'slide-vertical' == $meta['transition'] ) {
					$new_meta['config']['transition'] = 'vertical';
				} else {
					$new_meta['config']['transition'] = 'fade';
				}
			}

			if ( ! empty( $meta['speed'] ) ) {
				$new_meta['config']['duration'] = $meta['speed'];
			}

			if ( ! empty( $meta['duration'] ) ) {
				$new_meta['config']['speed'] = $meta['duration'];
			}

			if ( ! empty( $meta['animate'] ) ) {
				$new_meta['config']['auto'] = $meta['animate'];
			}

			if ( ! empty( $meta['navigation'] ) ) {
				$new_meta['config']['arrows'] = $meta['navigation'] ? 1 : 0;
			} else {
				$new_meta['config']['arrows'] = 0;
			}

			if ( ! empty( $meta['control'] ) ) {
				$new_meta['config']['control'] = $meta['control'] ? 1 : 0;
			} else {
				$new_meta['config']['control'] = 0;
			}

			if ( ! empty( $meta['keyboard'] ) ) {
				$new_meta['config']['keyboard'] = $meta['keyboard'];
			}

			if ( ! empty( $meta['pauseplay'] ) ) {
				$new_meta['config']['pauseplay'] = $meta['pauseplay'];
			}

			if ( ! empty( $meta['random'] ) ) {
				$new_meta['config']['random'] = $meta['random'];
			}

			if ( ! empty( $meta['number'] ) ) {
				$new_meta['config']['start'] = $meta['number'];
			}

			if ( ! empty( $meta['loop'] ) ) {
				$new_meta['config']['loop'] = $meta['loop'];
			}

			if ( ! empty( $meta['hover'] ) ) {
				$new_meta['config']['hover'] = $meta['hover'];
			}

			if ( ! empty( $meta['css'] ) ) {
				$new_meta['config']['css'] = $meta['css'];
			}

			if ( ! empty( $meta['smooth'] ) ) {
				$new_meta['config']['smooth'] = $meta['smooth'];
			}

			if ( ! empty( $meta['delay'] ) ) {
				$new_meta['config']['delay'] = $meta['delay'];
			}

			// Set to the classic theme to keep people from going nuts with a theme change.
			if ( ! empty( $meta['theme'] ) && 'metro' == $meta['theme'] ) {
				$new_meta['config']['slider_theme'] = 'metro';
			} else {
				$new_meta['config']['slider_theme'] = 'classic';
			}

			// Grab all attachments and add them to the slider.
			$attachments = get_posts(
				array(
					'orderby'        => 'menu_order',
					'order'          => 'ASC',
					'post_type'      => 'attachment',
					'post_parent'    => $slider->ID,
					'post_status'    => null,
					'posts_per_page' => -1,
				)
			);

			// Loop through attachments and add them to the slider.
			foreach ( (array) $attachments as $slide ) {
				switch ( $slide->post_mime_type ) {
					case 'soliloquy/video':
						$new_meta['slider'][ $slide->ID ] = array(
							'status'  => 'active',
							'id'      => $slide->ID,
							'src'     => '',
							'title'   => isset( $slide->post_title ) ? $slide->post_title : '',
							'link'    => '',
							'url'     => isset( $slide->post_content ) ? $slide->post_content : '',
							'thumb'   => '',
							'caption' => isset( $slide->post_excerpt ) ? $slide->post_excerpt : '',
							'type'    => 'video',
						);
						break;
					case 'soliloquy/html':
						$new_meta['slider'][ $slide->ID ] = array(
							'status' => 'active',
							'id'     => $slide->ID,
							'src'    => '',
							'title'  => isset( $slide->post_title ) ? $slide->post_title : '',
							'link'   => '',
							'code'   => isset( $slide->post_content ) ? $slide->post_content : '',
							'type'   => 'html',
						);
						break;
					default:
						$url                              = wp_get_attachment_image_src( $slide->ID, 'full' );
						$alt_text                         = get_post_meta( $slide->ID, '_wp_attachment_image_alt', true );
						$new_meta['slider'][ $slide->ID ] = array(
							'status'  => 'active',
							'id'      => $slide->ID,
							'src'     => isset( $url[0] ) ? esc_url( $url[0] ) : '',
							'title'   => get_the_title( $slide->ID ),
							'link'    => get_post_meta( $slide->ID, '_soliloquy_image_link', true ),
							'linktab' => get_post_meta( $slider->ID, '_soliloquy_image_link_tab', true ),
							'alt'     => ! empty( $alt_text ) ? $alt_text : get_the_title( $slide->ID ),
							'caption' => ! empty( $slide->post_excerpt ) ? $slide->post_excerpt : '',
							'filter'  => get_post_meta( $slide->ID, '_soliloquy_filters_image_filter', true ),
							'type'    => 'image',
						);
						break;
				}
			}

			// Convert v1 Pinterest addon data to v2 if necessary.
			$pinterest = get_post_meta( $slider->ID, '_soliloquy_pinterest', true );

			if ( ! empty( $pinterest['enable'] ) ) {
				$new_meta['config']['pinterest'] = $pinterest['enable'];
			}

			if ( ! empty( $pinterest['position'] ) ) {
				$new_meta['config']['pinterest_position'] = str_replace( '-', '_', $pinterest['position'] );
			}

			// Convert v1 carousel addon to v2 if necessary.
			$carousel = get_post_meta( $slider->ID, '_soliloquy_carousel', true );

			if ( ! empty( $carousel['width'] ) ) {
				$new_meta['config']['carousel']       = 1;
				$new_meta['config']['carousel_width'] = $carousel['width'];
			}

			if ( ! empty( $carousel['margin'] ) ) {
				$new_meta['config']['carousel_margin'] = $carousel['margin'];
			}

			if ( ! empty( $carousel['mminimum'] ) ) {
				$new_meta['config']['carousel_min'] = $carousel['minimum'];
			}

			if ( ! empty( $carousel['maximum'] ) ) {
				$new_meta['config']['carousel_maximum'] = $carousel['maximum'];
			}

			if ( ! empty( $carousel['move'] ) ) {
				$new_meta['config']['carousel_move'] = $carousel['move'];
			}

			// Convert v1 thumbnails addon to v2 if necessary.
			$thumbnails = get_post_meta( $slider->ID, '_soliloquy_thumbnails', true );

			if ( ! empty( $thumbnails['use'] ) ) {
				$new_meta['config']['thumbnails'] = $thumbnails['use'];
			}

			if ( ! empty( $thumbnails['width'] ) ) {
				$new_meta['config']['thumbnails_width'] = $thumbnails['width'];
			}

			if ( ! empty( $thumbnails['margin'] ) ) {
				$new_meta['config']['thumbnails_margin'] = $thumbnails['margin'];
			}

			if ( ! empty( $thumbnails['minimum'] ) ) {
				$new_meta['config']['thumbnails_num'] = $thumbnails['minimum'];
			}

			if ( ! empty( $thumbnails['position'] ) ) {
				$new_meta['config']['thumbnails_position'] = $thumbnails['position'];
			}

			// Convert v1 featured content to v 2 if necessary.
			$fc = get_post_meta( $slider->ID, '_soliloquy_fc', true );

			if ( ! empty( $meta['type'] ) && 'featured' == $meta['type'] ) {
				$new_meta['config']['type'] = 'fc';
			}

			if ( ! empty( $fc['post_types'] ) ) {
				$new_meta['config']['fc_post_types'] = $fc['post_types'];
			}

			if ( ! empty( $fc['terms'] ) ) {
				$new_meta['config']['fc_terms'] = $fc['terms'];
			}

			if ( ! empty( $fc['query'] ) ) {
				$new_meta['config']['fc_query'] = $fc['query'];
			}

			if ( ! empty( $fc['include_exclude'] ) ) {
				$new_meta['config']['fc_inc_ex'] = $fc['include_exclude'];
			}

			if ( ! empty( $fc['orderby'] ) ) {
				$new_meta['config']['fc_orderby'] = $fc['orderby'];
			}

			if ( ! empty( $fc['order'] ) ) {
				$new_meta['config']['fc_order'] = $fc['order'];
			}

			if ( ! empty( $fc['number'] ) ) {
				$new_meta['config']['fc_number'] = $fc['number'];
			}

			if ( ! empty( $fc['offset'] ) ) {
				$new_meta['config']['fc_offset'] = $fc['offset'];
			}

			if ( ! empty( $fc['post_status'] ) ) {
				$new_meta['config']['fc_status'] = $fc['post_status'];
			}

			if ( ! empty( $fc['post_url'] ) ) {
				$new_meta['config']['fc_post_url'] = $fc['post_url'];
			}

			if ( ! empty( $fc['post_title'] ) ) {
				$new_meta['config']['fc_post_title'] = $fc['post_title'];
			}

			if ( ! empty( $fc['post_title_link'] ) ) {
				$new_meta['config']['fc_post_title_link'] = $fc['post_title_link'];
			}

			if ( ! empty( $fc['content_type'] ) ) {
				$new_meta['config']['fc_content_type'] = str_replace( '-', '_', $fc['content_type'] );
			}

			if ( ! empty( $fc['post_content_length'] ) ) {
				$new_meta['config']['fc_content_length'] = $fc['post_content_length'];
			}

			if ( ! empty( $fc['ellipses'] ) ) {
				$new_meta['config']['fc_content_ellipses'] = $fc['ellipses'];
			}

			if ( ! empty( $fc['read_more'] ) ) {
				$new_meta['config']['fc_read_more'] = $fc['read_more'];
			}

			if ( ! empty( $fc['read_more_text'] ) ) {
				$new_meta['config']['fc_read_more_text'] = $fc['read_more_text'];
			}

			if ( ! empty( $fc['fallback'] ) ) {
				$new_meta['config']['fc_fallback'] = $fc['fallback'];
			}

			// Convert v1 Instagram addon to v2 if necessary.
			$instagram = get_post_meta( $slider->ID, '_soliloquy_instagram', true );

			// Update the Instagram db option from v1 to v2 if v2 does not already exist.
			$v2_auth = get_option( 'soliloquy_instagram' );

			if ( ! $v2_auth || empty( $v2_auth['token'] ) || empty( $v2_auth['id'] ) ) {
				$v1_auth  = get_option( 'soliloquy_instagram_data' );
				$new_auth = array();

				if ( ! empty( $v1_auth->access_token ) ) {
					$new_auth['token'] = $v1_auth->access_token;
				}

				if ( ! empty( $v1_auth->user->id ) ) {
					$new_auth['id'] = $v1_auth->user->id;
				}

				update_option( 'soliloquy_instagram', $new_auth );
			}

			if ( ! empty( $meta['type'] ) && 'instagram' == $meta['type'] ) {
				$new_meta['config']['type'] = 'instagram';
			}

			if ( ! empty( $instagram['number'] ) ) {
				$new_meta['config']['instagram_number'] = $instagram['number'];
			}

			if ( ! empty( $instagram['link'] ) ) {
				$new_meta['config']['instagram_link'] = $instagram['link'];
			}

			if ( ! empty( $instagram['caption'] ) ) {
				$new_meta['config']['instagram_caption'] = $instagram['caption'];
			}

			if ( ! empty( $instagram['random'] ) ) {
				$new_meta['config']['instagram_random'] = $instagram['random'];
			}

			if ( ! empty( $instagram['cache'] ) ) {
				$new_meta['config']['instagram_cache'] = $instagram['cache'];
			}

			// Convert v1 lightbox addon to v2 if necessary. This is a new lightbox engine so not a lot of crossover here.
			$lightbox = get_post_meta( $slider->ID, '_soliloquy_lightbox', true );

			if ( ! empty( $lightbox['auto'] ) ) {
				$new_meta['config']['lightbox'] = $lightbox['auto'];
			}

			if ( ! empty( $lightbox['lightbox_theme'] ) ) {
				$new_meta['config']['lightbox_theme'] = 'base';
			}

			// Update the post meta for the new slider.
			update_post_meta( $slider->ID, '_sol_slider_data', $new_meta );

			// Force the post to update.
			wp_update_post(
				array(
					'ID'        => $slider->ID,
					'post_type' => 'soliloquy',
				)
			);

			// Flush caches for any sliders.
			Soliloquy_Common::get_instance()->flush_slider_caches( $slider->ID, $new_meta['config']['slug'] );
		}

		// Now grab any v2 sliders and convert the post type back to the proper system.
		$v2_sliders = get_posts(
			array(
				'post_type'      => 'soliloquyv2',
				'posts_per_page' => -1,
			)
		);

		// Loop through the sliders, grab the data, delete and backwards convert them back to 'soliloquy' post type.
		foreach ( (array) $v2_sliders as $slider ) {
			// Grab any slider meta and add the attachment ID to the data array.
			$slider_meta = get_post_meta( $slider->ID, '_sol_slider_data', true );
			if ( ! empty( $slider_meta['slider'] ) ) {
				foreach ( $slider_meta['slider'] as $id => $data ) {
					$slider_meta['slider'][ $id ]['id'] = $id;
				}
			}

			update_post_meta( $slider->ID, '_sol_slider_data', $slider_meta );

			$data = array(
				'ID'        => $slider->ID,
				'post_type' => 'soliloquy',
			);
			wp_update_post( $data );

			// Flush caches for any sliders.
			Soliloquy_Common::get_instance()->flush_slider_caches( $slider->ID );
		}

		// Turn off cache suspension and flush the cache to remove any cache inconsistencies.
		wp_suspend_cache_invalidation( false );
		wp_cache_flush();

		// Update the option to signify that upgrading is complete.
		update_option( 'soliloquy_upgrade', true );

		// Send back the response.
		echo json_encode( true );
		die;
	}

	/**
	 * Called by the media view when the video URL input is changed
	 * Checks if the supplied video URL is a locally hosted video URL or not
	 *
	 * @since 1.1.1
	 *
	 * @return json Success or Error
	 */
	public function is_hosted_video() {

		// Run a security check first.
		check_admin_referer( 'soliloquy-is-hosted', 'nonce' );

		if ( ! current_user_can( 'edit_posts' ) ) {
			wp_send_json_error( [ 'message' => esc_html__( 'You are not allowed to edit sliders.', 'soliloquy' ) ] );
		}

		// Setup vars
		$video_url = ( isset( $_POST['video_url'] ) ? sanitize_text_field( $_POST['video_url'] ) : '' );

		// Check a URL was defined
		if ( empty( $video_url ) ) {
			wp_send_json_error( esc_attr__( 'No video URL was defined', 'soliloquy' ) );
			die();
		}

		// Get video type
		$video_type = Soliloquy_Common::get_instance()->get_video_type( $video_url, array(), array(), true );

		// Depending on the video type, return true or false to determine whether it's a self hosted video
		$is_hosted_video = false;
		switch ( $video_type ) {
			case 'youtube':
			case 'vimeo':
			case 'wistia':
				$is_hosted_video = false;
				break;

			case 'mp4':
			case 'flv':
			case 'ogv':
			case 'webm':
				$is_hosted_video = true;
				break;

			default:
				// Allow addons to define whether the video type is hosted or third party
				$is_hosted_video = apply_filters( 'soliloquy_is_hosted_video', $is_hosted_video, $video_type );
				break;
		}

		// Return
		wp_send_json_success( $is_hosted_video );
		die();
	}

	/**
	 * Changes the type of slider to the user selection.
	 *
	 * @since 1.0.0
	 */
	public function change_type() {

		// Run a security check first.
		check_admin_referer( 'soliloquy-change-type', 'nonce' );

		$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : null;

		if ( null === $post_id ) {
			wp_send_json_error( [ 'message' => esc_html__( 'Invalid Post ID.', 'soliloquy' ) ] );
		}

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			wp_send_json_error( [ 'message' => esc_html__( 'You are not allowed to edit sliders.', 'soliloquy' ) ] );
		}

		// Prepare variables.
		$post = get_post( $post_id );
		$type = stripslashes( $_POST['type'] );

		// Retrieve the data for the type selected.
		ob_start();
		$instance = Soliloquy_Metaboxes::get_instance();
		$instance->images_display( $type, $post );
		$html = ob_get_clean();

		// Send back the response.
		echo json_encode(
			array(
				'type' => $type,
				'html' => $html,
			)
		);
		die;
	}

	/**
	 * Loads an image into a slider.
	 *
	 * @since 1.0.0
	 */
	public function load_image() {

		// Run a security check first.
		check_ajax_referer( 'soliloquy-load-image', 'nonce' );

		// Prepare variables.
		$id      = absint( $_POST['id'] );
		$post_id = absint( $_POST['post_id'] );

		// Check if the user can edit slider and the attached post.
		if ( ! current_user_can( 'edit_post', $post_id ) || ! current_user_can( 'edit_post', $id ) ) {
			wp_send_json_error( [ 'message' => esc_html__( 'You are not allowed to edit sliders.', 'soliloquy' ) ] );
		}

		// Set post meta to show that this image is attached to one or more Soliloquy sliders.
		$has_slider = get_post_meta( $id, '_sol_has_slider', true );
		if ( empty( $has_slider ) ) {
			$has_slider = array();
		}

		$has_slider[] = $post_id;
		update_post_meta( $id, '_sol_has_slider', $has_slider );

		// Set post meta to show that this image is attached to a slider on this page.
		$in_slider = get_post_meta( $post_id, '_sol_in_slider', true );
		if ( empty( $in_slider ) ) {
			$in_slider = array();
		}

		$in_slider[] = $id;
		update_post_meta( $post_id, '_sol_in_slider', $in_slider );

		// Set data and order of image in slider.
		$slider_data = get_post_meta( $post_id, '_sol_slider_data', true );
		if ( empty( $slider_data ) ) {
			$slider_data = array();
		}

		// If no slider ID has been set, set it now.
		if ( empty( $slider_data['id'] ) ) {
			$slider_data['id'] = $post_id;
		}

		// Set data and update the meta information.
		$slider_data = $this->prepare_slider_data( $slider_data, $id );
		update_post_meta( $post_id, '_sol_slider_data', $slider_data );

		// Run hook before building out the item.
		do_action( 'soliloquy_ajax_load_image', $id, $post_id );

		// Build out the individual HTML output for the slider image that has just been uploaded.
		$html = Soliloquy_Metaboxes::get_instance()->get_slider_item( $id, $slider_data['slider'][ $id ], 'image', $post_id );

		// Flush the slider cache.
		Soliloquy_Common::get_instance()->flush_slider_caches( $post_id );

		echo json_encode( $html );
		die;
	}

	/**
	 * Inserts one or more slides into a slider.
	 *
	 * @since 1.0.0
	 */
	public function insert_slides() {

		// Run a security check first.
		check_ajax_referer( 'soliloquy-insert-images', 'nonce' );

		$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : null;

		if ( null === $post_id ) {
			wp_send_json_error( [ 'message' => esc_html__( 'Invalid Post ID.', 'soliloquy' ) ] );
		}

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			wp_send_json_error( [ 'message' => esc_html__( 'You are not allowed to edit sliders.', 'soliloquy' ) ] );
		}

		// Prepare variables.
		$images = ! empty( $_POST['images'] ) ? json_decode( stripslashes( $_POST['images'] ), true ) : array();
		$videos = ! empty( $_POST['videos'] ) ? stripslashes_deep( (array) $_POST['videos'] ) : array();
		$html   = ! empty( $_POST['html'] ) ? stripslashes_deep( (array) $_POST['html'] ) : array();

		// Grab and update any slider data if necessary.
		$in_slider = get_post_meta( $post_id, '_sol_in_slider', true );
		if ( empty( $in_slider ) ) {
			$in_slider = array();
		}

		// Set data and order of image in slider.
		$slider_data = get_post_meta( $post_id, '_sol_slider_data', true );
		if ( empty( $slider_data ) ) {
			$slider_data = array();
		}

		// If no slider ID has been set, set it now.
		if ( empty( $slider_data['id'] ) ) {
			$slider_data['id'] = $post_id;
		}

		// Loop through the images and add them to the slider.
		foreach ( (array) $images as $i => $id ) {

			// Update the attachment image post meta first.
			$has_slider = get_post_meta( $id, '_sol_has_slider', true );
			if ( empty( $has_slider ) ) {
				$has_slider = array();
			}

			$has_slider[] = $post_id;
			update_post_meta( $id, '_sol_has_slider', $has_slider );

			// Now add the image to the slider for this particular post.
			$in_slider[] = $id;
			$slider_data = $this->prepare_slider_data( $slider_data, $id['id'] );
		}

		// Loop through the videos and add them to the slider.
		foreach ( (array) $videos as $i => $data ) {

			// Pass over if the main items necessary for the video are not set.
			if ( ! isset( $data['title'] ) || ! isset( $data['url'] ) ) {
				continue;
			}

			// Generate a custom ID for the video.
			// Note: we don't use sanitize_title_with_dashes as this retains special accented characters, resulting in jQuery errors
			// when subsequently trying to edit an exitsing slide.
			$id = $slider_data['id'] . '-' . preg_replace( '/[^A-Za-z0-9]/', '', strtolower( $data['title'] ) );

			// Now add the image to the slider for this particular post.
			$in_slider[] = $id;
			$slider_data = $this->prepare_slider_data( $slider_data, $id, 'video', $data );

		}

		// Loop through the HTML and add them to the slider.
		foreach ( (array) $html as $i => $data ) {
			// Pass over if the main items necessary for the video are not set.
			if ( empty( $data['title'] ) || empty( $data['code'] ) ) {
				continue;
			}

			// Generate a custom ID for the video.
			$id = $slider_data['id'] . '-' . preg_replace( '/[^A-Za-z0-9]/', '', strtolower( $data['title'] ) );

			// Now add the image to the slider for this particular post.
			$in_slider[] = $id;
			$slider_data = $this->prepare_slider_data( $slider_data, $id, 'html', $data );
		}

		// Update the slider data.
		update_post_meta( $post_id, '_sol_in_slider', $in_slider );
		update_post_meta( $post_id, '_sol_slider_data', $slider_data );

		// Run hook before finishing.
		do_action( 'soliloquy_ajax_insert_slides', $images, $videos, $html, $post_id );

		// Flush the slider cache.
		Soliloquy_Common::get_instance()->flush_slider_caches( $post_id );

		// Return a HTML string comprising of all gallery images, so the UI can be updated
		$html = '';
		foreach ( (array) $slider_data['slider'] as $id => $data ) {
			$html .= Soliloquy_Metaboxes::get_instance()->get_slider_item( $id, $data, ( ! empty( $data['type'] ) ? $data['type'] : 'image' ), $post_id );
		}
		echo wp_send_json_success( $html );
		die;
	}

	/**
	 * Sorts images based on user-dragged position in the slider.
	 *
	 * @since 1.0.0
	 */
	public function sort_images() {

		// Run a security check first.
		check_admin_referer( 'soliloquy-sort', 'nonce' );

		$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : false;

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			wp_send_json_error( [ 'message' => esc_html__( 'You are not allowed to edit sliders.', 'soliloquy' ) ] );
		}

		// Prepare variables.
		$order       = explode( ',', $_POST['order'] );
		$slider_data = get_post_meta( $post_id, '_sol_slider_data', true );

		// Copy the slider config, removing the slides
		$new_order = $slider_data;
		unset( $new_order['slider'] );
		$new_order['slider'] = array();

		// Loop through the order and generate a new array based on order received.
		foreach ( $order as $id ) {
			$new_order['slider'][ $id ] = $slider_data['slider'][ $id ];
		}

		// Update the slider data.
		update_post_meta( $post_id, '_sol_slider_data', $new_order );

		// Flush the slider cache.
		Soliloquy_Common::get_instance()->flush_slider_caches( $post_id );

		echo json_encode( true );
		die;
	}

	/**
	 * Removes multiple images from a slider
	 *
	 * @since 2.5
	 */
	public function remove_slides() {

		// Run a security check first.
		check_admin_referer( 'soliloquy-remove-slide', 'nonce' );

		$post_id = isset( $_POST['post_id'] ) ? absint( wp_unslash( $_POST['post_id'] ) ) : false;

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			wp_send_json_error( [ 'message' => esc_html__( 'You are not allowed to edit sliders.', 'soliloquy' ) ] );
		}

		// Prepare variables.
		$attach_ids  = (array) $_POST['attachment_ids'];
		$slider_data = get_post_meta( $post_id, '_sol_slider_data', true );
		$in_slider   = get_post_meta( $post_id, '_sol_in_slider', true );

		foreach ( (array) $attach_ids as $attach_id ) {
			$has_slider = get_post_meta( $attach_id, '_sol_has_slider', true );

			// Unset the image from the slider, in_slider and has_slider checkers.
			unset( $slider_data['slider'][ $attach_id ] );

			if ( ( $key = array_search( $attach_id, (array) $in_slider ) ) !== false ) {
				unset( $in_slider[ $key ] );
			}

			if ( ( $key = array_search( $post_id, (array) $has_slider ) ) !== false ) {
				unset( $has_slider[ $key ] );
			}

			// Update the attachment data.
			update_post_meta( $attach_id, '_sol_has_slider', $has_slider );
		}

		// Update the gallery data
		update_post_meta( $post_id, '_sol_slider_data', $slider_data );
		update_post_meta( $post_id, '_sol_in_slider', $in_slider );

		// Run hook before finishing the reponse.
		do_action( 'soliloquy_ajax_remove_slides', $attach_id, $post_id );

		// Flush the gallery cache.
		Soliloquy_Common::get_instance()->flush_slider_caches( $post_id );

		echo json_encode( true );
		die;
	}

	/**
	 * Removes an image from a slider.
	 *
	 * @since 1.0.0
	 */
	public function remove_slide() {

		// Run a security check first.
		check_admin_referer( 'soliloquy-remove-slide', 'nonce' );

		$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : null;

		if ( null === $post_id ) {
			wp_send_json_error( [ 'message' => esc_html__( 'Invalid Post ID.', 'soliloquy' ) ] );
		}

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			wp_send_json_error( [ 'message' => esc_html__( 'You are not allowed to edit sliders.', 'soliloquy' ) ] );
		}

		// Prepare variables.
		$attach_id   = trim( $_POST['attachment_id'] );
		$slider_data = get_post_meta( $post_id, '_sol_slider_data', true );
		$in_slider   = get_post_meta( $post_id, '_sol_in_slider', true );
		$has_slider  = get_post_meta( $attach_id, '_sol_has_slider', true );

		// Unset the image from the slider, in_slider and has_slider checkers.
		unset( $slider_data['slider'][ $attach_id ] );

		if ( ( $key = array_search( $attach_id, (array) $in_slider ) ) !== false ) {
			unset( $in_slider[ $key ] );
		}

		if ( ( $key = array_search( $post_id, (array) $has_slider ) ) !== false ) {
			unset( $has_slider[ $key ] );
		}

		// Update the slider data.
		update_post_meta( $post_id, '_sol_slider_data', $slider_data );
		update_post_meta( $post_id, '_sol_in_slider', $in_slider );
		update_post_meta( $attach_id, '_sol_has_slider', $has_slider );

		// Run hook before finishing the reponse.
		do_action( 'soliloquy_ajax_remove_slide', $attach_id, $post_id );

		// Flush the slider cache.
		Soliloquy_Common::get_instance()->flush_slider_caches( $post_id );

		echo json_encode( true );
		die;
	}

	/**
	 * Saves the metadata for an image in a slider.
	 *
	 * @since 1.0.0
	 */
	public function save_meta() {

		// Run a security check first.
		check_admin_referer( 'soliloquy-save-meta', 'nonce' );

		$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : null;

		if ( null === $post_id ) {
			wp_send_json_error( [ 'message' => esc_html__( 'Invalid Post ID.', 'soliloquy' ) ] );
		}

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			wp_send_json_error( [ 'message' => esc_html__( 'You are not allowed to edit sliders.', 'soliloquy' ) ] );
		}

		// Prepare variables.
		$attach_id   = $_POST['attach_id'];
		$meta        = $_POST['meta'];
		$slider_data = get_post_meta( $post_id, '_sol_slider_data', true );

		// Go ahead and ensure to store the attachment ID.
		$slider_data['slider'][ $attach_id ]['id'] = $attach_id;

		// Save the different types of default meta fields for images, videos and HTML slides.
		if ( isset( $meta['status'] ) ) {
			$slider_data['slider'][ $attach_id ]['status'] = trim( esc_html( $meta['status'] ) );
		}

		if ( isset( $meta['title'] ) ) {
			$slider_data['slider'][ $attach_id ]['title'] = trim( esc_html( $meta['title'] ) );
		}

		if ( isset( $meta['alt'] ) ) {
			$slider_data['slider'][ $attach_id ]['alt'] = trim( esc_html( $meta['alt'] ) );
		}

		if ( isset( $meta['link'] ) ) {
			$slider_data['slider'][ $attach_id ]['link'] = esc_url_raw( $meta['link'] );
		}

		if ( isset( $meta['linktab'] ) && $meta['linktab'] ) {
			$slider_data['slider'][ $attach_id ]['linktab'] = 1;
		} else {
			$slider_data['slider'][ $attach_id ]['linktab'] = 0;
		}

		if ( isset( $meta['caption'] ) && ! empty( $meta['caption'] ) ) {
			$slider_data['slider'][ $attach_id ]['caption'] = wp_kses_post( $meta['caption'] );
		} else {
			$slider_data['slider'][ $attach_id ]['caption'] = '';
		}

		if ( isset( $meta['url'] ) ) {
			$slider_data['slider'][ $attach_id ]['url'] = esc_url_raw( $meta['url'] );
		}

		if ( isset( $meta['src'] ) ) {
			$slider_data['slider'][ $attach_id ]['src'] = esc_url_raw( $meta['src'] );
		}

		if ( isset( $meta['code'] ) ) {
			$slider_data['slider'][ $attach_id ]['code'] = wp_kses_post( $meta['code'] );
		}

		// Allow filtering of meta before saving.
		$slider_data = apply_filters( 'soliloquy_ajax_save_meta', $slider_data, $meta, $attach_id, $post_id );

		// Update the slider data.
		update_post_meta( $post_id, '_sol_slider_data', $slider_data );

		// Flush the slider cache.
		Soliloquy_Common::get_instance()->flush_slider_caches( $post_id );

		wp_send_json_success();

		die;
	}

	public function bulk_save_meta() {

		// Run a security check first.
		check_admin_referer( 'soliloquy-save-meta', 'nonce' );

		$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : null;

		if ( null === $post_id ) {
			wp_send_json_error( [ 'message' => esc_html__( 'Invalid Post ID.', 'soliloquy' ) ] );
		}

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			wp_send_json_error( [ 'message' => esc_html__( 'You are not allowed to edit sliders.', 'soliloquy' ) ] );
		}

		// Prepare variables.
		$attach_id = $_POST['image_ids'];
		$meta      = $_POST['meta'];

		// Get gallery.
		$slider_data = get_post_meta( $post_id, '_sol_slider_data', true );
		if ( empty( $slider_data ) || ! is_array( $slider_data ) ) {
			wp_send_json_error();
		}

		// Iterate through gallery images, updating the metadata.
		foreach ( $attach_id as $image_id ) {

			// If the image isn't in the gallery, something went wrong - so skip this image.
			if ( ! isset( $slider_data['slider'][ $image_id ] ) ) {
				continue;
			}

			// Go ahead and ensure to store the attachment ID.
			$slider_data['slider'][ $image_id ]['id'] = $image_id;

			if ( isset( $meta['alt'] ) && $meta['alt'] != '' ) {
				$slider_data['slider'][ $image_id ]['alt'] = trim( esc_html( $meta['alt'] ) );
			}

			if ( isset( $meta['status'] ) && $meta['status'] != '' ) {
				$slider_data['slider'][ $image_id ]['status'] = trim( esc_html( $meta['status'] ) );
			}

			if ( isset( $meta['link'] ) && $meta['link'] != '' ) {
				$slider_data['slider'][ $image_id ]['link'] = esc_url_raw( $meta['link'] );
			}

			if ( isset( $meta['linktab'] ) && $meta['linktab'] ) {
				$slider_data['slider'][ $image_id ]['linktab'] = 1;
			} else {
				$slider_data['slider'][ $image_id ]['linktab'] = 0;
			}

			if ( isset( $meta['caption'] ) && $meta['caption'] != '' ) {
				$slider_data['slider'][ $image_id ]['caption'] = wp_kses_post( $meta['caption'] );
			}

			if ( isset( $meta['url'] ) && $meta['url'] != '' ) {
				$slider_data['slider'][ $image_id ]['url'] = esc_url_raw( $meta['url'] );
			}

			if ( isset( $meta['src'] ) && $meta['src'] != '' ) {
				$slider_data['slider'][ $image_id ]['src'] = esc_url_raw( $meta['src'] );
			}

			$slider_data = apply_filters( 'soliloquy_ajax_save_bulk_meta', $slider_data, $meta, $image_id, $post_id );

		}

		// Update the slider data.
		update_post_meta( $post_id, '_sol_slider_data', $slider_data );

		// Flush the slider cache.
		Soliloquy_Common::get_instance()->flush_slider_caches( $post_id );

		// Done
		wp_send_json_success();
		die;
	}
	/**
	 * Refreshes the DOM view for a slider.
	 *
	 * @since 1.0.0
	 */
	public function refresh() {

		// Run a security check first.
		check_admin_referer( 'soliloquy-refresh', 'nonce' );

		$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : null;

		if ( null === $post_id ) {
			wp_send_json_error( [ 'message' => esc_html__( 'Invalid Post ID.', 'soliloquy' ) ] );
		}

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			wp_send_json_error( [ 'message' => esc_html__( 'You are not allowed to edit sliders.', 'soliloquy' ) ] );
		}

		// Prepare variables.
		$slider = '';

		// Grab all slider data.
		$slider_data = get_post_meta( $post_id, '_sol_slider_data', true );

		// If there are no slider items, don't do anything.
		if ( empty( $slider_data ) || empty( $slider_data['slider'] ) ) {
			echo json_encode( array( 'error' => true ) );
			die;
		}

		// Loop through the data and build out the slider view.
		foreach ( (array) $slider_data['slider'] as $id => $data ) {
			$slider .= Soliloquy_Metaboxes::get_instance()->get_slider_item( $id, $data, $data['type'], $post_id );
		}

		echo json_encode( array( 'success' => $slider ) );
		die;
	}

	/**
	 * Retrieves and return slider data for the specified ID.
	 *
	 * @since 1.0.0
	 */
	public function load_slider_data() {

		// Run a security check first.
		check_admin_referer( 'soliloquy-load-slider', 'nonce' );

		// Prepare variables and grab the slider data.
		$slider_id   = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : false;
		$slider_data = get_post_meta( $slider_id, '_sol_slider_data', true );

		// Send back publicly viewable slider data.
		if ( is_post_publicly_viewable( $slider_id ) ) {
			echo wp_json_encode( $slider_data );
		} else {
			echo wp_json_encode( '' );
		}
		die;
	}

	/**
	 * Installs an Soliloquy addon.
	 *
	 * @since 1.0.0
	 */
	public function install_addon() {

		// Run a security check first.
		check_admin_referer( 'soliloquy-install', 'nonce' );

		if ( ! current_user_can( 'install_plugins' ) ) {
			wp_send_json_error( [ 'message' => esc_html__( 'You are not allowed to install plugins.', 'soliloquy' ) ] );
		}

		// Install the addon.
		if ( isset( $_POST['plugin'] ) ) {
			$download_url = $_POST['plugin'];
			global $hook_suffix;

			// Set the current screen to avoid undefined notices.
			set_current_screen();

			// Prepare variables.
			$method = '';
			$url    = add_query_arg(
				array(
					'page' => 'soliloquy-settings',
				),
				admin_url( 'admin.php' )
			);
			$url    = esc_url( $url );

			// Start output bufferring to catch the filesystem form if credentials are needed.
			ob_start();
			if ( false === ( $creds = request_filesystem_credentials( $url, $method, false, false, null ) ) ) {
				$form = ob_get_clean();
				echo json_encode( array( 'form' => $form ) );
				die;
			}

			// If we are not authenticated, make it happen now.
			if ( ! WP_Filesystem( $creds ) ) {
				ob_start();
				request_filesystem_credentials( $url, $method, true, false, null );
				$form = ob_get_clean();
				echo json_encode( array( 'form' => $form ) );
				die;
			}

			// We do not need any extra credentials if we have gotten this far, so let's install the plugin.
			require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
			require_once plugin_dir_path( Soliloquy::get_instance()->file ) . 'includes/admin/skin.php';

			// Create the plugin upgrader with our custom skin.
			$installer = new Plugin_Upgrader( $skin = new Soliloquy_Skin() );
			$installer->install( $download_url );

			// Flush the cache and return the newly installed plugin basename.
			wp_cache_flush();
			if ( $installer->plugin_info() ) {
				$plugin_basename = $installer->plugin_info();
				echo json_encode( array( 'plugin' => $plugin_basename ) );
				die;
			}
		}

		// Send back a response.
		echo json_encode( true );
		die;
	}

	/**
	 * Activates an Soliloquy addon.
	 *
	 * @since 1.0.0
	 */
	public function activate_addon() {

		// Run a security check first.
		check_admin_referer( 'soliloquy-activate', 'nonce' );

		if ( ! current_user_can( 'install_plugins' ) ) {
			wp_send_json_error( [ 'message' => esc_html__( 'You are not allowed to activate plugins.', 'soliloquy' ) ] );
		}

		// Activate the addon.
		if ( isset( $_POST['plugin'] ) ) {
			$activate = activate_plugin( $_POST['plugin'] );

			if ( is_wp_error( $activate ) ) {
				echo json_encode( array( 'error' => $activate->get_error_message() ) );
				die;
			}
		}

		echo json_encode( true );
		die;
	}

	/**
	 * Deactivates an Soliloquy addon.
	 *
	 * @since 1.0.0
	 */
	public function deactivate_addon() {

		// Run a security check first.
		check_admin_referer( 'soliloquy-deactivate', 'nonce' );

		if ( ! current_user_can( 'install_plugins' ) ) {
			wp_send_json_error( [ 'message' => esc_html__( 'You are not allowed to dectivate plugins.', 'soliloquy' ) ] );
		}

		// Deactivate the addon.
		if ( isset( $_POST['plugin'] ) ) {
			$deactivate = deactivate_plugins( $_POST['plugin'] );
		}

		echo json_encode( true );
		die;
	}

	/**
	 * Helper function to prepare the metadata for an image in a slider.
	 *
	 * @since 1.0.0
	 *
	 * @param array  $slider_data  Array of data for the slider.
	 * @param int    $id             The Post ID to prepare data for.
	 * @param string $type        The type of slide to prepare (defaults to image).
	 * @param array  $data         Data to be used for the slide.
	 * @return array $slider_data Amended slider data with updated image metadata.
	 */
	public function prepare_slider_data( $slider_data, $id, $type = 'image', $data = array() ) {

		// Get global option for slide status
		$publishingDefault = get_option( 'soliloquy-publishing-default', 'pending' );

		$common = Soliloquy_Common::get_instance();

		switch ( $type ) {
			case 'image':
				$attachment = get_post( $id );
				$url        = wp_get_attachment_image_src( $id, 'full' );
				$alt_text   = get_post_meta( $id, '_wp_attachment_image_alt', true );
				$slide      = array(
					'status'        => $publishingDefault,
					'id'            => $id,
					'attachment_id' => $id,
					'src'           => isset( $url[0] ) ? esc_url( $url[0] ) : '',
					'title'         => get_the_title( $id ),
					'link'          => '',
					'alt'           => ! empty( $alt_text ) ? $alt_text : get_the_title( $id ),
					'caption'       => ! empty( $attachment->post_excerpt ) ? wp_kses_post( $attachment->post_excerpt ) : '',
					'type'          => $type,
				);
				break;
			case 'video':
				$slide = array(
					'status'  => $publishingDefault,
					'id'      => $id,
					'src'     => isset( $data['src'] ) ? esc_url( $data['src'] ) : '',
					'title'   => isset( $data['title'] ) ? esc_html( $data['title'] ) : '',
					'url'     => isset( $data['url'] ) ? esc_url( $data['url'] ) : '',
					'caption' => isset( $data['caption'] ) ? trim( $data['caption'] ) : '',
					'type'    => $type,
				);

				// If no thumbnail specified, attempt to get it from the video
				if ( empty( $data['src'] ) ) {
					// Get Video Thumbnail
					if ( preg_match( '#(?<=v=)[a-zA-Z0-9-]+(?=&)|(?<=v\/)[^&\n]+(?=\?)|(?<=v=)[^&\n]+|(?<=youtu.be/)[^&\n]+#', $data['url'], $y_matches ) ) {
						// YouTube
						$videoID = $y_matches[0];

						// Get HD or SD thumbnail
						$data['src'] = $this->get_youtube_thumbnail_url( $videoID );
					} elseif ( preg_match( '#(?:https?:\/\/(?:[\w]+\.)*vimeo\.com(?:[\/\w]*\/videos?)?\/([0-9]+)[^\s]*)#i', $data['url'], $v_matches ) ) {
						// Vimeo
						$videoID = $v_matches[1];

						// Get highest resolution thumbnail
						$data['src'] = $this->get_vimeo_thumbnail_url( $videoID );
					} elseif ( preg_match( '/https?:\/\/(.+)?(wistia.com|wi.st)\/.*/i', $data['url'], $w_matches ) ) {
						// Wistia
						$parts   = explode( '/', $w_matches[0] );
						$videoID = array_pop( $parts );

						// Get image from API
						$res = wp_safe_remote_get( 'http://fast.wistia.net/oembed?url=' . urlencode( $item['url'] ) );
						$bod = wp_remote_retrieve_body( $res );
						$api = json_decode( $bod, true );
						if ( ! empty( $api['thumbnail_url'] ) ) {
							$data['src'] = remove_query_arg( 'image_crop_resized', $api['thumbnail_url'] );
						}
					} else {
						// Unknown
						$videoID = false;
					}

					// If a thumbnail was found, import it to the local filesystem
					$stream = Soliloquy_Import::get_instance()->import_remote_image( $data['src'], $data, $id, 0, true );
					if ( ! is_wp_error( $stream ) ) {
						if ( empty( $stream['error'] ) || isset( $stream['error'] ) && ! $stream['error'] ) {
							$slide['attachment_id'] = $stream['attachment_id'];
							$slide['src']           = $stream['url'];
						}
					}
				}

				break;
			case 'html':
				$slide = array(
					'status' => $publishingDefault,
					'id'     => $id,
					'title'  => isset( $data['title'] ) ? esc_html( $data['title'] ) : '',
					'code'   => isset( $data['code'] ) ? wp_kses_post( $data['code'] ) : '',
					'type'   => $type,
				);
				break;
		}
		// If slider data is not an array (i.e. we have no slides), just add the slide to the array
		if ( ! isset( $slider_data['slider'] ) || ! is_array( $slider_data['slider'] ) ) {
			$slider_data['slider']        = array();
			$slider_data['slider'][ $id ] = $slide;

		} else {
			// Add this image to the start or end of the gallery, depending on the setting
			if ( isset( $slider_data['config']['sort_order'] ) && $slider_data['config']['sort_order'] != 'manual' || isset( $slider_data['config']['sort_order'] ) && $slider_data['config']['sort_order'] != 'random' ) {

				$slide_position = 'sort';

			} else {

				$slide_position = get_option( 'soliloquy_slide_position' );

			}

			switch ( $slide_position ) {
				case 'sort':
					$slider_data['slider'][ $id ] = $slide;

					$slider_data = $common->sort_slides( $slider_data, $slider_data['config']['sort_order'] );

					break;
				case 'before':
					// Add slide to start of slides array
					// Store copy of slides, reset slider array and rebuild
					$slides                       = $slider_data['slider'];
					$slider_data['slider']        = array();
					$slider_data['slider'][ $id ] = $slide;
					foreach ( $slides as $old_slide_id => $old_slide ) {
						$slider_data['slider'][ $old_slide_id ] = $old_slide;
					}
					break;
				case 'after':
				default:
					// Add slide, this will default to the end of the array
					$slider_data['slider'][ $id ] = $slide;
					break;
			}
		}

		// Filter and return
		$slider_data = apply_filters( 'soliloquy_ajax_item_data', $slider_data, $id, $type );

		return $slider_data;
	}

	/**
	 * Attempts to get a HD thumbnail URL for the given video ID.
	 * If a 120x90 grey placeholder image is returned, the video isn't HD, so
	 * the function will return the SD thumbnail URL
	 *
	 * @since 2.3.9.7
	 *
	 * @param string $videoID YouTube Video ID
	 * @return string HD or SD Thumbnail URL
	 */
	public function get_youtube_thumbnail_url( $videoID ) {

		// Determine video URL
		$prefix  = is_ssl() ? 'https' : 'http';
		$baseURL = $prefix . '://img.youtube.com/vi/' . $videoID . '/';
		$hdURL   = $baseURL . 'maxresdefault.jpg'; // 1080p or 720p
		$sdURL   = $baseURL . '0.jpg'; // 480x360

		// Get HD image from YouTube
		$imageData = wp_safe_remote_get(
			$hdURL,
			array(
				'timeout' => 10,
			)
		);

		// Check request worked
		if ( is_wp_error( $imageData ) || ! isset( $imageData['body'] ) ) {
			// Failed - fallback to SD Thumbnail
			return $sdURL;
		}

		if ( function_exists( 'getimagesizefromstring' ) ) {

			// Get image size
			$imageSize = getimagesizefromstring( $imageData['body'] );

		} else {

			$imageSize = $this->getimagesizefromstring( $imageData['body'] );

		}

		// Check request worked
		if ( ! is_array( $imageSize ) ) {
			// Failed - fallback to SD Thumbnail
			return $sdURL;
		}

		// Check image size isn't 120x90
		if ( $imageSize[0] == 120 && $imageSize[1] == 90 ) {
			// Failed - fallback to SD Thumbnail
			return $sdURL;
		}

		// Image is a valid YouTube HD thumbnail
		return $hdURL;
	}

	/**
	 * Attempts to get the highest resolution thumbnail URL for the given video ID.
	 *
	 * @since 2.3.9.7
	 *
	 * @param string $videoID Vimeo Video ID
	 * @return string Best resolution URL
	 */
	public function get_vimeo_thumbnail_url( $videoID ) {

		// Get existing access token
		$vimeoAccessToken = get_option( 'soliloquy_vimeo_access_token' );

		// Load Vimeo API
		$vimeo = new Soliloquy_Vimeo( '5edbf52df73b6834db186409f88d2108df6a3d7f', '54e233c7ec90b22ad7cc77875b9a5a9d3083fa08' );
		$vimeo->setToken( $vimeoAccessToken );

		// Attempt to get video
		$response = $vimeo->request( '/videos/' . $videoID . '/pictures' );

		// Check response
		if ( $response['status'] != 200 ) {
			// May need a new access token
			// Clear old token + request a new one
			$vimeo->setToken( '' );
			$token            = $vimeo->clientCredentials();
			$vimeoAccessToken = $token['body']['access_token'];
			$vimeo->setToken( $vimeoAccessToken );

			// Store new token in options data
			update_option( 'soliloquy_vimeo_access_token', $vimeoAccessToken );

			// Run request again
			$response = $vimeo->request( '/videos/' . $videoID . '/pictures' );
		}

		// Check response
		if ( $response['status'] != 200 ) {
			// Really a failure!
			return false;
		}

		// If here, we got the video details
		// Check thumbnails are in the response
		if ( ! isset( $response['body']['data'] ) || ! isset( $response['body']['data'][0] ) || ! isset( $response['body']['data'][0]['sizes'] ) ) {
			return false;
		}

		// Get last item from the array index, as this is the highest resolution thumbnail
		$thumbnail = end( $response['body']['data'][0]['sizes'] );

		// Check thumbnail URL exists
		if ( ! isset( $thumbnail['link'] ) ) {
			return false;
		}

		// Return thumbnail URL
		unset( $vimeo );
		return $thumbnail['link'];
	}

	public function slider_view() {

		// Run a security check first.
		check_admin_referer( 'soliloquy-save-meta', 'nonce' );

		$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : null;

		if ( null === $post_id ) {
			wp_send_json_error( [ 'message' => esc_html__( 'Invalid Post ID.', 'soliloquy' ) ] );
		}

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			wp_send_json_error( [ 'message' => esc_html__( 'You are not allowed to edit sliders.', 'soliloquy' ) ] );
		}

		$view = $_POST['view'];

		$slider_data = get_post_meta( $post_id, '_sol_slider_data', true );
		// Save the different types of default meta fields for images, videos and HTML slides.
		if ( isset( $view ) ) {
			$slider_data['admin_view'] = trim( esc_html( $view ) );
		}

		// Allow filtering of meta before saving.
		$slider_data = apply_filters( 'soliloquy_ajax_change_status', $slider_data, $meta, $attach_id, $post_id );

		// Update the slider data.
		update_post_meta( $post_id, '_sol_slider_data', $slider_data );

		// Flush the slider cache.
		Soliloquy_Common::get_instance()->flush_slider_caches( $post_id );

		wp_send_json_success();
	}

	public function change_slide_status() {

		// Run a security check first.
		check_ajax_referer( 'soliloquy-save-meta', 'nonce' );

		$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : null;

		if ( null === $post_id ) {
			wp_send_json_error( [ 'message' => esc_html__( 'Invalid Post ID.', 'soliloquy' ) ] );
		}

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			wp_send_json_error( [ 'message' => esc_html__( 'You are not allowed to edit sliders.', 'soliloquy' ) ] );
		}

		// Prepare variables.
		$attach_id   = $_POST['slide_id'];
		$status      = $_POST['status'];
		$slider_data = get_post_meta( $post_id, '_sol_slider_data', true );

		// Go ahead and ensure to store the attachment ID.
		$slider_data['slider'][ $attach_id ]['id'] = $attach_id;

		// Save the different types of default meta fields for images, videos and HTML slides.
		if ( isset( $status ) ) {
			$slider_data['slider'][ $attach_id ]['status'] = trim( esc_html( $status ) );
		}

		// Allow filtering of meta before saving.
		$slider_data = apply_filters( 'soliloquy_ajax_change_status', $slider_data, $status, $attach_id, $post_id );

		// Update the slider data.
		update_post_meta( $post_id, '_sol_slider_data', $slider_data );

		// Flush the slider cache.
		Soliloquy_Common::get_instance()->flush_slider_caches( $post_id );

		wp_send_json_success();
		die;
	}

	/**
	 * sort_slides function.
	 *
	 * @access public
	 * @return void
	 */
	public function sort_slides() {

		// Run a security check first.
		check_admin_referer( 'soliloquy-save-meta', 'nonce' );

		$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : null;

		if ( null === $post_id ) {
			wp_send_json_error( [ 'message' => esc_html__( 'Invalid Post ID.', 'soliloquy' ) ] );
		}

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			wp_send_json_error( [ 'message' => esc_html__( 'You are not allowed to edit sliders.', 'soliloquy' ) ] );
		}

		$common = Soliloquy_Common_Admin::get_instance();

		// Prepare variables.
		$order = $_POST['order'];

		$slides = '';

		$data = get_post_meta( $post_id, '_sol_slider_data', true );

		$slider_data = $common->sort_slides( $data, $order );

		// Update the slider data.
		update_post_meta( $post_id, '_sol_slider_data', $slider_data );

		// Run hook before finishing.
		do_action( 'soliloquy_ajax_sort_slides', $post_id, $slider_data, $order );

		// Flush the slider cache.
		Soliloquy_Common::get_instance()->flush_slider_caches( $post_id );

		// Return a HTML string comprising of all gallery images, so the UI can be updated
		$html = '';
		foreach ( (array) $slider_data['slider'] as $id => $data ) {
			$html .= Soliloquy_Metaboxes::get_instance()->get_slider_item( $id, $data, ( ! empty( $data['type'] ) ? $data['type'] : 'image' ), $post_id );
		}

		echo wp_send_json_success( $html );

		die;
	}

	/**
	 * Grabs JS and executes it for any uninitialised sliders on screen
	 *
	 * Used by soliloquyInitManually() JS function, which in turn is called
	 * by AJAX requests e.g. after an Infinite Scroll event.
	 *
	 * @since 1.0.0
	 */
	public function init_sliders() {

		// Run a security check first.
		check_ajax_referer( 'soliloquy-ajax-nonce', 'ajax_nonce' );

		// Check we have some slider IDs
		if ( ! isset( $_REQUEST['ids'] ) ) {
			die();
		}

		// Setup instance
		$instance = Soliloquy_Shortcode::get_instance();
		$base     = Soliloquy::get_instance();

		// Build JS for each slider
		$js = '';
		foreach ( $_REQUEST['ids'] as $slider_id ) {

			// Get slider
			$data = $base->get_slider( $slider_id );

			// If no slider found, skip
			if ( ! $data ) {

				if ( class_exists( 'Soliloquy_Dynamic_Common' ) ) {

					$dynamic_id = Soliloquy_Dynamic_Common::get_instance()->get_dynamic_id();
					$defaults   = get_post_meta( $dynamic_id, '_sol_slider_data', true );

					$data       = $defaults;
					$data['id'] = 'custom_' . $slider_id;

				} else {

					continue;

				}
			}
			$js .= $instance->slider_init_single( $data, true );
		}

		// Output JS
		echo $js;
		die();
	}

	/**
	 * Helper function for older php versions getimagesizefromstring
	 *
	 * @access public
	 * @param mixed $data
	 * @param array &$imageinfo (default: array())
	 * @return void
	 */
	public function getimagesizefromstring( $data, &$imageinfo = array() ) {

		$uri = 'data://application/octet-stream;base64,' . base64_encode( $data );

		return getimagesize( $uri, $imageinfo );
	}

	/**
	 * Returns the singleton instance of the class.
	 *
	 * @since 2.5
	 *
	 * @return object The Soliloquy_Ajax object.
	 */
	public static function get_instance() {

		if ( ! isset( self::$instance ) && ! ( self::$instance instanceof Soliloquy_Ajax ) ) {
			self::$instance = new Soliloquy_Ajax();
		}

		return self::$instance;
	}
}

$soliloquy_ajax = Soliloquy_Ajax::get_instance();