[ Avaa Bypassed ]




Upload:

Command:

www-data@13.59.0.231: ~ $
<?php

use Tribe__Date_Utils as Dates;
use Tribe__Utils__Array as Arr;

/**
 *  Class that implements the export to iCal functionality
 *  both for list and single events
 */
class Tribe__Events__iCal {

	/**
	 * @var int The number of events that will be exported when generating the iCal feed.
	 */
	protected $feed_default_export_count = 30;

	/**
	 * The $post where the *.ics file is generated
	 *
	 * @since 4.9.4
	 *
	 * @var null
	 */
	protected $post = null;

	/**
	 * An array with all the events that are part of the *.ics file
	 *
	 * @since 4.9.4
	 *
	 * @var array
	 */
	protected $events = [];

	/**
	 * The type of iCal Feed ( ical|outlook)
	 *
	 * @since 5.16.0
	 *
	 * @var string
	 */
	protected $type = 'ical';

	/**
	 * Set all the filters and actions necessary for the operation of the iCal generator.
	 */
	public function hook() {
		add_action( 'tribe_events_after_footer', [ $this, 'maybe_add_link' ], 10, 1 );
		add_action( 'tribe_events_single_event_after_the_content', [ $this, 'single_event_links' ] );
		add_action( 'template_redirect', [ $this, 'do_ical_template' ] );
		add_filter( 'tribe_get_ical_link', [ $this, 'day_view_ical_link' ], 20, 1 );
		add_action( 'wp_head', [ $this, 'set_feed_link' ], 2, 0 );
	}

	/**
	 * outputs a <link> element for the ical feed
	 */
	public function set_feed_link() {
		if ( ! current_theme_supports( 'automatic-feed-links' ) ) {
			return;
		}
		$separator  = _x( '&raquo;', 'feed link', 'the-events-calendar' );
		$feed_title = sprintf( esc_html__( '%1$s %2$s iCal Feed', 'the-events-calendar' ), get_bloginfo( 'name' ), $separator );

		printf(
			'<link rel="alternate" type="text/calendar" title="%s" href="%s" />',
			esc_attr( $feed_title ),
			esc_url( tribe_get_ical_link() )
		);

		echo "\n";
	}

	/**
	 * Returns the url for the iCal generator for lists of posts.
	 *
	 * @param string $type The type of iCal link to return, defaults to 'home'.
	 *
	 * @return string
	 */
	public function get_ical_link( $type = 'home' ) {
		$tec = Tribe__Events__Main::instance();

		return add_query_arg( [ 'ical' => 1 ], $tec->getLink( $type ) );
	}

	/**
	 * Make sure when we grab a month link it includes the correct month.
	 *
	 * @param string $event_date Date of the month we are getting the link for.
	 *
	 * @return string The iCal export URL for the Month view.
	 */
	public function month_view_ical_link( $event_date = null ) {
		$tec = Tribe__Events__Main::instance();

		// Default to current month if not set.
		if ( empty( $event_date ) ) {
			$event_date = Dates::build_date_object()->format( Dates::DBYEARMONTHTIMEFORMAT );
		}

		$url = $tec->getLink( 'month', $event_date );

		return add_query_arg( [ 'ical' => 1 ], $url );
	}

	/**
	 * Make sure ical link has the date in the URL instead of "today" on day view
	 *
	 * @param $link
	 *
	 * @return string
	 */
	public function day_view_ical_link( $link ) {
		if ( tribe_is_day() ) {

			if ( ! $wp_query = tribe_get_global_query_object() ) {
				return;
			}

			$day  = $wp_query->get( 'start_date' );
			$link = trailingslashit( esc_url( trailingslashit( tribe_get_day_link( $day ) ) . '?ical=1' ) );
		}

		return $link;
	}

	/**
	 * Generates the markup for iCal and gCal single event links
	 **/
	public function single_event_links() {

		// don't show on password protected posts
		if ( is_single() && post_password_required() ) {
			return;
		}
		$calendar_links = '<div class="tribe-events-cal-links">';
		$calendar_links .= '<a class="tribe-events-gcal tribe-events-button" href="' . Tribe__Events__Main::instance()->esc_gcal_url( tribe_get_gcal_link() ) . '" target="_blank" rel="noopener noreferrer noindex" title="' . esc_attr__( 'Add to Google Calendar', 'the-events-calendar' ) . '">+ ' . esc_html__( 'Google Calendar', 'the-events-calendar' ) . '</a>';
		$calendar_links .= '<a class="tribe-events-ical tribe-events-button" href="' . esc_url( tribe_get_single_ical_link() ) . '" title="' . esc_attr__( 'Download .ics file', 'the-events-calendar' ) . '"  rel="noopener noreferrer noindex" >+ ' . esc_html__( 'Add to iCalendar', 'the-events-calendar' ) . '</a>';
		$calendar_links .= '</div><!-- .tribe-events-cal-links -->';

		/**
		 * Allow for complete customization of the iCal and gCal single-event links.
		 *
		 * @param string $calendar_links The HTML of the iCal and gCal single-event link buttons.
		 */
		echo apply_filters( 'tribe_events_ical_single_event_links', $calendar_links );
	}


	/**
	 * Generates the markup for the "iCal Import" link for the views.
	 */
	public function maybe_add_link() {

		if ( ! $wp_query = tribe_get_global_query_object() ) {
			return;
		}

		/**
		 * A filter to control whether the "iCal Import" link shows up or not.
		 *
		 * @param boolean $show Whether to show the "iCal Import" link; defaults to true.
		 */
		$show_ical = apply_filters( 'tribe_events_list_show_ical_link', true );

		if ( ! $show_ical ) {
			return;
		}

		if ( tribe_is_month() && ! tribe_events_month_has_events() ) {
			return;
		}

		if ( ! tribe_is_month() && ( is_single() || empty( $wp_query->posts ) ) ) {
			return;
		}

		$tec  = Tribe__Events__Main::instance();
		$view = $tec->displaying;

		if ( defined( 'DOING_AJAX' ) && DOING_AJAX && isset( $wp_query->query_vars['eventDisplay'] ) ) {
			$view = $wp_query->query_vars['eventDisplay'];
		}

		/**
		 * Allow for customization of the iCal export link "Export Events" text.
		 *
		 * @param string $text The default link text, which is "Export Events".
		 */
		$text  = apply_filters( 'tribe_events_ical_export_text', esc_html__( 'Export Events', 'the-events-calendar' ) );
		$title = esc_html__( 'Use this to share calendar data with Google Calendar, Apple iCal and other compatible apps', 'the-events-calendar' );

		printf(
			'<a class="tribe-events-ical tribe-events-button" rel="noindex nofollow" title="%1$s" href="%2$s">+ %3$s</a>',
			$title,
			esc_url( tribe_get_ical_link() ),
			$text
		);
	}

	/**
	 * Executes the iCal generator when the appropriate query_var or $_GET is setup.
	 */
	public function do_ical_template() {
		// hijack to iCal template if query string included.
		if (
			! get_query_var( 'ical' )
			&& ! isset( $_GET['ical'] )
			&& ! get_query_var( 'outlook-ical' )
			&& ! isset( $_GET['outlook-ical'] )
		) {
			return;
		}

		if (
			get_query_var( 'outlook-ical' )
			|| isset( $_GET['outlook-ical'] )
		) {
			$this->type = 'outlook';
		}

		/**
		 * Action fired before the creation of the feed is started, helpful to set up methods and other filters used
		 * on this class.
		 *
		 * @since 4.6.11
		 */
		do_action( 'tribe_events_ical_before' );

		if ( ! $wp_query = tribe_get_global_query_object() ) {
			return;
		}

		$event_ids = tribe_get_request_var( 'event_ids', false );

		/**
		 * Allows filtering the event IDs after the `Tribe__Events__ICal` class
		 * tried to fetch them from the current request.
		 *
		 * @since 4.6.0
		 *
		 * @param array<int>|false Either a list of requested event post IDs or `false`
		 *                         if the current request does not specify the event post
		 *                         IDs to fetch.
		 */
		$event_ids = apply_filters( 'tribe_ical_template_event_ids', $event_ids );

		if ( false !== $event_ids ) {
			$event_ids = Arr::list_to_array( $event_ids );

			// Exit or the feed will still generate.
			if ( empty( $event_ids ) ) {
				die();
			}

			$events = array_map( 'tribe_get_event', $event_ids );
			$this->generate_ical_feed( $events );
		} elseif ( is_singular( Tribe__Events__Main::POSTTYPE ) ) {
			$this->generate_ical_feed( $wp_query->post );
		} else {
			$this->generate_ical_feed();
		}
		die();
	}

	/**
	 * Checks access to an event's content, in the context for generating an iCal file.
	 *
	 * @since 6.1.3
	 *
	 * @param numeric|WP_Post $post The post to evaluate our access to.
	 *
	 * @return bool
	 */
	public static function has_access_to_see_event_content( $post ): bool {
		// Can we see it at all?
		if ( ! self::has_access_to_see_event_exists( $post ) ) {
			return false;
		}

		// If password required, we hide the content from the feed.
		return ! post_password_required( $post );
	}

	/**
	 * Checks access to an event, in the context for generating an iCal file.
	 *
	 * @since 6.1.3
	 *
	 * @param numeric|WP_Post $post The post to evaluate our access to.
	 *
	 * @return bool
	 */
	public static function has_access_to_see_event_exists( $post ): bool {
		$post = get_post( $post );
		if ( ! $post instanceof WP_Post ) {
			return false;
		}
		// Most events.
		if ( $post->post_status === 'publish' ) {
			return true;
		}
		// If private, make sure they have access (and are logged in).
		if ( $post->post_status === 'private' && current_user_can( 'read_post', $post->ID ) ) {
			return true;
		}

		// Fallback to denied access unless explicitly approved above.
		return false;
	}

	/**
	 * Generates the iCal file
	 *
	 * @since 6.1.3 Adding access checks to the provided posts.
	 *
	 * @param int|null|array $post If you want the ical file for a single event
	 * @param boolean  $echo Whether the content should be echoed or returned
	 *
	 * @return string
	 */
	public function generate_ical_feed( $post = null, $echo = true ) {
		// If we are searching via a single numeric/post, turn into an array.
		if ( ! empty( $post ) && ! is_array( $post ) ) {
			$post = [ $post ];
		}

		// Gatekeep any externally handed events through permissions.
		if ( is_array( $post ) ) {
			$post = array_filter( $post, static function ( $event_id ) {
				return self::has_access_to_see_event_exists( get_post( $event_id ) );
			} );
			if ( empty( $post ) ) {
				if ( $echo ) {
					die();
				} else {
					return '';
				}
			}
			$post = array_map( 'get_post', $post );
		}

		// Now setup to do our search.
		$this->post   = $post;
		$this->events = $this->get_event_posts();

		$content = $this->get_content();

		if ( $echo ) {
			$this->set_headers();
			tribe_exit( $content );
		}

		return $content;
	}

	/**
	 * Get an array with all the Events to be used to process the *.ics file
	 *
	 * @since 4.9.4
	 *
	 * @return array|null
	 */
	protected function get_event_posts() {
		if ( $this->post ) {
			return is_array( $this->post ) ? $this->post : [ $this->post ];
		}

		if ( tribe_is_month() ) {
			return $this->get_month_view_events();
		}

		$list_view_slug = \Tribe\Events\Views\V2\Views\List_View::get_view_slug();

		if ( tribe_is_organizer() ) {
			return $this->get_events_list(
				[
					'organizer'    => get_the_ID(),
					'eventDisplay' => $list_view_slug,
				]
			);
		}

		if ( tribe_is_venue() ) {
			return $this->get_events_list(
				[
					'venue'        => get_the_ID(),
					'eventDisplay' => tribe_get_request_var( 'tribe_event_display', $list_view_slug ),
				]
			);
		}

		if ( ! $wp_query = tribe_get_global_query_object() ) {
			return [];
		}

		$args = $wp_query->query_vars;

		if ( $list_view_slug === $args['eventDisplay'] ) {
			// Whe producing a List view iCal feed the `eventDate` is misleading.
			unset( $args['eventDate'] );

			// If passed a date, only observe it if it's in the future.
			if ( isset( $args['tribe-bar-date'] ) ) {
				$set_date = Dates::build_date_object( $args['tribe-bar-date'] );
				if ( $set_date < Dates::build_date_object() ) {
					unset( $args['tribe-bar-date'] );
				}
			}
		}

		return $this->get_events_list( $args, $wp_query );
	}

	/**
	 * Gets all events in the current month, matching those presented in month view
	 * by default (and therefore potentially including some events from the tail end
	 * of the previous month and start of the following month).
	 *
	 * We build a fresh 'custom'-type query here rather than taking advantage of the
	 * main query since page spoofing can render the actual query and results
	 * inaccessible (and it cannot be recovered via a query reset).
	 *
	 * @return array events in the month
	 */
	private function get_month_view_events() {

		if ( ! $wp_query = tribe_get_global_query_object() ) {
			return [];
		}

		$event_date = $wp_query->get( 'eventDate' );

		$month = empty( $event_date )
			? tribe_get_month_view_date()
			: $wp_query->get( 'eventDate' );

		$args = [
			'eventDisplay'   => 'custom',
			'start_date'     => \Tribe\Events\Views\V2\Views\Month_View::calculate_first_cell_date( $month ),
			'end_date'       => \Tribe\Events\Views\V2\Views\Month_View::calculate_final_cell_date( $month ),
			'posts_per_page' => -1,
			'hide_upcoming'  => true,
		];

		// Verify the Initial Category.
		if ( $wp_query->get( Tribe__Events__Main::TAXONOMY, false ) !== false ) {
			$args[ Tribe__Events__Main::TAXONOMY ] = $wp_query->get( Tribe__Events__Main::TAXONOMY );
		}

		/**
		 * Provides an opportunity to modify the query args used to build a list of events
		 * to export from month view.
		 *
		 * This could be useful where its desirable to limit the exported data set to only
		 * those events taking place in the specific month being viewed (rather than an exact
		 * match of the events shown in month view itself, which may include events from
		 * adjacent months).
		 *
		 * @var array  $args
		 * @var string $month
		 */
		$args = (array) apply_filters( 'tribe_ical_feed_month_view_query_args', $args, $month );

		return tribe_get_events( $args );
	}

	/**
	 * Set the headers before the file is delivered.
	 *
	 * @since 4.9.4
	 */
	protected function set_headers() {
		header( 'HTTP/1.0 200 OK', true, 200 );
		header( 'Content-type: text/calendar; charset=UTF-8' );
		header(
			'Content-Disposition: attachment; filename="' . $this->get_file_name() . '"'
		);
	}

	/**
	 * Get the file name of the *.ics file
	 *
	 * @since 4.9.4
	 * @since 5.16.0 - Add the iCal type to the filename so both ics and outlook ics are unique names.
	 *
	 * @return mixed The calendar name
	 */
	protected function get_file_name() {
		$event_ids = wp_list_pluck( $this->events, 'ID' );
		$site      = sanitize_title( get_bloginfo( 'name' ) );
		$hash      = substr( md5( $this->type . implode( $event_ids ) ), 0, 11 );
		$filename  = sprintf( '%s-%s.ics', $site, $hash );

		/**
		 * Modifies the filename provided in the Content-Disposition header for iCal feeds.
		 *
		 * @var string       $filename
		 * @var WP_Post|null $post
		 */
		return apply_filters( 'tribe_events_ical_feed_filename', $filename, $this->post );
	}

	/**
	 * Get the full content of the *.ics file.
	 *
	 * @since 4.9.4
	 *
	 * @return string
	 */
	protected function get_content() {
		$parts = [
			$this->get_start(),
			$this->get_timezones( $this->events ),
			$this->get_body( $this->events ),
			$this->get_end()
		];
		return implode( '', $parts );
	}

	/**
	 * Get the start of the .ics File
	 *
	 * @since 4.9.4
	 * @since 5.16.0 - Add a check for iCAL type to prevent Outlook ics from including X-WR-CALNAME.
	 *
	 * @return mixed
	 */
	protected function get_start() {
		$blog_home    = get_bloginfo( 'url' );
		$blog_name    = get_bloginfo( 'name' );

		$content  = "BEGIN:VCALENDAR\r\n";
		$content .= "VERSION:2.0\r\n";
		$content .= 'PRODID:-//' . $blog_name . ' - ECPv' . Tribe__Events__Main::VERSION . "//NONSGML v1.0//EN\r\n";
		$content .= "CALSCALE:GREGORIAN\r\n";
		$content .= "METHOD:PUBLISH\r\n";

		/**
		 * Allows for customizing the value of the generated iCal file's "X-WR-CALNAME:" property.
		 *
		 * @param string $blog_name The value to use for "X-WR-CALNAME"; defaults to value of get_bloginfo( 'name' ).
		 */
		$x_wr_calname = apply_filters( 'tribe_ical_feed_calname', $blog_name );

		if ( ! empty( $x_wr_calname ) && 'ical' === $this->type ) {
			$content .= 'X-WR-CALNAME:' . $x_wr_calname . "\r\n";
		}

		$content .= 'X-ORIGINAL-URL:' . $blog_home . "\r\n";
		$content .= "X-WR-CALDESC:" . sprintf( esc_html_x( 'Events for %s', 'iCal feed description', 'the-events-calendar' ), $blog_name ) . "\r\n";

		/**
		 * Allows for customization of the various properties at the top of the generated iCal file.
		 *
		 * @param string $content Existing properties atop the file; starts at "BEGIN:VCALENDAR", ends at "X-WR-CALDESC".
		 */
		return apply_filters( 'tribe_ical_properties', $content );
	}

	/**
	 * Add the VTIMEZONE group to the file
	 *
	 * @since 4.9.4
	 *
	 * @param array $events
	 *
	 * @return string
	 */
	protected function get_timezones( $events = [] ) {
		$timezones = $this->parse_timezones( $events );

		if ( empty( $timezones ) ) {
			return '';
		}

		$item = [];
		foreach ( $timezones as $row ) {
			/** @var DateTimeZone $timezone */
			$timezone = $row['timezone'];

			$ordered = [
				'start' => wp_list_pluck( $row['events'], 'start_year' ),
				'end'   => wp_list_pluck( $row['events'], 'end_year' ),
			];

			sort( $ordered['start'] );
			rsort( $ordered['end'] );

			$ordered['start'] = array_values( $ordered['start'] );
			$ordered['end']   = array_values( $ordered['end'] );

			$start = reset( $ordered['start'] );
			$end   = reset( $ordered['end'] );

			if ( empty( $start ) || empty( $end ) ) {
				continue;
			}

			$transitions = $timezone->getTransitions( $start, $end );
			if ( is_array( $transitions ) && count( $transitions ) === 1 ) {
				$transitions[] = array_values( $transitions )[ 0 ];
			}

			$id = $timezone->getName();
			$item[] = 'TZID:' .  $id;

			$last_transition = null;
			foreach ( $transitions as $i => $transition ) {
				if ( $i === 0 ) {
					$last_transition = $transition;
					continue;
				}

				$type = 'STANDARD';
				if ( $transition['isdst'] ) {
					$type = 'DAYLIGHT';
				}
				$item[] = 'BEGIN:' . $type;
				$item[] = 'TZOFFSETFROM:' . $this->format_offset( $last_transition['offset'] );
				$item[] = 'TZOFFSETTO:' . $this->format_offset( $transition['offset'] );
				$item[] = 'TZNAME:' . $transition['abbr'];
				try {
					$start = new DateTime( $transition['time'], $timezone );
					$item[] = 'DTSTART:' . $start->format( "Ymd\THis" );
				} catch ( Exception $e ) {
					// @todo [BTRIA-610]: report this exception
				}
				$item[] = 'END:' . $type;
				$last_transition = $transition;
			}
		}

		/**
		 * Allow for customization of an individual "VTIMEZONE" item to be rendered inside an iCal export file.
		 *
		 * @since 4.9.4
		 *
		 * @param array $item The various iCal file format components of this specific event item.
		 * @param array $timezones The various iCal timzone components of this specific event item.
		 */
		$item = apply_filters( 'tribe_ical_feed_vtimezone', $item, $timezones );

		return "BEGIN:VTIMEZONE\r\n" . implode( "\r\n", $item ) . "\r\nEND:VTIMEZONE\r\n";
	}

	/**
	 * Create an array of arrays with unique Timezones for all the events, every timezone has
	 * the following fields:
	 *
	 * - timezone. The Timezone Object
	 * - events. List with all the events
	 *
	 * @since 4.9.4
	 *
	 * @param $events array An array with all the events to parse the timezones.
	 *
	 * @return array
	 */
	protected function parse_timezones( $events ) {
		$data = [];
		foreach ( $events as $event ) {
			if ( ! $event instanceof WP_Post ) {
				continue;
			}

			$timezone = $this->get_timezone( $event );

			if ( ! isset( $data[ $timezone ] ) ) {
				$data[ $timezone ] = [
					'timezone' => Tribe__Events__Timezones::build_timezone_object( $timezone ),
					'events' => [],
				];
			}

			$data[ $timezone ]['events'][] = [
				'event' => $event,
				'start_year' => strtotime( 'first day of january', tribe_get_start_date( $event, false, 'U' ) ),
				'end_year' => strtotime( 'last day of december', tribe_get_end_date( $event, false, 'U' ) ),
			];
		}
		return $data;
	}

	/**
	 * Format the offset into Hours and minutes from seconds.
	 *
	 * @since 4.9.4
	 *
	 * @param $offset
	 *
	 * @return string
	 */
	protected function format_offset( $offset ) {
		$hours   = intval( $offset / 60 / 60 );
		$minutes = abs( $offset ) / 60 - intval( abs( $offset ) / 60 / 60 ) * 60;
		$format  = "+%02d%02d";
		if ( $hours < 0 ) {
			$format = "%03d%02d";
		}

		return sprintf( $format, $hours, $minutes );
	}

	/**
	 * Get the Body With all the events of the .ics file
	 *
	 * @since 4.9.4
	 * @since5.1.6 - Utilize get_ical_output_for_an_event() to get the iCal output.
	 *
	 * @param array $posts
	 *
	 * @return string
	 */
	protected function get_body( $posts = [] ) {
		$tec         = Tribe__Events__Main::instance();
		$events      = '';

		foreach ( $posts as $event_post ) {

			$item = $this->get_ical_output_for_an_event( $event_post, $tec );

			$events .= "BEGIN:VEVENT\r\n" . implode( "\r\n", $item ) . "\r\nEND:VEVENT\r\n";
		}

		return $events;
	}

	/**
	 * Sanitize organizer name.
	 *
	 * @since 6.2.2
	 *
	 * @param string $name
	 *
	 * @return string
	 */
	private function sanitize_organizer_name( string $name ): string {
		// Characters not allowed in organizer name: CTLs, DQUOTE, ";", ":", ","
		$sanitized_name = rawurlencode( $name );

		// Array of characters to allow in organizer name.
		$chars = [ ' ', '&', '!', '?', "'", '*', '(', ')', '@', '#', '$', '%', '^', '+', '=', '{', '}', '[', ']', '|', '\\', '/', '<', '>', '`', '~' ];
		$encoded_chars = array_map( 'rawurlencode', $chars );

		// Since single quotes are allowed, let's convert any double quotes to single quotes.
		$encoded_chars[] = rawurlencode( '"' );
		$chars[] = "'";

		return str_replace( $encoded_chars, $chars, $sanitized_name );
	}

	/**
	 * Get the iCal Output for the provided event object.
	 *
	 * @since 5.1.6
	 * @since 6.2.2   Sanitize organizer name using new method.
	 *
	 * @param \WP_Post             $event_post The event post object.
	 * @param \Tribe__Events__Main $tec        An instance of the main TEC Class.
	 *
	 * @return array  An array of iCal output fields.
	 */
	public function get_ical_output_for_an_event( $event_post, Tribe__Events__Main $tec ) {
		// Add fields to iCal output.
		$item              = [];
		$access_to_content = self::has_access_to_see_event_content( $event_post );
		$full_format       = 'Ymd\THis';
		$utc_format        = 'Ymd\THis\Z';
		$all_day           = ( 'yes' === get_post_meta( $event_post->ID, '_EventAllDay', true ) );
		$time              = (object) [
			'start'    => tribe_get_start_date( $event_post->ID, false, 'U' ),
			'end'      => tribe_get_end_date( $event_post->ID, false, 'U' ),
			'modified' => Tribe__Date_Utils::wp_strtotime( $event_post->post_modified ),
			'created'  => Tribe__Date_Utils::wp_strtotime( $event_post->post_date ),
		];

		$type   = 'DATE-TIME';
		$format = $full_format;

		if ( $all_day ) {
			$type   = 'DATE';
			$format = 'Ymd';
		}

		$tzoned = (object) [
			'start'    => Tribe__Date_Utils::build_date_object( $time->start )->format( $format ),
			'end'      => Tribe__Date_Utils::build_date_object( $time->end )->format( $format ),
			'modified' => Tribe__Date_Utils::build_date_object( $time->modified )->format( $utc_format ),
			'created'  => Tribe__Date_Utils::build_date_object( $time->created )->format( $utc_format ),
		];

		$dtstart = $tzoned->start;
		$dtend   = $tzoned->end;

		if ( 'DATE' === $type ) {
			// For all day events dtend should always be +1 day.
			if ( $all_day ) {
				$dtend = Tribe__Date_Utils::build_date_object( strtotime( '+1 day', strtotime( $dtend ) ) )->format( $format );
			}

			$item['DTSTART'] = 'DTSTART;VALUE=' . $type . ':' . $dtstart;
			$item['DTEND']   = 'DTEND;VALUE=' . $type . ':' . $dtend;
		} else {
			// Are we using the sitewide timezone or the local event timezone?
			$timezone_name = $this->get_timezone( $event_post );
			$timezone      = Tribe__Events__Timezones::build_timezone_object( $timezone_name );

			$item['DTSTART'] = 'DTSTART;TZID=' . $timezone->getName() . ':' . $dtstart;
			$item['DTEND']   = 'DTEND;TZID=' . $timezone->getName() . ':' . $dtend;
		}

		$item['DTSTAMP']       = 'DTSTAMP:' . Tribe__Date_Utils::build_date_object()->format( $full_format );
		$item['CREATED']       = 'CREATED:' . $tzoned->created;
		$item['LAST-MODIFIED'] = 'LAST-MODIFIED:' . $tzoned->modified;
		$item['UID']           = 'UID:' . $event_post->ID . '-' . $time->start . '-' . $time->end . '@' . wp_parse_url( home_url( '/' ), PHP_URL_HOST );
		$item['SUMMARY']       = 'SUMMARY:' . $this->replace( wp_strip_all_tags( $event_post->post_title ) );

		if ( $access_to_content ) {
			$content = apply_filters( 'the_content', tribe( 'editor.utils' )->exclude_tribe_blocks( $event_post->post_content ) );
		} else {
			$content = _x( 'Content is protected.', 'Description in iCal content for events with hidden/protected content.', 'the-events-calendar' );
			/**
			 * Filters the password protected description for ical event descriptions that are displayed in the output.
			 *
			 * @since 6.1.3
			 *
			 * @param string The replaced message that will display in the ical event description.
			 */
			$content = apply_filters( 'tec_events_ical_protected_content_description', $content );
		}

		$item['DESCRIPTION'] = 'DESCRIPTION:' . $this->replace( wp_strip_all_tags( str_replace( '</p>', '</p> ', $content ) ) );

		$item['URL'] = 'URL:' . get_permalink( $event_post->ID );

		// Add location if available.
		$location = \Tribe__Events__Venue::get_address_full_string( $event_post->ID );
		if ( ! empty( $location ) ) {
			$str_location = $this->replace( $location, [ ',', "\n" ], [ '\,', '\n' ] );

			$item['LOCATION'] = 'LOCATION:' . $str_location;
		}

		// Add categories if available.
		$event_cats = wp_get_object_terms( $event_post->ID, Tribe__Events__Main::TAXONOMY, [ 'fields' => 'names' ] );

		if ( ! is_wp_error( $event_cats ) && ! empty( $event_cats ) ) {
			$item['CATEGORIES'] = 'CATEGORIES:' . $this->html_decode( join( ',', (array) $event_cats ) );
		}

		// Add featured image if available.
		if ( has_post_thumbnail( $event_post->ID ) && $access_to_content ) {
			$thumbnail_id        = get_post_thumbnail_id( $event_post->ID );
			$thumbnail_url       = wp_get_attachment_url( $thumbnail_id );
			$thumbnail_mime_type = get_post_mime_type( $thumbnail_id );

			/**
			 * Allow for customization of an individual iCal-exported event's thumbnail.
			 *
			 * @param string $string  This thumbnail's iCal-formatted "ATTACH;" string with the thumbnail mime type and URL.
			 * @param int    $post_id The ID of the event this thumbnail belongs to.
			 */
			$item['ATTACH'] = apply_filters( 'tribe_ical_feed_item_thumbnail', sprintf( 'ATTACH;FMTTYPE=%s:%s', $thumbnail_mime_type, $thumbnail_url ), $event_post->ID );
		}

		// Add organizer if available.
		$organizer_email = tribe_get_organizer_email( $event_post->ID, false );
		if ( $organizer_email ) {
			$organizer_id = tribe_get_organizer_id( $event_post->ID );
			$organizer    = get_post( $organizer_id );

			if ( $organizer_id ) {
				$sanitized_name = $this->sanitize_organizer_name( $organizer->post_title );
				$item['ORGANIZER'] = sprintf( 'ORGANIZER;CN="%s":MAILTO:%s', $sanitized_name, $organizer_email );
			} else {
				$item['ORGANIZER'] = sprintf( 'ORGANIZER:MAILTO:%s', $organizer_email );
			}
		}

		/**
		 * Allow for customization of an individual "VEVENT" item to be rendered inside an iCal export file.
		 *
		 * @param array  $item       The various iCal file format components of this specific event item.
		 * @param object $event_post The WP_Post of this event.
		 */
		return apply_filters( 'tribe_ical_feed_item', $item, $event_post );
	}

	/**
	 * Replace the text and encode the text before doing the replacement.
	 *
	 * @since 4.9.4
	 *
	 * @param string $text The text to be replaced.
	 * @param array  $search What elements to search to replace.
	 * @param array  $replacement New values used to replace.
	 *
	 * @return mixed
	 */
	protected function replace( $text = '', $search = [], $replacement = [] ) {
		$search = empty( $search ) ? [ ',', "\n", "\r" ] : $search;
		$replacement = empty( $replacement ) ? [ '\,', '\n', '' ] : $replacement;
		return str_replace( $search, $replacement, $this->html_decode( $text ) );
	}

	/**
	 * Apply html_entity_decode on a string using ENT_QUOTES style
	 *
	 * @since 4.9.4
	 *
	 * @param string $text
	 *
	 * @return string
	 */
	protected function html_decode( $text = '' ) {
		return html_entity_decode( $text, ENT_QUOTES );
	}

	/**
	 * Return the timezone name associated with the event
	 *
	 * @since 4.9.4
	 *
	 * @param $event \WP_Post The $event post
	 *
	 * @return string
	 */
	protected function get_timezone( $event ) {
		return Tribe__Events__Timezones::EVENT_TIMEZONE === Tribe__Events__Timezones::mode()
			? Tribe__Events__Timezones::get_event_timezone_string( $event->ID )
			: Tribe__Events__Timezones::wp_timezone_string();
	}

	/**
	 * Return the end of the .ics file
	 *
	 * @since 4.9.4
	 *
	 * @return string
	 */
	protected function get_end() {
		return 'END:VCALENDAR';
	}

	/**
	 * Get a list of events, the function make sure it uses the default values used on the main events page
	 * so if is called from a different location like a page or post (shortcode) it will retain the original values
	 * to generate the events feed.
	 *
	 * @since 4.6.11
	 *
	 * @param array $args The WP_Query arguments.
	 * @param mixed $query A WP_Query object or null if none.
	 * @return array
	 */
	protected function get_events_list( $args = [], $query = null ) {
		/**
		 * Filter the arguments used to construct the call to get the list of events.
		 *
		 * @since 4.6.11
		 *
		 * @param array $args Arguments used in WP_Query call.
		 */
		$args = apply_filters( 'tribe_events_ical_events_list_args', $args );

		/**
		 * Filter the Query object used to get the list of events used to populate the feed.
		 *
		 * @since 4.6.11
		 *
		 * @param mixed $query a WP_Query or null.
		 */
		$query = apply_filters( 'tribe_events_ical_events_list_query', $query );

		$count = $this->feed_posts_per_page();
		$query_posts_per_page = 0;
		if ( $query instanceof WP_Query ) {
			$query_posts_per_page = $query->get( 'posts_per_page' );
		}

		$list = [];
		// When `posts_per_page` is set to `-1` we can slice.
		if ( $query_posts_per_page >= 0 && $count > $query_posts_per_page ) {
			$args['posts_per_page'] = $count;
			$events_query = tribe_get_events( wp_parse_args( $args, [ 'posts_per_page' => $this->feed_posts_per_page() ] ), true );
			$list = $events_query->get_posts();
		} elseif ( $query instanceof WP_Query ) {
			$list = array_slice( $query->posts, 0, $count );
		}
		return $list;
	}

	/**
	 * Get the number of posts per page to be used on the feed of the iCal, make sure it passes the value via the filter
	 * tribe_ical_feed_posts_per_page and validates the number is greater than 0.
	 *
	 * @since 4.6.11
	 *
	 * @return int
	 */
	public function feed_posts_per_page() {
		/**
		 * Filters the number of upcoming events the iCal feed should export.
		 *
		 * This filter allows developer to override the pagination setting and the default value
		 * to export a number of events that's inferior or superior to the one shown on the page.
		 * The minimum value is 1.
		 *
		 * @param int $count The number of upcoming events that should be exported in the
		 *                   feed, defaults to 30.
		 */
		$count = apply_filters( 'tribe_ical_feed_posts_per_page', $this->feed_default_export_count );

		return is_numeric( $count ) && is_int( $count ) && $count > 0
			? $count
			: $this->feed_default_export_count;
	}

	/**
	 * Gets the number of events that should be exported when generating the iCal feed.
	 *
	 * @return int
	 */
	public function get_feed_default_export_count() {
		return $this->feed_default_export_count;
	}

	/**
	 * Sets the number of events that should be exported when generating the iCal feed.
	 *
	 * @param int $count
	 */
	public function set_feed_default_export_count( $count ) {
		$this->feed_default_export_count = $count;
	}
}

Filemanager

Name Type Size Permission Actions
Admin Folder 0777
Aggregator Folder 0777
Ajax Folder 0777
Collections Folder 0777
Customizer Folder 0777
Dates Folder 0777
Editor Folder 0777
Event_Status Folder 0777
Event_Tickets Folder 0777
Featured_Events Folder 0777
Google Folder 0777
Importer Folder 0777
Integrations Folder 0777
JSON_LD Folder 0777
Linked_Posts Folder 0777
Meta Folder 0777
Models Folder 0777
REST Folder 0777
Repositories Folder 0777
Revisions Folder 0777
Service_Providers Folder 0777
Shortcode Folder 0777
Taxonomy Folder 0777
Template Folder 0777
Utils Folder 0777
Validator Folder 0777
Views Folder 0777
API.php File 29.34 KB 0644
Adjacent_Events.php File 10.27 KB 0644
Admin_List.php File 14.84 KB 0644
Aggregator.php File 17.13 KB 0644
Amalgamator.php File 14.04 KB 0644
Assets.php File 18.52 KB 0644
Capabilities.php File 5.56 KB 0644
Constants.php File 1.78 KB 0644
Cost_Utils.php File 4.81 KB 0644
Deactivation.php File 1.47 KB 0644
Default_Values.php File 793 B 0644
Editor.php File 17.7 KB 0644
Embedded_Maps.php File 5.54 KB 0644
Event_Cleaner.php File 2.65 KB 0644
Event_Cleaner_Scheduler.php File 8.36 KB 0644
Featured_Events.php File 1.75 KB 0644
Front_Page_View.php File 10.73 KB 0644
Gutenberg.php File 3.27 KB 0644
I18n.php File 14.5 KB 0644
Ignored_Events.php File 31.48 KB 0644
Linked_Posts.php File 47.54 KB 0644
Main.php File 137.8 KB 0644
Options_Exception.php File 890 B 0644
Organizer.php File 25.63 KB 0644
Plugin_Register.php File 1007 B 0644
Post_Exception.php File 859 B 0644
Privacy.php File 1.31 KB 0644
Query.php File 18.14 KB 0644
Rewrite.php File 28.77 KB 0644
Template_Factory.php File 15.11 KB 0644
Templates.php File 6.48 KB 0644
Timezones.php File 6.36 KB 0644
Updater.php File 9.8 KB 0644
Venue.php File 28.32 KB 0644
iCal.php File 30.9 KB 0644