<?php
/**
 * Handles the query manipulation of events
 */

/**
 * Registers our custom query variables
 *
 * Hooked onto query_vars
 * @since 1.0.0
 * @access private
 * @ignore
 *
 * @param array $qvars Query variables
 * @param array Query variables with plug-in added variables
 */
function eventorganiser_register_query_vars( $qvars ){
	//Add these query variables
	$qvars[] = 'venue';//Depreciated
	$qvars[] = 'ondate';
	$qvars[] = 'showrepeats';
	$qvars[] = 'eo_interval';
	$qvars[] = 'event_start_before';
	$qvars[] = 'event_start_after';
	$qvars[] = 'event_end_before';
	$qvars[] = 'event_after_after';
	$qvars[] = 'event_series';
	return $qvars;
}
add_filter('query_vars', 'eventorganiser_register_query_vars' );


/**
 * Parses event queries and alters the WP_Query object appropriately
 *
 * Parse's the query, and sets date range and other event specific query variables.
 * If query is for 'event' post type - the posts_* filters are added.
 *
 * Hooked onto pre_get_posts
 * @since 1.0.0
 * @access private
 * @ignore
 *
 * @param WP_Query $query The query
 */
function eventorganiser_pre_get_posts( $query ) {

	//Deprecated, use event-venue instead.
	if ( $query->get( 'venue' ) ) {
		$venue = $query->get( 'venue' );
		$query->set( 'event-venue',$venue );
		$query->set( 'post_type', 'event' );
	}

	//If the query is for eo-events feed, set post type
	if ( $query->is_feed( 'eo-events' ) ) {
		$query->set( 'post_type', 'event' );
		$query->is_comment_feed = false;
	}

	//If querying for all events starting on given date, set the date parameters
	if ( $query->get( 'ondate' ) ) {

		//Normalise date delimiter
		$ondate_start = str_replace( '/', '-', $query->get( 'ondate' ) );
		$ondate_end = str_replace( '/', '-', $query->get( 'ondate' ) );

		$parts = count(explode('-',$ondate_start));
		if( $parts == 1 && is_numeric($ondate_start) ){
			//Numeric - interpret as year
			$ondate_start .= '-01-01';
			$ondate_end .= '-12-31';
		}elseif( $parts == 2 ){
			// 2012-01 format: interpret as month
			$ondate_start .= '-01';
			try{
				$end = new DateTime($ondate_start);
				$ondate_end = $end->format('Y-m-t');
			}catch( Exception $e){
				$query->set('ondate',false);
			}
		}

		$query->set( 'post_type', 'event' );
		$query->set( 'event_start_before', $ondate_end );
		$query->set( 'event_end_after', $ondate_start );
	}

	//If not on event, stop here.
	if( ! eventorganiser_is_event_query( $query, true ) )
		return $query;

	// WP will cast stdClass Object to WP_Object loosing event data if caching is enabled.
	$query->set('cache_results', false);

	//@see https://github.com/stephenharris/Event-Organiser/issues/30
	if( $query->is_main_query() ){
		if( eo_is_event_archive( 'day' ) || eo_is_event_archive( 'month' ) || eo_is_event_archive( 'year' ) ){
			$query->set( 'showpastevents', true );
		}
	}

	$blog_now = new DateTime("now", eo_get_blog_timezone());

	//Determine whether or not to show past events and each occurrence. //If not set, use options
	//@see https://core.trac.wordpress.org/ticket/16471
	if( !is_admin() && !is_single() && !$query->is_feed('eo-events') && !isset($query->query_vars['showpastevents']) ){
		//If showpastevents is not set - use options (except for admin / single pages.
		$query->set('showpastevents', eventorganiser_get_option('showpast') );
	}

	//Deprecated: showrepeats - use group_events_by instead
	//@see https://core.trac.wordpress.org/ticket/16471
	if( isset($query->query_vars['showrepeats']) && !isset($query->query_vars['group_events_by']) ){
		if( !$query->query_vars['showrepeats'] )
			$query->set('group_events_by','series');
	}

	//Determine how to group events: by series or show each occurrence
	//@see https://core.trac.wordpress.org/ticket/16471
	if( !isset($query->query_vars['group_events_by']) ){

		//Group by isn't set - default depends on context:
		if( $query->is_main_query() &&  (is_admin() || is_single() || $query->is_feed('eo-events') ) ){

			//If in admin or single page - we probably don't want to see duplicates of (recurrent) events - unless specified otherwise.
			//but neither do we care which date component is selected.
			$query->set('group_events_by','series_indeterminate');

		}elseif( eventorganiser_get_option('group_events') == 'series' ){
			//In other instances (archives, shortcode listing) if showrepeats option is false display only the next event.
			$query->set('group_events_by','series');
		}else{
			$query->set('group_events_by','occurrence');
		}
	}

	//Parse user input as date-time objects
	$date_objs = array('event_start_after'=>'','event_start_before'=>'','event_end_after'=>'','event_end_before'=>'');
	foreach($date_objs as $prop => $value):
		$date = $query->get($prop);
		if ( !( $date instanceof DateTime ) ){
			try{
				$date = ( empty($date) ? false : new DateTime($date, eo_get_blog_timezone()) );
			}catch( Exception $e){
				$date = false;
			}
		}
		$date_objs[$prop] = $date;
		$query->set($prop, $date);
	endforeach;

	//If eo_interval is set, determine date ranges
	if ( $query->get( 'eo_interval' ) ) {
		switch ( $query->get( 'eo_interval' ) ) :
			case 'expired':
				$meta_query = (array) $query->get('meta_query');
				$meta_query[] =array(
					'key' => '_eventorganiser_schedule_last_finish',
					'value' => $blog_now->format('Y-m-d H:i:s'),
					'compare' => '<='
				);
				$query->set('meta_query',$meta_query) ;
				break;

			case 'future':
				$meta_query = $query->get('meta_query');
				$meta_query = empty($meta_query) ? array() : $meta_query;
				$meta_query[] =array(
					'key' => '_eventorganiser_schedule_last_start',
					'value' => $blog_now->format('Y-m-d H:i:s'),
					'compare' => '>='
				);
				$query->set('meta_query',$meta_query) ;
				break;

			case 'P1D':
			case 'P1W':
			case 'P1M':
			case 'P6M':
			case 'P1Y':
				//I hate you php5.2
				$intervals = array( 'P1D' => '+1 day', 'P1W' => '+1 week', 'P1M' => '+1 month', 'P6M' => '+6 month', 'P1Y' => '+1 Year' );
				$cutoff = clone $blog_now;
				$cutoff->modify( $intervals[ $query->get( 'eo_interval' ) ] );

				if( is_admin() && 'series_indeterminate' == $query->get('group_events_by') ){
					//On admin we want to show the **first** occurrence of a recurring event which has an occurrence in the interval
					global $wpdb;
					$post_ids = $wpdb->get_results($wpdb->prepare(
						"SELECT DISTINCT post_id FROM {$wpdb->eo_events}
						WHERE {$wpdb->eo_events}.StartDate <= %s
						AND {$wpdb->eo_events}.EndDate >= %s",
						$cutoff->format('Y-m-d'),$blog_now->format('Y-m-d')));

					if($post_ids)
						$query->set('post__in',wp_list_pluck($post_ids, 'post_id'));

				}else{
					if( empty($date_objs['event_start_before']) || $cutoff < $date_objs['event_start_before'] ){
						$date_objs['event_start_before'] = $cutoff;
					}
					if( empty($date_objs['event_end_after']) || $blog_now > $date_objs['event_end_after'] ){
						$date_objs['event_end_after'] = $blog_now;
					}
				}
		endswitch;
	}//Endif interval set

	$running_event_is_past= (  eventorganiser_get_option('runningisnotpast') ? true : false);

	//Set date range according to whether we show past events
	//@see https://core.trac.wordpress.org/ticket/16471
	if(isset($query->query_vars['showpastevents'])&& !$query->query_vars['showpastevents'] ){
		//Showing only future events

		//Running event is past - Get events which start in the future
		//A current event is not past - Get events which finish in the future
		$key = ( $running_event_is_past ? 'event_start_after' : 'event_end_after');

		//If current queried date is not set or before now, set the queried date to now
		$date_objs[$key]  = (empty($date_objs[$key]) || $blog_now > $date_objs[$key]) ? $blog_now : $date_objs[$key];
	}


	//Set event dates to 'Y-m-d H:i:s' format.
	foreach ($date_objs as $prop => $datetime ){
		if( !empty($datetime) )
			$query->set($prop, $datetime->format('Y-m-d H:i:s'));
	}

	if( $query->is_feed('eo-events') ){
		//Posts per page for feeds bug https://core.trac.wordpress.org/ticket/17853
		add_filter('post_limits','wp17853_eventorganiser_workaround');
		$query->set('posts_per_page',-1);
	}

	//Add the posts_* filters to modify the query
	add_filter('posts_fields', 'eventorganiser_event_fields',10,2);
	add_filter('posts_join', 'eventorganiser_join_tables',10,2);
	add_filter('posts_where','eventorganiser_events_where',10,2);
	add_filter('posts_orderby','eventorganiser_sort_events',10,2);
	add_filter('posts_groupby', 'eventorganiser_event_groupby',10,2);
}
add_action( 'pre_get_posts', 'eventorganiser_pre_get_posts', 11 );

//Workaround for https://github.com/stephenharris/Event-Organiser/issues/55,
add_action( 'pre_get_posts', '__return_false', 10 );


/**
 * A work around for a bug that posts_per_page is over-ridden by the posts_per_rss option. https://core.trac.wordpress.org/ticket/17853

 * posts_per_rss option overirdes posts_per_page  and nopaging is also set to 'false'.
 * For ics feeds nopaging should be true and post_per_page should be -1. We intercept the LIMIT part of the query and remove it.
 * We return '' so there is no LIMIT part to the query.
 * Hooked on in eventorganiser_pre_get_posts
 * @since 1.5.7
 * @access private
 * @ignore
 * @param string $limit LIMIT part of the SQL statement
 * @return string Empty string
 */
function wp17853_eventorganiser_workaround( $limit ){
	remove_filter(current_filter(),__FUNCTION__);
	return '';
}


/**
 * SELECT only date fields from events and venue table for events
 * All other fields deprecated and stored in post meta since 1.5
 * Hooked onto posts_fields
 *
 *@since 1.0.0
 *@access private
 *@ignore
 *@param string $select SELECT part of the SQL statement
 *@param string $query WP_Query
 *@return string
 */
function eventorganiser_event_fields( $select, $query ){
	global $wpdb;

	$q_fields = $query->get( 'fields' );

	if( eventorganiser_is_event_query( $query, true ) && 'ids' != $q_fields && 'id=>parent' != $q_fields ){
		$et =$wpdb->eo_events;
		$select .= ", {$et}.event_id, {$et}.event_id AS occurrence_id, {$et}.StartDate, {$et}.StartTime, {$et}.EndDate, {$et}.FinishTime, {$et}.event_occurrence ";
	}
	return $select;
}


/**
 * GROUP BY Event (occurrence) ID
 * Event posts do not want to be grouped by post, but by occurrence. When grouping
 * by series we do not use GROUP BY because we cannot be sure of the record that
 * is selected for the group.
 *
 * @since 1.0.0
 * @access private
 * @ignore
 * @param string $groupby GROUP BY part of the SQL statement
 * @param string $query WP_Query
 * @return string
 */
function eventorganiser_event_groupby( $groupby, $query ) {
	global $wpdb;

	if ( eventorganiser_is_event_query( $query, true ) ) {

		if ( 'series_indeterminate' == $query->get( 'group_events_by' ) ) {
			return "{$wpdb->eo_events}.post_id";
		}

		if ( empty( $groupby ) ) {
			return $groupby;
		}

		return "{$wpdb->eo_events}.event_id";
	}

	return $groupby;
}


/**
 * LEFT JOIN all EVENTS.
 * Joins events table when querying for events
 * Hooked onto posts_join
 *
 * @since 1.0.0
 * @access private
 * @ignore
 * @param string $join JOIN part of the SQL statement
 * @param string $query WP_Query
 * @return string
 */
function eventorganiser_join_tables( $join, $query ) {
	global $wpdb;

	if ( eventorganiser_is_event_query( $query, true ) ) {

		$join .= " LEFT JOIN $wpdb->eo_events ON $wpdb->posts.ID = {$wpdb->eo_events}.post_id ";

		if ( 'series' == $query->get( 'group_events_by' ) ) {

			//This could potentially be a big select - use 'series_indeterminate' if
			//you don't care how the occurrence is selected - it's a lot cheaper.
			//@link https://github.com/stephenharris/Event-Organiser/issues/432
			$wpdb->query("SET SQL_BIG_SELECTS=1;");

			//@link https://github.com/stephenharris/Event-Organiser/issues/430
			$where = _eventorganiser_generate_mysql_where( $query );
			$where  = $where ? "WHERE {$where}" : '';
			$join .= " LEFT JOIN
				(SELECT post_id, StartDate, StartTime FROM {$wpdb->eo_events} $where) AS {$wpdb->eo_events}2
				ON {$wpdb->eo_events}.post_id = {$wpdb->eo_events}2.post_id
				AND TIMESTAMP({$wpdb->eo_events}.StartDate,{$wpdb->eo_events}.StartTime)
				> TIMESTAMP({$wpdb->eo_events}2.StartDate,{$wpdb->eo_events}2.StartTime)";
		}
	}
	return $join;
}

/**
 *
 * @access private
 */
function _eventorganiser_generate_mysql_where( $query ) {

	global $wpdb;

	$where = array();

	//If we only want events (or occurrences of events) that belong to a particular 'event'
	//https://core.trac.wordpress.org/ticket/16471
	if ( isset( $query->query_vars['event_series'] ) ) :
		$series_id = $query->query_vars['event_series'];
		$where[]   = $wpdb->prepare( "{$wpdb->eo_events}.post_id =%d ", $series_id );
	endif;

	//https://core.trac.wordpress.org/ticket/16471
	if ( isset( $query->query_vars['event_occurrence_id'] ) ) :
		$occurrence_id = $query->query_vars['event_occurrence_id'];
		$where[]       = $wpdb->prepare( "{$wpdb->eo_events}.event_id=%d ", $occurrence_id );
	endif;

	//https://core.trac.wordpress.org/ticket/16471
	if ( isset( $query->query_vars['event_occurrence__not_in'] ) ) :
		$occurrence__not_in = implode( ', ', array_map( 'intval', $query->query_vars['event_occurrence__not_in'] ) );
		$where[]            = "{$wpdb->eo_events}.event_id NOT IN({$occurrence__not_in}) ";
	endif;

	//https://core.trac.wordpress.org/ticket/16471
	if ( isset( $query->query_vars['event_occurrence__in'] ) ) :
		$occurrence__in = implode( ', ', array_map( 'intval', $query->query_vars['event_occurrence__in'] ) );
		$where[]        = "{$wpdb->eo_events}.event_id IN({$occurrence__in}) ";
	endif;

	//Check date ranges were are interested in.
	$date_queries = array(
		'event_start_after'  => array(
			'notstrict' => "{$wpdb->eo_events}.StartDate >= %s ",
			'strict'    => "({$wpdb->eo_events}.StartDate > %s OR ({$wpdb->eo_events}.StartDate = %s AND {$wpdb->eo_events}.StartTime > %s)) ",
		),
		'event_start_before' => array(
			'notstrict' => "{$wpdb->eo_events}.StartDate <= %s ",
			'strict'    => "({$wpdb->eo_events}.StartDate < %s OR ({$wpdb->eo_events}.StartDate = %s AND {$wpdb->eo_events}.StartTime < %s)) ",
		),
		'event_end_after'    => array(
			'notstrict' => "{$wpdb->eo_events}.EndDate >= %s ",
			'strict'    => "({$wpdb->eo_events}.EndDate > %s OR ({$wpdb->eo_events}.EndDate = %s AND {$wpdb->eo_events}.FinishTime > %s)) ",
		),
		'event_end_before'   => array(
			'notstrict' => "{$wpdb->eo_events}.EndDate <= %s ",
			'strict'    => "({$wpdb->eo_events}.EndDate < %s OR ({$wpdb->eo_events}.EndDate = %s AND {$wpdb->eo_events}.FinishTime < %s)) ",
		),
	);

	//Construct sql query.
	foreach ( $date_queries as $prop => $_sql ) {
		$datetime = $query->get( $prop );
		if ( ! empty( $datetime ) ) {
			$date = eo_format_date( $datetime, 'Y-m-d' );
			$time = eo_format_date( $datetime, 'H:i:s' );
			if ( '00:00:00' == $time ) {
				$sql     = $_sql['notstrict'];
				$where[] = $wpdb->prepare( $sql, $date );
			} else {
				$sql     = $_sql['strict'];
				$where[] = $wpdb->prepare( $sql, $date, $date, $time );
			}
		}
	}

	$where = array_filter( $where );
	return implode( ' AND ', $where );
}


/**
 * Checks whether a given query is for events
 * @package event-query-functions
 * @param WP_Query $query The query to test
 * @param bool $exclusive Whether to test if the query is *exclusively* for events, or can include other post types
 * @return bool True if the query is an event query. False otherwise.
 */
function eventorganiser_is_event_query( $query, $exclusive = false ){

	$post_types = $query->get( 'post_type' );

	if( 'any' == $post_types ){
		$post_types = get_post_types( array('exclude_from_search' => false) );
	}

	if( 'event' == $post_types || array( 'event' ) == $post_types ){
		$bool = true;

	}elseif( $query && $query->is_feed( 'eo-events' ) ){
		$bool = true;

	}elseif( empty( $post_types ) && eo_is_event_taxonomy( $query ) ){

		//Querying by taxonomy - check if 'event' is the only post type
		$post_types = array();
		$taxonomies = wp_list_pluck( $query->tax_query->queries, 'taxonomy' );

		foreach ( get_post_types() as $pt ) {

			$object_taxonomies = ( $pt === 'attachment' ? get_taxonomies_for_attachments() : get_object_taxonomies( $pt ) );

			if ( array_intersect( $taxonomies, $object_taxonomies ) ) {
				$post_types[] = $pt;
			}
		}

		if( in_array( 'event', $post_types ) ){
			if( $exclusive && 1 == count( $post_types ) ){
				$query->set( 'post_type', 'event' );
				$bool = true;
			}elseif( !$exclusive ){
				$bool = true;
			}else{
				$bool = false;
			}
		}else{
			$bool = false;
		}
	}elseif( $exclusive ){
		$bool = false;

	}elseif( ( is_array( $post_types ) && in_array( 'event', $post_types ) ) ){

		$bool = true;

	}else{
		$bool = false;

	}

	/**
	 * Filters whether the query is an event query.
	 *
	 * This should be `true` if the query is for events, `false` otherwise. The
	 * third parameter, `$exclusive` qualifies if this means 'query exclusively
	 * for events' or not. If `true` then this filter should return `true` only
	 * if the query is exclusively for events.
	 *
	 * @param bool     $bool      Whether the query is an event query.
	 * @param WP_Query $query     The WP_Query instance to check.
	 * @param bool     $exclusive Whether the check if for queries exclusively for events.
	 */
	return apply_filters( 'eventorganiser_is_event_query', $bool, $query, $exclusive );
}


/**
 * Selects posts which satisfy custom WHERE statements
 * Hooked onto posts_where
 *
 * @since 1.0.0
 * @access private
 * @ignore
 * @param string $where WHERE part of the SQL statement
 * @param string $query WP_Query
 * @return string
 */
function eventorganiser_events_where( $where, $query ) {
	global $wpdb;

	$_where = _eventorganiser_generate_mysql_where( $query );
	if ( $_where ) {
		$where .= " AND {$_where}";
	}
	if('series' == $query->get( 'group_events_by' )){
		$where .= " AND {$wpdb->eo_events}2.StartDate is NULL";
	}

	return $where;
}

/**
* Alter the order of posts.
* This function allows to sort our events by a custom order (venue, date etc)
 * Hooked onto posts_orderby
 *
 *@since 1.0.0
 *@access private
 *@ignore
 *@param string $orderby ORDER BY part of the SQL statement
 *@param string $query WP_Query
 *@return string
 */
function eventorganiser_sort_events( $orderby, $query ) {
	global $wpdb;

	if ( $query->get( 'orderby' ) ) {
		//If the query sets an orderby return what to do if it is one of our custom orderbys
		$order_crit = $query->get( 'orderby' );
		$order_dir  = $query->get( 'order' );

		switch ( $order_crit ) :
			case 'eventstart':
				return  " {$wpdb->eo_events}.StartDate $order_dir, {$wpdb->eo_events}.StartTime $order_dir";
				break;

			case 'eventend':
				return  " {$wpdb->eo_events}.EndDate $order_dir, {$wpdb->eo_events}.FinishTime $order_dir";
				break;

			default:
				return $orderby;
		endswitch;

	} elseif ( eventorganiser_is_event_query( $query, true ) ) {
			//If no orderby is set, but we are querying events, return the default order for events;
			$orderby = " {$wpdb->eo_events}.StartDate ASC, {$wpdb->eo_events}.StartTime ASC";
	}
	return $orderby;
}




/**
 * Checks if the main query is for a venue
 * This will be deprecated shortly
 *@since 1.0.0
 *@ignore
 *@return bool True if the main query is for a venue, false otherwise.
 */
function eo_is_venue(){
	global $wp_query;
	//@see https://core.trac.wordpress.org/ticket/16471
	return (isset($wp_query->query_vars['venue'] ) || is_tax('event-venue'));
}


function _eventorganiser_update_event_dates_cache( $events, $query ){

	// No point in doing all this work if we didn't match any posts.
	if ( ! $events ) {
		return $events;
	}

	//PHP 5.3 and early can't cache datetime objects
	if ( version_compare( PHP_VERSION, '5.3.0' ) < 0 ) {
		return $events;
	}

	//TODO do we need to check if $events is an array of WP_Post objects?
	//TODO allow this to be skipped?

	if ( ! eventorganiser_is_event_query( $query ) ) {
		return $events;
	}

	$tz = eo_get_blog_timezone();

	foreach ( $events as $event ) {

		$id = $event->ID;
		$cached_event = (array) wp_cache_get( 'eventorganiser_occurrences_'.$id );

		if ( isset( $cached_event[$event->occurrence_id] ) ) {
			continue;
		}

		$cached_event[$event->occurrence_id] = 	array(
			'start' => new DateTime($event->StartDate.' '.$event->StartTime, $tz ),
			'end'   => new DateTime($event->EndDate.' '.$event->FinishTime, $tz ),
		);

		wp_cache_set( 'eventorganiser_occurrences_'.$id, $cached_event );

	}

	return $events;
}

add_filter( 'the_posts', '_eventorganiser_update_event_dates_cache', 10, 2 );
?>
