<?php

namespace EFFE_Plugin;

/**
 * The public-facing functionality of the plugin.
 *
 * @package    Event_Feed_For_Eventbrite
 * @subpackage Event_Feed_For_Eventbrite/public
 * @author     Bohemia Plugins <https://www.bohemiaplugins.com>
 */
class Event_Feed_For_Eventbrite_Public {

	/**
	 * The ID of this plugin.
	 *
	 * @since    1.0.0
	 * @access   private
	 * @var      string    $plugin_name    The ID of this plugin.
	 */
	private $plugin_name;

	/**
	 * The version of this plugin.
	 *
	 * @since    1.0.0
	 * @access   private
	 * @var      string    $version    The current version of this plugin.
	 */
	private $version;

	/**
	 * Initialize the class and set its properties.
	 *
	 * @since    1.0.0
	 * @access   public
	 * @param    string    $plugin_name       The name of the plugin.
	 * @param    string    $version           The version of this plugin.
	 */
	public function __construct( $plugin_name, $version ) {

		$this->plugin_name = $plugin_name;
		$this->version = $version;

	}

	/**
	 * Register the stylesheets for the public-facing side of the site.
	 *
	 * @since    1.0.0
	 * @access   public
	 */
	public function register_styles() {

		// Vue modal
		wp_register_style( $this->plugin_name . '-vue-modal', plugin_dir_url( __FILE__ ) . 'libs/vue-modal/vue-modal.css', array(), $this->version, 'all' );

		// Main styles
		wp_register_style( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'assets/css/app.css', array( $this->plugin_name . '-vue-modal' ), $this->version, 'all' );

		// Tooltipster
		wp_register_style( $this->plugin_name . '-tooltipster', plugin_dir_url( __FILE__ ) . 'libs/tooltipster/tooltipster.bundle.min.css', array(), $this->version, 'all' );

		// Clipboard
		wp_register_style( $this->plugin_name . '-tooltipster-theme', plugin_dir_url( __FILE__ ) . 'libs/tooltipster/tooltipster-sideTip-borderless.min.css', array(), $this->version, 'all' );

	}

	/**
	 * Enqueue the stylesheets for the public-facing side of the site.
	 *
	 * @since    1.0.0
	 * @access   public
	 */
	public function enqueue_styles() {

		// Load only on event feed preview page
		if ( is_singular( 'event_feed' ) ) {
			wp_enqueue_style( $this->plugin_name . '-tooltipster' );
			wp_enqueue_style( $this->plugin_name . '-tooltipster-theme' );
		}

	}

	/**
	 * Register the JavaScript for the public-facing side of the site.
	 *
	 * @since    1.0.0
	 * @access   public
	 */
	public function register_scripts() {

		// Vue
		wp_register_script( 'vue', plugin_dir_url( __FILE__ ) . 'libs/vue/vue.min.js', array(), '', true );

		// Eventbrite checkout popup script
		wp_register_script( $this->plugin_name . '-checkout', 'https://www.eventbrite.com/static/widgets/eb_widgets.js', array( 'jquery' ), $this->version, true );

		// Tooltipster
		wp_register_script( $this->plugin_name . '-tooltipster', plugin_dir_url( __FILE__ ) . 'libs/tooltipster/tooltipster.bundle.min.js', array( 'jquery' ), $this->version, true );
		
		// Clipboard
		wp_register_script( $this->plugin_name . '-clipboard', plugin_dir_url( __FILE__ ) . 'libs/clipboard/clipboard.min.js', array( 'jquery' ), $this->version, true );

		// Vue app
		wp_register_script( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'assets/js/app.js', array( 'vue', $this->plugin_name . '-checkout' ), $this->version, true );

		// Send translations to js
		wp_localize_script( $this->plugin_name, 'EventFeedForEventbriteAppTranslations',
			array( 
				'back_button_text' => esc_html__( 'Back to event details', 'event-feed-for-eventbrite' ),
				'close_button_text' => esc_html__( 'Close', 'event-feed-for-eventbrite' ),
				'edit_link_text' => esc_html__( 'Edit this feed', 'event-feed-for-eventbrite' ),
				'loading_text' => esc_html__( 'Loading events...', 'event-feed-for-eventbrite' ),
				'no_events_text' => esc_html__( 'No upcoming events...', 'event-feed-for-eventbrite' ),
				'loading_error_text' => esc_html__( 'Error loading event feed...', 'event-feed-for-eventbrite' ),
				'api_error_text' => esc_html__( 'Please enter your Eventbrite API key in the plugin settings...', 'event-feed-for-eventbrite' ),
				'copyright_text' => esc_html__( 'Powered by', 'event-feed-for-eventbrite' ),
				'add_to_calendar_text' => esc_html__( 'Add to calendar', 'event-feed-for-eventbrite' ),
				'google_calendar_text' => esc_html__( 'Google Calendar', 'event-feed-for-eventbrite' ),
				'outlook_calendar_text' => esc_html__( 'Outlook Calendar', 'event-feed-for-eventbrite' ),
				'office365_calendar_text' => esc_html__( 'Office365 Calendar', 'event-feed-for-eventbrite' ),
				'yahoo_calendar_text' => esc_html__( 'Yahoo Calendar', 'event-feed-for-eventbrite' ),
				'ics_calendar_text' => esc_html__( 'Download ICS file', 'event-feed-for-eventbrite' ),
				'modal_error_text' => esc_html__( 'We are sorry, we were unable to load the full description from Eventbrite.', 'event-feed-for-eventbrite' ),
				'modal_error_link' => esc_html__( 'Click here to view event on Eventbrite', 'event-feed-for-eventbrite' ),
				'organizer_link' => esc_html__( 'Website', 'event-feed-for-eventbrite' ),
				'organizer_title' => esc_html__( 'Event Organizer', 'event-feed-for-eventbrite' ),
				'eventbrite_link' => esc_html__( 'View on', 'event-feed-for-eventbrite' )
			)
		);

		// Send licence information to Vue
		wp_localize_script( $this->plugin_name, 'EventFeedForEventbrite', [
			'premium' => json_encode( effe_freemius()->can_use_premium_code() ),
			'free' => json_encode( effe_freemius()->is_free_plan() ),
			'admin' => json_encode( current_user_can( 'edit_posts' ) ),
			'admin_url' => esc_url( admin_url() )
		] );

		// Public script
		wp_register_script( $this->plugin_name . '-preview', plugin_dir_url( __FILE__ ) . 'assets/js/preview.js', array( 'jquery', $this->plugin_name . '-tooltipster', $this->plugin_name . '-clipboard' ), $this->version, true );

		// Send translations to js
		wp_localize_script( $this->plugin_name . '-preview', 'EventFeedForEventbritePreviewTranslations',
			array( 
				'copy_text' => esc_html__( 'Copy', 'event-feed-for-eventbrite' ),
				'copied_text' => esc_html__( 'Copied', 'event-feed-for-eventbrite' )
			)
		);

	}

	/**
	 * Enqueue the JavaScript for the public-facing side of the site.
	 *
	 * @since    1.0.0
	 * @access   public
	 */
	public function enqueue_scripts() {

		// Load only on event feed preview page
		if ( is_singular( 'event_feed' ) ) {

			// Public script
			wp_enqueue_script( $this->plugin_name . '-preview' );

		}

	}

	/**
	 * Add icon to delete cache button in admin bar.
	 *
	 * @since    1.0.0
	 * @access   public
	 */
	public function event_feed_delete_cache_css() {
		echo '<style type="text/css">';
			echo '#wpadminbar #wp-admin-bar-event-feed-for-eventbrite-purge-cache-btn .ab-icon:before { content: "\f463"; top: 2px; }';
		echo '</style>';
	}

	/**
	 * Get event feed information
	 *
	 * @since    1.0.0
	 * @access   public
	 */
	public function event_feed_rest_data( $request ) {

		// Create query object and format events data
		$query_obj = new Eventbrite_Query( $request['id'] );
		$query = (object) $query_obj->get_feed_events();
		$events = $query->events;
		
		// Get feed ID
		$feed_id = $request['id'];

		// WPML - get ID of the translated feed instead
		if( function_exists( 'icl_object_id' ) ) {
			$feed_id = icl_object_id( $request['id'], 'event_feed', true );
		}

		// Create event feed object
		$feed_obj = new Event_Feed_For_Eventbrite_Feed( $feed_id );

		// Format feed data
		$feed = (object) $feed_obj->get_event_feed_data();

		// Format values for js output
		foreach( $events as $event ) {
			
			// Get settings
			$formats_settings = get_option( $this->plugin_name . '-formats' );

			// Time format
			$time_format = ( is_array( $formats_settings ) && array_key_exists( 'time_format', $formats_settings ) ) ? $formats_settings['time_format'] : false;
			if( $time_format == true ) {
				$vue_time_format = 'G:i';
			} else {
				$vue_time_format = 'g:i a';
			}

			// Timezone settings
			$eventbrite_timezone = ( is_array( $formats_settings ) && array_key_exists( 'eventbrite_timezone', $formats_settings ) ) ? $formats_settings['eventbrite_timezone'] : false;

			// Start date
			$event->vue = new \stdClass();
			$fulldate_format = isset( $formats_settings['date_format_custom'] ) ? sanitize_text_field( $formats_settings['date_format_custom'] ) : 'F j, Y';
			if( $eventbrite_timezone == true ) {
				$date_start_local = date_create( (string) $event->start->local, new \DateTimeZone( 'UTC' ) );
				$event_start_daycheck = $date_start_local->format( 'd m Y' );
				$event->vue->start = sanitize_text_field( date_i18n( $fulldate_format . ' @ ' . $vue_time_format, $date_start_local->getTimestamp() ) );
				$event->vue->start_full = $event->vue->start;
				$event->vue->start_weekday = sanitize_text_field( date_i18n( 'D', $date_start_local->getTimestamp() ) );
				$event->vue->start_month = sanitize_text_field( date_i18n( 'M', $date_start_local->getTimestamp() ) );
				$event->vue->start_day = sanitize_text_field( date_i18n( 'j', $date_start_local->getTimestamp() ) );
			} else {
				$event_start_daycheck = get_date_from_gmt( (string) $event->start->utc, 'd m Y' );
				$event->vue->start = sanitize_text_field( wp_date( $fulldate_format . ' @ ' . $vue_time_format, get_date_from_gmt( (string) $event->start->utc, 'U' ) ) );
				$event->vue->start_full = $event->vue->start;
				$event->vue->start_weekday = sanitize_text_field( wp_date( 'D', get_date_from_gmt( (string) $event->start->utc, 'U' ) ) );
				$event->vue->start_month = sanitize_text_field( wp_date( 'M', get_date_from_gmt( (string) $event->start->utc, 'U' ) ) );
				$event->vue->start_day = sanitize_text_field( wp_date( 'j', get_date_from_gmt( (string) $event->start->utc, 'U' ) ) );
			}

			// End date
			if( $eventbrite_timezone == true ) {
				$date_end_local = date_create( (string) $event->end->local, new \DateTimeZone( 'UTC' ) );
				$event_end_daycheck = $date_end_local->format( 'd m Y' );
				if( $event_start_daycheck == $event_end_daycheck ) {
					$event->vue->end = sanitize_text_field( date_i18n( $vue_time_format, $date_end_local->getTimestamp() ) );
					$event->vue->end_full = $event->vue->end;
				} else {
					$event->vue->end = sanitize_text_field( date_i18n( $fulldate_format . ' @ ' . $vue_time_format, $date_end_local->getTimestamp() ) );
					$event->vue->end_full = $event->vue->end;
				}
			} else {
				$event_end_daycheck = get_date_from_gmt( (string) $event->end->utc, 'd m Y' );
				if( $event_start_daycheck == $event_end_daycheck ) {
					$event->vue->end = sanitize_text_field( wp_date( $vue_time_format, get_date_from_gmt( (string) $event->end->utc, 'U' ) ) );
					$event->vue->end_full = $event->vue->end;
				} else {
					$event->vue->end = sanitize_text_field( wp_date( $fulldate_format . ' @ ' . $vue_time_format, get_date_from_gmt( (string) $event->end->utc, 'U' ) ) );
					$event->vue->end_full = $event->vue->end;
				}
			}

			// Image
			if( $event->logo_url ) {
				$event->vue->image = wp_strip_all_tags( esc_url_raw( $event->logo_url ) );
				$event->vue->has_image = true;
			} else {
				$event->vue->image = wp_strip_all_tags( esc_url_raw( $this->get_placeholder_image_uri() ) );
				$event->vue->has_image = false;
			}

			// Large image
			$event->vue->image_large = isset( $event->logo->original->url ) ? wp_strip_all_tags( esc_url_raw( $event->logo->original->url ) ) : false; 

			// Price
			$event->vue->price = sanitize_text_field( $this->get_event_price( $event ) );

			// Location
			$address_format = isset( $formats_settings['address_format'] ) ? sanitize_text_field( $formats_settings['address_format'] ) : '[localized_address_display]';
			
			$venue_name = isset( $event->venue->name ) ? sanitize_text_field( $event->venue->name ) : false;
			$venue_address = isset( $event->venue->address ) ? $event->venue->address : false;

			if( $venue_address ) {

				$localized_address_display 	= sanitize_text_field( (string) $event->venue->address->localized_address_display ); 
				$localized_area_display 	= sanitize_text_field( (string) $event->venue->address->localized_area_display );
				$address_1 					= sanitize_text_field( (string) $event->venue->address->address_1 );
				$address_2 					= sanitize_text_field( (string) $event->venue->address->address_2 );
				$city 						= sanitize_text_field( (string) $event->venue->address->city );
				$region 					= sanitize_text_field( (string) $event->venue->address->region );
				$postal_code 				= sanitize_text_field( (string) $event->venue->address->postal_code );
				$country 					= sanitize_text_field( (string) $event->venue->address->country );
				$latitude 					= sanitize_text_field( (string) $event->venue->address->latitude );
				$longitude 					= sanitize_text_field( (string) $event->venue->address->longitude );

				// Format address according to user's setting
				( $venue_name ) ? $address_format = str_replace( '[venue_name]', $venue_name, $address_format ) : $address_format = str_replace( '[venue_name]', '', $address_format );
				( $localized_address_display ) ? $address_format = str_replace( '[localized_address_display]', $localized_address_display, $address_format ) : $address_format = str_replace( '[localized_address_display]', '', $address_format );
				( $localized_area_display ) ? $address_format = str_replace( '[localized_area_display]', $localized_area_display, $address_format ) : $address_format = str_replace( '[localized_area_display]', '', $address_format );
				( $address_1 ) ? $address_format = str_replace( '[address_1]', $address_1, $address_format ) : $address_format = str_replace( '[address_1]', '', $address_format );
				( $address_2 ) ? $address_format = str_replace( '[address_2]', $address_2, $address_format ) : $address_format = str_replace( '[address_2]', '', $address_format );
				( $city ) ? $address_format = str_replace( '[city]', $city, $address_format ) : $address_format = str_replace( '[city]', '', $address_format );
				( $region ) ? $address_format = str_replace( '[region]', $region, $address_format ) : $address_format = str_replace( '[region]', '', $address_format );
				( $postal_code ) ? $address_format = str_replace( '[postal_code]', $postal_code, $address_format ) : $address_format = str_replace( '[postal_code]', '', $address_format );
				( $country ) ? $address_format = str_replace( '[country]', $country, $address_format ) : $address_format = str_replace( '[country]', '', $address_format );
				( $latitude ) ? $address_format = str_replace( '[latitude]', $latitude, $address_format ) : $address_format = str_replace( '[latitude]', '', $address_format );
				( $longitude ) ? $address_format = str_replace( '[longitude]', $longitude, $address_format ) : $address_format = str_replace( '[longitude]', '', $address_format );

				$event->vue->location = sanitize_text_field( (string) $address_format );

			} elseif( $event->online_event == true ) {
				$event->vue->location = esc_html__( 'Online', 'event-feed-for-eventbrite' );
			} else { 
				$event->vue->location = esc_html__( 'To be announced', 'event-feed-for-eventbrite' ); 
			}

			// Shorten the title if defined in the feed settings
			if( $event->post_title ) {
				
				if( $feed->title_length_full == false ) {
					$title_length = intval( (int) $feed->title_length );
					$event->vue->title = sanitize_text_field( str_replace(' ..', '..', mb_strimwidth( $event->post_title, 0, $title_length + 2, '..' ) ) );
				} else {
					$event->vue->title = sanitize_text_field( $event->post_title );
				}

			}

			// Shorten the description
			if( $event->post_content ) {

				if( $feed->excerpt_length_full == false ) {
					$excerpt_length = intval( (int) $feed->excerpt_length );
					$event->vue->description = sanitize_textarea_field( str_replace(' ..', '..', mb_strimwidth( $event->post_content, 0, $excerpt_length + 2, '..' ) ) );
				} else {
					$event->vue->description = sanitize_textarea_field( $event->post_content );
				}
				
			}

			// Convert to object
			$event->vue = (object) $event->vue;

		}

		// Put all data together
		$rest_data = [];
		$rest_data['events'] = $events;

		return (object) $rest_data;
		
	}

	/**
	 * Get HQ event image
	 *
	 * @since    1.0.0
	 * @access   public
	 */
	public function event_image_rest_data( $request ) {
		$img = eventbrite()->get_event_original_image( $request['id'], array( 'width' => '800', 'height' => '400' ) );
		$rest_data = [];
		$rest_data['url'] = $img;

		return (object) $rest_data;
	}

	/**
	 * Custom endpoint for accessing event feed information
	 *
	 * @since    1.0.0
	 * @access   public
	 */
	public function register_event_feed_rest_endpoint() {
		register_rest_route(
			'event-feed-for-eventbrite/v1', '/feed/(?P<id>\d+)', array(
				'methods'       		=> 'GET',
				'callback'      		=> array( $this, 'event_feed_rest_data' ),
				'permission_callback' 	=> '__return_true',
				'show_in_index' 		=> false
			)
		);
	}

	/**
	 * Custom endpoint for getting HQ event image
	 *
	 * @since    1.0.0
	 * @access   public
	 */
	public function register_event_image_rest_endpoint() {
		register_rest_route(
			'event-feed-for-eventbrite/v1', '/image/(?P<id>\d+)', array(
				'methods'       		=> 'GET',
				'callback'      		=> array( $this, 'event_image_rest_data' ),
				'permission_callback' 	=> '__return_true',
				'show_in_index' 		=> false
			)
		);
	}

	/**
	 * Add Vue modal container on the end of body
	 *
	 * @since    1.0.0
	 * @access   public
	 */
	public function add_vue_modal_container() {
		echo '<div id="eventbrite-modal-container"></div>';
	}

	/**
	 * Event feed shortcode 
	 *
	 * @since    1.0.0
	 * @access   public
	 */
	public function event_feed_shortcode( $atts ) {

		// Block rendering if Gutenberg
		if( defined('REST_REQUEST') ) {
			return;
		} 

		if( $atts['id'] !== '' ) {

			// Define all shortcode attributes
			$atts = shortcode_atts( 
				array(
					'id' => '',
					'uid' => uniqid()
				),
				$atts,
				'event-feed'
			);

			$shortcode_id = intval( $atts['id'] );
			$shortcode_uid = sanitize_text_field( $atts['uid'] );

			// Check if there is published post and if it's a feed post type
			if( ( 'publish' == get_post_status ( $shortcode_id ) ) && ( 'event_feed' == get_post_type( $shortcode_id ) ) ) {

				// Get feed ID
				$feed_id = $shortcode_id;

				// WPML - get ID of the translated feed instead
				if( function_exists( 'icl_object_id' ) ) {
					$feed_id = icl_object_id( $shortcode_id, 'event_feed', true );
				}

				// Create event feed object
				$feed_obj = new Event_Feed_For_Eventbrite_Feed( $feed_id );
				$feed = (object) $feed_obj->get_event_feed_data();

				// Get Settings
				$general_settings = get_option( $this->plugin_name ); 
				$appearance_settings = get_option( $this->plugin_name . '-appearance' );
				$format_settings = get_option( $this->plugin_name . '-formats' );
				$advanced_settings = get_option( $this->plugin_name . '-advanced' );
				$settings = [
					'api_key' 				=> ! empty( $general_settings['api_key'] )			 		? true 																		: false,
					'google_fonts' 			=> isset( $appearance_settings['google_fonts'] ) 			? boolval( $appearance_settings['google_fonts'] ) 							: false,
					'google_fonts_family' 	=> isset( $appearance_settings['google_fonts_family'] ) 	? sanitize_text_field( $appearance_settings['google_fonts_family'] ) 		: 'Poppins',
					'placeholder_id' 		=> isset( $appearance_settings['placeholder_id'] ) 			? intval( $appearance_settings['placeholder_id'] ) 							: false,
					'show_copyright' 		=> isset( $advanced_settings['show_copyright'] ) 			? boolval( $advanced_settings['show_copyright'] ) 							: false,
					'shortdate_months' 		=> isset( $format_settings['shortdate_months'] ) 			? boolval( $format_settings['shortdate_months'] ) 					    	: false,
				];
				$theme_color = $this->convert_hex_to_hsl( $feed->theme_color );
				$theme_color_h = intval( $theme_color['hue'] );
				$theme_color_s = intval( $theme_color['saturation'] );
				$theme_color_l = intval( $theme_color['lightness'] );
				$theme_color_l_dark = ( intval( $theme_color['lightness'] - 4 ) );

				// Displayed events settings for different breakpoints
				$events_limit_no = intval( $feed->events_limit_no );
				$events_limit_no_large_tablet = intval( $feed->events_limit_no_large_tablet );
				$events_limit_no_small_tablet = intval( $feed->events_limit_no_small_tablet );
				$events_limit_no_mobile = intval( $feed->events_limit_no_mobile );
				
				$events_limit = intval( $feed->events_limit + 1 );
				$events_limit_large_tablet = intval( $feed->events_limit_large_tablet + 1 );
				$events_limit_small_tablet = intval( $feed->events_limit_small_tablet + 1 );
				$events_limit_mobile = intval( $feed->events_limit_mobile + 1 );

				// Font
				$google_font = boolval( $settings['google_fonts'] );
				$google_font_family = esc_html( $settings['google_fonts_family'] );
				if( $google_font == true ) {
					$font_line = '--event-feed-for-eventbrite-font-family: ' . $google_font_family . ';';
					wp_enqueue_style( $this->plugin_name . '-font', "https://fonts.googleapis.com/css2?family={$google_font_family}:wght@400;500;600;700&display=swap", array(), $this->version, 'all' );
				} else {
					$font_line = '';
				}

				// Add dynamic styles to stylesheet
				$feed_selector = '#event-feed-for-eventbrite-app-' . $shortcode_uid;
				$modal_selector = '.eventbrite-modal-' . $shortcode_uid;
				$modal_error_selector = '.eventbrite-modal-error-' . $shortcode_uid;
				$spinner_selector = '.eventbrite-modal-spinner-wrapper-' . $shortcode_uid;
				$custom_css = "
					{$feed_selector}, {$modal_selector}, {$modal_error_selector}, {$spinner_selector} {
						{$font_line}
						--event-feed-for-eventbrite-theme-color: {$feed->theme_color};
						--event-feed-for-eventbrite-theme-color-h: {$theme_color_h};
						--event-feed-for-eventbrite-theme-color-s: {$theme_color_s}%;
						--event-feed-for-eventbrite-theme-color-l: {$theme_color_l}%;
						--event-feed-for-eventbrite-theme-color-l-dark: {$theme_color_l_dark}%;
						--event-feed-for-eventbrite-rows-desktop: {$feed->rows};
						--event-feed-for-eventbrite-rows-large-tablets: {$feed->rows_large_tablet};
						--event-feed-for-eventbrite-rows-small-tablets: {$feed->rows_small_tablet};
						--event-feed-for-eventbrite-rows-mobile: {$feed->rows_mobile};
					}";
				if( $events_limit_no != true ) {
					$custom_css .= "
					@media (min-width: 1280px) {
						{$feed_selector} .eventbrite-feed .eventbrite-item:nth-of-type(1n+{$events_limit}) {
							display: none;
						}
					}";
				}
				if( $events_limit_no_large_tablet != true ) {
					$custom_css .= "
					@media (min-width: 1024px) and (max-width: 1279px) {
						{$feed_selector} .eventbrite-feed .eventbrite-item:nth-of-type(1n+{$events_limit_large_tablet}) {
							display: none;
						}
					}";
				}
				if( $events_limit_no_small_tablet != true ) {
					$custom_css .= "
					@media (min-width: 768px) and (max-width: 1023px) {
						{$feed_selector} .eventbrite-feed .eventbrite-item:nth-of-type(1n+{$events_limit_small_tablet}) {
							display: none;
						}
					}";
				}
				if( $events_limit_no_mobile != true ) {
					$custom_css .= "
					@media (max-width: 767px) {
						{$feed_selector} .eventbrite-feed .eventbrite-item:nth-of-type(1n+{$events_limit_mobile}) {
							display: none;
						}
					}";
				}
				wp_add_inline_style( $this->plugin_name, $custom_css );

				// Get API route
				$api = esc_url_raw( get_rest_url() );

				// Send feed-specific data to Vue
				wp_localize_script( $this->plugin_name, 'EventFeedForEventbrite' . $shortcode_uid, [
					'uid' => $shortcode_uid,
					'feed' => $feed,
					'settings' => $settings,
					'api' => $api
				] );
				
				// Enqueue Vue app
				wp_enqueue_script( $this->plugin_name );

				// Enqueue main style
				wp_enqueue_style( $this->plugin_name );

				// Output template
				ob_start();

				// Update $atts to use sanitized values
				$atts['uid'] = $shortcode_uid;
				$atts['id'] = $shortcode_id;
				
				include( 'partials/layouts/vue-wrapper.php' );
				return ob_get_clean();

			}
			
		}

	}

	/**
	 * Load template for single event feed template
	 *
	 * @since    1.0.0
	 * @access   public
	 */
	public function single_event_feed_template( $template ) {
		
		$file_name = 'single-event_feed.php';
		if ( is_singular( 'event_feed' ) ) {
			$template = dirname( __FILE__ ) . '/templates/' . $file_name;
		}
	
		return $template;
	}

	/**
	 * Get placeholder image
	 *
	 * @since    1.0.0
	 * @access   public
	 */
	public function get_placeholder_image_uri() {
		$appearance_settings = get_option( $this->plugin_name . '-appearance' );
		$placeholder_id = isset( $appearance_settings['placeholder_id'] ) ? intval( $appearance_settings['placeholder_id'] ) : false;

		// Get user-defined placeholder from the feed settings
		if( wp_get_attachment_image_url( $placeholder_id, 'event-feed-for-eventbrite-thumbnail' ) !== false ) {
			return wp_get_attachment_image_url( $placeholder_id, 'event-feed-for-eventbrite-thumbnail' );

		// If no placeholder is defined, use default option
		} elseif (file_exists(__DIR__ . '/assets/img/placeholder.png')) {
			return plugin_dir_url(__FILE__) . 'assets/img/placeholder.png';
		}

	}

	/**
	 * Get event price or ticket sale status
	 *
	 * @since    1.0.0
	 * @access   public
	 */
	public function get_event_price( $event ) {

		// Sales ended
		if( $event->event_sales_status->sales_status == 'sales_ended' ) {
			return esc_html__( 'Sales Ended', 'event-feed-for-eventbrite' );

		// Sold out
		} elseif( $event->event_sales_status->sales_status == 'sold_out' ) {
			return esc_html__( 'Sold Out', 'event-feed-for-eventbrite' );

		// Not yet on sale
		} elseif( $event->event_sales_status->sales_status == 'not_yet_on_sale' ) {
			return esc_html__( 'Not yet on sale', 'event-feed-for-eventbrite' );

		// Unavailable
		} elseif( $event->event_sales_status->sales_status == 'unavailable' ) {
			return esc_html__( 'Unavailable', 'event-feed-for-eventbrite' );

		// On sale
		} elseif( $event->event_sales_status->sales_status == 'on_sale' ) {
			return $this->get_event_price_range( $event );
		}

	}

	/**
	 * Get ticket price range and format according to different currencies 
	 *
	 * @since    1.0.0
	 * @access   private
	 */
	private function get_event_price_range( $event ) {

		if( $event->ticket_availability->has_available_tickets === true ) {

			// Get tickets data from API
			$tickets = array();
			$currency_code = isset( $event->ticket_availability->minimum_ticket_price->currency ) ? sanitize_text_field( $event->ticket_availability->minimum_ticket_price->currency ) : false;

			// Define if the currency symbol should be prepended or appended to the ticket value
			$array_after = array( 'CZK', 'HUF', 'PLN', 'SEK', 'THB' );
			$currency_before = true;
			if( in_array( $currency_code, $array_after ) ) { 
				$currency_before = false;
			}

			// Prepare tickets array
			foreach( $event->tickets as $key => $ticket ) {

				// Paid ticket
				if( $ticket->cost ) {
					$tickets[$key]['value'] = intval( $ticket->cost->value );
					$tickets[$key]['major_value'] = sanitize_text_field( $ticket->cost->major_value );
					$tickets[$key]['fee'] = sanitize_text_field( $ticket->fee->major_value );
					$tickets[$key]['tax'] = sanitize_text_field( $ticket->tax->major_value );
					$tickets[$key]['total_value'] = $tickets[$key]['major_value'] + $tickets[$key]['fee'] + $tickets[$key]['tax'];
					$tickets[$key]['display'] = sanitize_text_field( $ticket->cost->display );

				// Free ticket
				} else {
					$tickets[$key]['value'] = 0;
					$tickets[$key]['major_value'] = 0.00;
					$tickets[$key]['fee'] = 0.00;
					$tickets[$key]['tax'] = 0.00;
					$tickets[$key]['total_value'] = 0.00;
					$tickets[$key]['display'] = esc_html__( 'Free', 'event-feed-for-eventbrite' );
				}

				// Get currency symbol, if not available, get symbol from API
				$tickets[$key]['symbol'] = ( $this->get_currency_symbol( $currency_code ) ) ? sanitize_text_field( $this->get_currency_symbol( $currency_code ) ) : sanitize_text_field( str_replace( $tickets[$key]['major_value'], '', $tickets[$key]['display'] ) );
				$tickets[$key]['free'] = boolval( $ticket->free );
				$tickets[$key]['donation'] = boolval( $ticket->donation );

			}

			// Sort array by ticket cost (ascending order)
			usort( $tickets, function( $a, $b ) {
				return $a['value'] - $b['value'];
			} );

			// Find corresponding ticket objects for each value
			$min_ticket = $tickets[0];
			$max_ticket = end( $tickets );

			// Output ticket cost/cost range in the right format
			$ticket_range = '';

			// Output minimal value
			if( $min_ticket['free'] === true ) {
				$ticket_range = esc_html__( 'Free', 'event-feed-for-eventbrite' );
			} elseif( $min_ticket['donation'] === true ) {
				$ticket_range = esc_html__( 'Donation', 'event-feed-for-eventbrite' );
			} elseif( $currency_before === true ) {
				$min_ticket_format = number_format( (float) $min_ticket['total_value'], 2, '.', '' );
				$min_ticket_format = str_replace('.00', '', $min_ticket_format);
				$ticket_range = '<span class="currency">' . esc_html( $max_ticket['symbol'] ) . '</span>' . esc_html( $min_ticket_format );
			} else {
				$min_ticket_format = number_format( (float) $min_ticket['total_value'], 2, '.', '' );
				$min_ticket_format = str_replace('.00', '', $min_ticket_format);
				$ticket_range = esc_html( $min_ticket_format ) . ' ' . '<span class="currency">' . esc_html( $max_ticket['symbol'] ) . '</span>';
			} 

			// If minimal and maximal value is not the same, display ticket cost range
			if( $min_ticket['value'] !== $max_ticket['value'] ) {
				if( $max_ticket['free'] === true || $max_ticket['donation'] === true ) {
					$ticket_range .= '';
				} elseif( $currency_before === true ) {
					$max_ticket_format = number_format( (float) $max_ticket['total_value'], 2, '.', '' );
					$max_ticket_format = str_replace('.00', '', $max_ticket_format);
					$ticket_range .= ' - ' . '<span class="currency">' . esc_html( $max_ticket['symbol'] ) . '</span>' . esc_html( $max_ticket_format );
				} else {
					$max_ticket_format = number_format( (float) $max_ticket['total_value'], 2, '.', '' );
					$max_ticket_format = str_replace('.00', '', $max_ticket_format);
					$ticket_range .= ' - ' . esc_html( $max_ticket_format ) . ' ' . '<span class="currency">' . esc_html( $max_ticket['symbol'] ) . '</span>';
				}
			}

			return $ticket_range;

		}

	}

	/**
	 * Get currency symbol from ISO 4217 code
	 *
	 * @since    1.0.0
	 * @access   public
	 * @link	 https://fastspring.com/blog/how-to-format-30-currencies-from-countries-all-over-the-world/
	 */
	public function get_currency_symbol( $currency_code ) {
		switch( $currency_code ) {
			case 'ARS': return '$'; break;
			case 'AUD': return '$'; break;
			case 'BRL': return 'R$'; break;
			case 'CAD': return '$'; break;
			case 'CHF': return 'fr.'; break;
			case 'CZK': return 'Kč'; break;
			case 'DKK': return 'kr.'; break;
			case 'EUR': return '€'; break;
			case 'GBP': return '£'; break;
			case 'HKD': return 'HK$'; break;
			case 'HUF': return 'Ft'; break;
			case 'ILS': return '₪'; break;
			case 'JPY': return '¥'; break;
			case 'MXN': return '$'; break;
			case 'MYR': return 'RM'; break;
			case 'NOK': return 'kr'; break;
			case 'NZD': return 'NZ$'; break;
			case 'PHP': return '₱'; break;
			case 'PLN': return 'zł'; break;
			case 'SEK': return 'kr'; break;
			case 'SGD': return '$'; break;
			case 'THB': return '฿'; break;
			case 'TWD': return '元'; break;
			case 'USD': return '$'; break;
		}
	}

	/**
	 * Convert HEX color to HSL format
	 *
	 * @since    1.0.0
	 * @access   public
	 * @link 	 https://board.phpbuilder.com/d/10402773-php-function-to-convert-hex-to-hsl-not-hsl-to-hex/
	 */
	public function convert_hex_to_hsl( $hex ) {

		$hex = ltrim( $hex, '#' );

		$red = hexdec( substr( $hex, 0, 2) ) / 255;
		$green = hexdec( substr( $hex, 2, 2) ) / 255;
		$blue = hexdec( substr( $hex, 4, 2) ) / 255;

		$cmin = min( $red, $green, $blue );
		$cmax = max( $red, $green, $blue );
		$delta = $cmax - $cmin;

		if ( $delta == 0 ) {
			$hue = 0;
		} elseif ( $cmax === $red ) {
			$hue = ( ( $green - $blue ) / $delta );
		} elseif ( $cmax === $green ) {
			$hue = ( $blue - $red ) / $delta + 2;
		} else {
			$hue = ( $red - $green ) / $delta + 4;
		}

		$hue = round( $hue * 60 );
		if ($hue < 0) {
			$hue += 360;
		}

		$lightness = ( ( $cmax + $cmin ) / 2 );
		$saturation = $delta === 0 ? 0 : ( $delta / ( 1 - abs( 2 * $lightness - 1 ) ) );
		if ( $saturation < 0 ) {
			$saturation += 1;
		}

		$lightness = round( $lightness * 100 );
		$saturation = round( $saturation * 100 );

		return array( 
			'hue' 			=> $hue,
			'saturation' 	=> $saturation,
			'lightness' 	=> $lightness
		);

	}

	/**
	 * Excludes scripts from minification and combination in Siteground Optimizer.
	 *
	 * @since    1.2.1
	 * @access   public
	 */
	function siteground_optimizer_js_exclude( $exclude_list ) {
		$exclude_list[] = 'vue';
		$exclude_list[] = 'event-feed-for-eventbrite-checkout';
		$exclude_list[] = 'event-feed-for-eventbrite-tooltipster';
		$exclude_list[] = 'event-feed-for-eventbrite-clipboard';
		$exclude_list[] = 'event-feed-for-eventbrite';
		return $exclude_list;
	}

	/**
	 * Excludes external scripts from combination in Siteground Optimizer.
	 *
	 * @since    1.3.0
	 * @access   public
	 */
	function siteground_optimizer_external_js_exclude( $exclude_list ) {
		$exclude_list[] = 'eventbrite.com';
		return $exclude_list;
	}

	/**
	 * Excludes scripts from minification, combination and deferring in WP Rocket.
	 *
	 * @since    1.3.0
	 * @access   public
	 */
	function wp_rocket_exclude_js( $exclude_list ) {
		if ( ! is_array( $exclude_list ) ) {
			$exclude_list = array();
		}

		$exclude_list[] = 'vue';
		$exclude_list[] = 'event-feed-for-eventbrite-checkout';
		$exclude_list[] = 'event-feed-for-eventbrite-tooltipster';
		$exclude_list[] = 'event-feed-for-eventbrite-clipboard';
		$exclude_list[] = 'event-feed-for-eventbrite';
		return $exclude_list;
	}

}