File "Event.php"
Full Path: /home/romayxjt/public_html/wp-content/plugins/the-events-calendar/src/Tribe/Repositories/Event.php
File size: 67.54 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* The main ORM/Repository class for events.
*
* @since 4.9
*/
use Tribe__Date_Utils as Dates;
use Tribe__Timezones as Timezones;
use Tribe__Utils__Array as Arr;
/**
* Class Tribe__Events__Repositories__Event
*
*
* @since 4.9
*/
class Tribe__Events__Repositories__Event extends Tribe__Repository {
/**
* The unique fragment that will be used to identify this repository filters.
*
* @var string
*/
protected $filter_name = 'events';
/**
* The menu_order override used in pre_get_posts to support negative menu_order lookups for Sticky Events.
*
* @var int
*/
protected $menu_order = 0;
/**
* The meta key that should be used for the start date.
*
* Defaults to `_EventStartDateUTC`.
*
* @see \Tribe__Events__Repositories__Event::use_utc()
*
* @var string
*/
protected $start_meta_key = '_EventStartDateUTC';
/**
* The meta key that should be used for the end date.
*
* Defaults to `_EventEndDateUTC`.
*
* @see \Tribe__Events__Repositories__Event::use_utc()
*
* @var string
*/
protected $end_meta_key = '_EventEndDateUTC';
/**
* The timezone object that should be used to normalize dates.
*
* Defaults to the UTC timezone.
*
* @see \Tribe__Events__Repositories__Event::use_utc()
*
* @var \DateTimeZone
*/
protected $normal_timezone;
/**
* Whether the use of UTC times for events filtering and ordering is being forced by means of a `use_utc` call
* or not.
*
* @since 4.9.7
*
* @var bool
*/
protected $using_utc;
/**
* Tribe__Events__Repositories__Event constructor.
*
* Sets up the repository default parameters and schema.
*
* @since 4.9
*/
public function __construct() {
parent::__construct();
/**
* Depending on the setting used to present event on the site the timezone used to normalize
* events and the keys used to sort them will be different.
* This initial setting can be reverted on a per-instance base using the `use_utc` method.
*
* @see Tribe__Events__Repositories__Event::use_utc()
*/
if ( Timezones::is_mode( 'site' ) ) {
$this->normal_timezone = new DateTimeZone( 'UTC' );
$this->start_meta_key = '_EventStartDateUTC';
$this->end_meta_key = '_EventEndDateUTC';
} else {
$this->normal_timezone = Timezones::build_timezone_object();
$this->start_meta_key = '_EventStartDate';
$this->end_meta_key = '_EventEndDate';
}
$this->create_args['post_type'] = Tribe__Events__Main::POSTTYPE;
$tribe_events_category = Tribe__Events__Main::TAXONOMY;
$this->taxonomies = [
$tribe_events_category,
'post_tag',
];
// Add event specific aliases.
$this->update_fields_aliases = array_merge(
$this->update_fields_aliases,
[
'start_date' => '_EventStartDate',
'end_date' => '_EventEndDate',
'start_date_utc' => '_EventStartDateUTC',
'end_date_utc' => '_EventEndDateUTC',
'duration' => '_EventDuration',
'all_day' => '_EventAllDay',
'timezone' => '_EventTimezone',
'venue' => '_EventVenueID',
'organizer' => '_EventOrganizerID',
'category' => $tribe_events_category,
'cost' => '_EventCost',
'currency_symbol' => '_EventCurrencySymbol',
'currency_position' => '_EventCurrencyPosition',
'show_map' => '_EventShowMap',
'show_map_link' => '_EventShowMapLink',
'url' => '_EventURL',
'hide_from_upcoming' => '_EventHideFromUpcoming',
// Where is "sticky"? It's handled in the meta filtering by setting `menu_order`.
'featured' => '_tribe_featured',
]
);
$this->default_args = [
'post_type' => Tribe__Events__Main::POSTTYPE,
'order' => 'ASC',
'order_by' => 'event_date',
// We'll be handling the dates, let's mark the query as a non-filtered one.
'tribe_suppress_query_filters' => true,
];
$this->schema = array_merge(
$this->schema,
[
'starts_before' => [ $this, 'filter_by_starts_before' ],
'starts_after' => [ $this, 'filter_by_starts_after' ],
'starts_on_or_after' => [ $this, 'filter_by_starts_on_or_after' ],
'starts_between' => [ $this, 'filter_by_starts_between' ],
'ends_before' => [ $this, 'filter_by_ends_before' ],
'ends_on_or_before' => [ $this, 'filter_by_ends_on_or_before' ],
'ends_after' => [ $this, 'filter_by_ends_after' ],
'ends_between' => [ $this, 'filter_by_ends_between' ],
'date_overlaps' => [ $this, 'filter_by_date_overlaps' ],
'starts_and_ends_between' => [ $this, 'filter_by_starts_and_ends_between' ],
'runs_between' => [ $this, 'filter_by_runs_between' ],
'all_day' => [ $this, 'filter_by_all_day' ],
'multiday' => [ $this, 'filter_by_multiday' ],
'on_calendar_grid' => [ $this, 'filter_by_on_calendar_grid' ],
'timezone' => [ $this, 'filter_by_timezone' ],
'featured' => [ $this, 'filter_by_featured' ],
'hidden' => [ $this, 'filter_by_hidden' ],
'linked_post' => [ $this, 'filter_by_linked_post' ],
'organizer' => [ $this, 'filter_by_organizer' ],
'sticky' => [ $this, 'filter_by_sticky' ],
'venue' => [ $this, 'filter_by_venue' ],
'cost_currency_symbol' => [ $this, 'filter_by_cost_currency_symbol' ],
'cost' => [ $this, 'filter_by_cost' ],
'cost_between' => [ $this, 'filter_by_cost_between' ],
'cost_less_than' => [ $this, 'filter_by_cost_less_than' ],
'cost_greater_than' => [ $this, 'filter_by_cost_greater_than' ],
'on_date' => [ $this, 'filter_by_on_date' ],
'hidden_from_upcoming' => [ $this, 'filter_by_hidden_on_upcoming' ],
]
);
// Add backcompat aliases.
$this->schema['hide_upcoming'] = [ $this, 'filter_by_hidden' ];
$this->schema['start_date'] = [ $this, 'filter_by_starts_on_or_after' ];
$this->schema['end_date'] = [ $this, 'filter_by_ends_on_or_before' ];
$this->add_simple_meta_schema_entry( 'website', '_EventURL' );
$this->add_simple_tax_schema_entry( 'event_category', $tribe_events_category );
$this->add_simple_tax_schema_entry( 'event_category_not_in', $tribe_events_category, 'term_not_in' );
$this->add_simple_tax_schema_entry( 'category', $tribe_events_category );
$this->add_simple_tax_schema_entry( 'category_not_in', $tribe_events_category, 'term_not_in' );
$this->add_simple_tax_schema_entry( $tribe_events_category, $tribe_events_category );
$this->add_simple_tax_schema_entry( $tribe_events_category . '_not_in', $tribe_events_category, 'term_not_in' );
$this->add_simple_tax_schema_entry( 'tag', 'post_tag' );
$this->add_simple_tax_schema_entry( 'tag_not_in', 'post_tag', 'term_not_in' );
}
/**
* {@inheritdoc}
*/
public function by_args( array $args ) {
/**
* Some key arguments have been passed as arrays but will require unpacking.
* Due to the dynamic nature of the ORM implementation this is a curated list
* that should be updated here. Do not try to move this conditional unpacking logic
* in the ORM: this is an issue the proxy function should handle ad-hoc.
*/
$requiring_unpack = [ 'date_overlaps', 'runs_between' ];
foreach ( array_intersect( array_keys( $args ), $requiring_unpack ) as $key ) {
$this->by( $key, ...$args[ $key ] );
unset( $args[ $key ] );
}
return parent::by_args( $args );
}
/**
* Filters the event by their all-day status.
*
* @since 4.9
*
* @param bool $all_day Whether the events should be all-day or not.
*
* @return array|null An array of query arguments or null if modified with internal methods.
*/
public function filter_by_all_day( $all_day = true ) {
if ( (bool) $all_day ) {
$this->by( 'meta_in', '_EventAllDay', [ 'yes', '1' ] );
return null;
}
return [
'meta_query' => [
'by-all-day' => [
'not-exists' => [
'key' => '_EventAllDay',
'compare' => 'NOT EXISTS',
'value' => '#',
],
'relation' => 'OR',
'is-not-yes' => [
'key' => '_EventAllDay',
'compare' => 'NOT IN',
'value' => [ 'yes', '1' ],
],
],
],
];
}
/**
* Filters events whose start date occurs before the provided date; fetch is not inclusive.
*
* @since 4.9
*
* @param string|DateTime|int $datetime A `strtotime` parse-able string, a DateTime object or
* a timestamp.
* @param string|DateTimeZone $timezone A timezone string, UTC offset or DateTimeZone object;
* defaults to the site timezone; this parameter is ignored
* if the `$datetime` parameter is a DatTime object.
*
* @return array An array of arguments that should be added to the WP_Query object.
*/
public function filter_by_starts_before( $datetime, $timezone = null ) {
$date = Tribe__Date_Utils::build_date_object( $datetime, $timezone )
->setTimezone( $this->normal_timezone );
// If this is a UTC date, use our UTC field, else whatever was specified.
$key = $this->normal_timezone->getName() === 'UTC' ? '_EventStartDateUTC' : $this->start_meta_key;
return [
'meta_query' => [
'starts-before' => [
'key' => $key,
'compare' => '<',
'value' => $date->format( Tribe__Date_Utils::DBDATETIMEFORMAT ),
'type' => 'DATETIME',
],
],
];
}
/**
* Filters events whose end date occurs on or before the provided date; fetch is not inclusive.
*
* @since 4.9
*
* @param string|DateTime|int $datetime A `strtotime` parse-able string, a DateTime object or
* a timestamp.
* @param string|DateTimeZone $timezone A timezone string, UTC offset or DateTimeZone object;
* defaults to the site timezone; this parameter is ignored
* if the `$datetime` parameter is a DatTime object.
*
* @return array An array of arguments that should be added to the WP_Query object.
*/
public function filter_by_ends_on_or_before( $datetime, $timezone = null ) {
$date = Tribe__Date_Utils::build_date_object( $datetime, $timezone )
->setTimezone( $this->normal_timezone );
// If this is a UTC date, use our UTC field, else whatever was specified.
$key = $this->normal_timezone->getName() === 'UTC' ? '_EventEndDateUTC' : $this->end_meta_key;
return [
'meta_query' => [
'ends-before' => [
'key' => $key,
'compare' => '<=',
'value' => $date->format( Tribe__Date_Utils::DBDATETIMEFORMAT ),
'type' => 'DATETIME',
],
],
];
}
/**
* Filters events whose end date occurs before the provided date; fetch is inclusive.
*
* @since 4.9
*
* @param string|DateTime|int $datetime A `strtotime` parse-able string, a DateTime object or
* a timestamp.
* @param string|DateTimeZone $timezone A timezone string, UTC offset or DateTimeZone object;
* defaults to the site timezone; this parameter is ignored
* if the `$datetime` parameter is a DatTime object.
*
* @return array An array of arguments that should be added to the WP_Query object.
*/
public function filter_by_ends_before( $datetime, $timezone = null ) {
$date = Tribe__Date_Utils::build_date_object( $datetime, $timezone )
->setTimezone( $this->normal_timezone );
// If this is a UTC date, use our UTC field, else whatever was specified.
$key = $this->normal_timezone->getName() === 'UTC' ? '_EventEndDateUTC' : $this->end_meta_key;
return [
'meta_query' => [
'ends-before' => [
'key' => $key,
'compare' => '<',
'value' => $date->format( Tribe__Date_Utils::DBDATETIMEFORMAT ),
'type' => 'DATETIME',
],
],
];
}
/**
* Filters events whose start date occurs after the provided date; fetch is not inclusive.
*
* @since 4.9
*
* @param string|DateTime|int $datetime A `strtotime` parse-able string, a DateTime object or
* a timestamp.
* @param string|DateTimeZone $timezone A timezone string, UTC offset or DateTimeZone object;
* defaults to the site timezone; this parameter is ignored
* if the `$datetime` parameter is a DatTime object.
*
* @return array An array of arguments that should be added to the WP_Query object.
*/
public function filter_by_starts_after( $datetime, $timezone = null ) {
$date = Tribe__Date_Utils::build_date_object( $datetime, $timezone )
->setTimezone( $this->normal_timezone );
// If this is a UTC date, use our UTC field, else whatever was specified.
$key = $this->normal_timezone->getName() === 'UTC' ? '_EventStartDateUTC' : $this->start_meta_key;
return [
'meta_query' => [
'starts-after' => [
'key' => $key,
'compare' => '>',
'value' => $date->format( Tribe__Date_Utils::DBDATETIMEFORMAT ),
'type' => 'DATETIME',
],
],
];
}
/**
* Filters events whose start date occurs on or after the provided date; fetch is inclusive.
*
* @since 4.9
*
* @param string|DateTime|int $datetime A `strtotime` parse-able string, a DateTime object or
* a timestamp.
* @param string|DateTimeZone $timezone A timezone string, UTC offset or DateTimeZone object;
* defaults to the site timezone; this parameter is ignored
* if the `$datetime` parameter is a DatTime object.
*
* @return array An array of arguments that should be added to the WP_Query object.
*/
public function filter_by_starts_on_or_after( $datetime, $timezone = null ) {
$date = Tribe__Date_Utils::build_date_object( $datetime, $timezone )
->setTimezone( $this->normal_timezone );
// If this is a UTC date, use our UTC field, else whatever was specified.
$key = $this->normal_timezone->getName() === 'UTC' ? '_EventStartDateUTC' : $this->start_meta_key;
return [
'meta_query' => [
'starts-after' => [
'key' => $key,
'compare' => '>=',
'value' => $date->format( Tribe__Date_Utils::DBDATETIMEFORMAT ),
'type' => 'DATETIME',
],
],
];
}
/**
* Filters events whose end date occurs after the provided date; fetch is not inclusive.
*
* @since 4.9
*
* @param string|DateTime|int $datetime A `strtotime` parse-able string, a DateTime object or
* a timestamp.
* @param string|DateTimeZone $timezone A timezone string, UTC offset or DateTimeZone object;
* defaults to the site timezone; this parameter is ignored
* if the `$datetime` parameter is a DatTime object.
*
* @return array An array of arguments that should be added to the WP_Query object.
*/
public function filter_by_ends_after( $datetime, $timezone = null ) {
$date = Tribe__Date_Utils::build_date_object( $datetime, $timezone )
->setTimezone( $this->normal_timezone );
// If this is a UTC date, use our UTC field, else whatever was specified.
$key = $this->normal_timezone->getName() === 'UTC' ? '_EventEndDateUTC' : $this->end_meta_key;
return [
'meta_query' => [
'ends-after' => [
'key' => $key,
'compare' => '>',
'value' => $date->format( Tribe__Date_Utils::DBDATETIMEFORMAT ),
'type' => 'DATETIME',
],
],
];
}
/**
* Filters events whose duration overlaps a given Start and End date; fetch is inclusive
* Will include multi-day events.
*
* @since 4.9
* @since 4.9.11 Add the `$min_sec_overlap` parameter.
*
* @param string|DateTime|int $start_datetime A `strtotime` parse-able string, a DateTime object or
* a timestamp.
* @param string|DateTime|int $end_datetime A `strtotime` parse-able string, a DateTime object or
* a timestamp.
* @param string|DateTimeZone $timezone A timezone string, UTC offset or DateTimeZone object;
* defaults to the site timezone; this parameter is ignored
* if the `$datetime` parameter is a DatTime object.
* @param null|int $min_sec_overlap The minimum overlap, in seconds, an event should have with the
* interval; defaults to at least a second.
*/
public function filter_by_date_overlaps( $start_datetime, $end_datetime, $timezone = null, $min_sec_overlap = 1 ) {
global $wpdb;
$utc = $this->normal_timezone;
$lower = Tribe__Date_Utils::build_date_object( $start_datetime, $timezone )->setTimezone( $utc );
$upper = Tribe__Date_Utils::build_date_object( $end_datetime, $timezone )->setTimezone( $utc );
$lower_string = $lower->format( Tribe__Date_Utils::DBDATETIMEFORMAT );
$upper_string = $upper->format( Tribe__Date_Utils::DBDATETIMEFORMAT );
$start_key = $this->start_meta_key;
$end_key = $this->end_meta_key;
$join_start_key = 'tribe_start_date_utc';
$join_end_key = 'tribe_end_date_utc';
$this->filter_query->join(
"LEFT JOIN {$wpdb->postmeta} {$join_start_key}
ON ( {$wpdb->posts}.ID = {$join_start_key}.post_id
AND {$join_start_key}.meta_key = '{$start_key}' )"
);
$this->filter_query->join(
"LEFT JOIN {$wpdb->postmeta} {$join_end_key}
ON ( {$wpdb->posts}.ID = {$join_end_key}.post_id
AND {$join_end_key}.meta_key = '{$end_key}' )"
);
$alt_where = $wpdb->prepare(
"(
TIMESTAMPDIFF ( SECOND, {$join_start_key}.meta_value, '{$upper_string}' ) >= %d
AND
TIMESTAMPDIFF ( SECOND, '{$lower_string}', {$join_end_key}.meta_value ) >= %d
)",
$min_sec_overlap,
$min_sec_overlap
);
$this->filter_query->where( $alt_where );
}
/**
* Filters events whose start date occurs between a set of dates; fetch is inclusive.
*
* @since 4.9
*
* @param string|DateTime|int $start_datetime A `strtotime` parse-able string, a DateTime object or
* a timestamp.
* @param string|DateTime|int $end_datetime A `strtotime` parse-able string, a DateTime object or
* a timestamp.
* @param string|DateTimeZone $timezone A timezone string, UTC offset or DateTimeZone object;
* defaults to the site timezone; this parameter is ignored
* if the `$datetime` parameter is a DatTime object.
*/
public function filter_by_starts_between( $start_datetime, $end_datetime, $timezone = null ) {
$utc = $this->normal_timezone;
$lower = Tribe__Date_Utils::build_date_object( $start_datetime, $timezone )->setTimezone( $utc );
$upper = Tribe__Date_Utils::build_date_object( $end_datetime, $timezone )->setTimezone( $utc );
// If this is a UTC date, use our UTC field, else whatever was specified.
$key = $utc->getName() === 'UTC' ? '_EventStartDateUTC' : $this->start_meta_key;
$this->by(
'meta_between',
$key,
[
$lower->format( Tribe__Date_Utils::DBDATETIMEFORMAT ),
$upper->format( Tribe__Date_Utils::DBDATETIMEFORMAT ),
],
'DATETIME'
);
}
/**
* Filters events whose end date occurs between a set of dates; fetch is inclusive.
*
* @since 4.9
*
* @param string|DateTime|int $start_datetime A `strtotime` parse-able string, a DateTime object or
* a timestamp.
* @param string|DateTime|int $end_datetime A `strtotime` parse-able string, a DateTime object or
* a timestamp.
* @param string|DateTimeZone $timezone A timezone string, UTC offset or DateTimeZone object;
* defaults to the site timezone; this parameter is ignored
* if the `$datetime` parameter is a DatTime object.
*/
public function filter_by_ends_between( $start_datetime, $end_datetime, $timezone = null ) {
$utc = $this->normal_timezone;
$lower = Tribe__Date_Utils::build_date_object( $start_datetime, $timezone )->setTimezone( $utc );
$upper = Tribe__Date_Utils::build_date_object( $end_datetime, $timezone )->setTimezone( $utc );
// If this is a UTC date, use our UTC field, else whatever was specified.
$key = $utc->getName() === 'UTC' ? '_EventEndDateUTC' : $this->end_meta_key;
$this->by(
'meta_between',
$key,
[
$lower->format( Tribe__Date_Utils::DBDATETIMEFORMAT ),
$upper->format( Tribe__Date_Utils::DBDATETIMEFORMAT ),
],
'DATETIME'
);
}
/**
* Filters events to include only those that match the provided multi day state.
*
* Please note that an event might be multi-day in its timezone but not in another;
* this filter will make the check on the event times localized to the event timezone.
* Furthermore the end of day cutoff is taken into account so, given a cutoff of 10PM
* an event starting at 10:30PM and ending at 11AM is not multi-day.
*
* @since 4.9
*
* @param bool $multiday Whether to filter by events that are or not multi-day.
*/
public function filter_by_multiday( $multiday = true ) {
global $wpdb;
$this->filter_query->join( "LEFT JOIN {$wpdb->postmeta} multiday_start_date
ON ( {$wpdb->posts}.ID = multiday_start_date.post_id
AND multiday_start_date.meta_key = '_EventStartDate' )" );
$this->filter_query->join( "LEFT JOIN {$wpdb->postmeta} multiday_end_date
ON ( {$wpdb->posts}.ID = multiday_end_date.post_id
AND multiday_end_date.meta_key = '_EventEndDate' )" );
// We're interested in the time only.
$end_of_day_cutoff = tribe_end_of_day( 'today', 'H:i:s' );
if ( '23:59:59' === $end_of_day_cutoff ) {
/*
* An event is considered multi-day when the end date is not the same as the start date when
* using the "natural" end-of-day cutoff.
*/
$compare = $multiday ? '!=' : '=';
$this->filter_query->where(
"DATE( multiday_end_date.meta_value ) {$compare} DATE( multiday_start_date.meta_value )"
);
} else {
/*
* An event is considered multi-day when the end date is after the end-of-day cutoff of the start date.
* Since the cut-off moves forward from midnight add 1 day to the start date.
*/
$compare = $multiday ? '>' : '<=';
$this->filter_query->where(
"multiday_end_date.meta_value {$compare} DATE_FORMAT( DATE_ADD( multiday_start_date.meta_value, INTERVAL 1 DAY ) , '%Y-%m-%d {$end_of_day_cutoff}' )"
);
}
}
/**
* Filters events to include only those events that appear on the given month’s calendar grid.
*
* @since 4.9
*
* @param int $month The month to display.
* @param int $year The year to display.
*
* @return array|null An array of arguments that should be added to the query or `null`
* if the arguments are not valid (thus the filter will be ignored).
*/
public function filter_by_on_calendar_grid( $month, $year ) {
$year_month_string = "{$year}-{$month}";
if ( ! Tribe__Date_Utils::is_valid_date( $year_month_string ) ) {
/*
* Months and years are known but, at runtime, the client code might get, or pass,
* them wrong. In that case this filter will not be applied.
*/
return null;
}
$start = \Tribe\Events\Views\V2\Views\Month_View::calculate_first_cell_date( $year_month_string );
$end = \Tribe\Events\Views\V2\Views\Month_View::calculate_final_cell_date( $year_month_string );
return $this->filter_by_runs_between( $start, tribe_end_of_day( $end ) );
}
/**
* Filters events to include only those events that are running between two dates.
*
* An event is running between two dates when its start date or end date are between
* the two dates.
*
* @since 4.9
*
* @param string|DateTime|int $start_datetime A `strtotime` parse-able string, a DateTime object or
* a timestamp.
* @param string|DateTime|int $end_datetime A `strtotime` parse-able string, a DateTime object or
* a timestamp.
* @param string|DateTimeZone $timezone A timezone string, UTC offset or DateTimeZone object;
* defaults to the site timezone; this parameter is ignored
* if the `$datetime` parameter is a DatTime object.
*
* @return array An array of arguments that should be added to the WP_Query object.
*/
public function filter_by_runs_between( $start_datetime, $end_datetime, $timezone = null ) {
$start_date = Tribe__Date_Utils::build_date_object( $start_datetime, $timezone )
->setTimezone( $this->normal_timezone )
->format( Tribe__Date_Utils::DBDATETIMEFORMAT );
$end_date = Tribe__Date_Utils::build_date_object( $end_datetime, $timezone )
->setTimezone( $this->normal_timezone )
->format( Tribe__Date_Utils::DBDATETIMEFORMAT );
// If this is a UTC date, use our UTC field, else whatever was specified.
$start_key = $this->normal_timezone->getName() === 'UTC' ? '_EventStartDateUTC' : $this->start_meta_key;
$end_key = $this->normal_timezone->getName() === 'UTC' ? '_EventEndDateUTC' : $this->end_meta_key;
return [
'meta_query' => [
'runs-between' => [
'starts' => [
'after-the-start' => [
'key' => $start_key,
'value' => $start_date,
'compare' => '>=',
'type' => 'DATETIME',
],
'relation' => 'AND',
'before-the-end' => [
'key' => $start_key,
'value' => $end_date,
'compare' => '<=',
'type' => 'DATETIME',
],
],
'relation' => 'OR',
'ends' => [
'after-the-start' => [
'key' => $end_key,
'value' => $start_date,
'compare' => '>=',
'type' => 'DATETIME',
],
'relation' => 'AND',
'before-the-end' => [
'key' => $end_key,
'value' => $end_date,
'compare' => '<=',
'type' => 'DATETIME',
],
],
],
],
];
}
/**
* Filters events the given timezone.
*
* UTC, UTC+0, and UTC-0 should be parsed as the same timezone.
*
* @since 4.9
*
* @param string|DateTimeZone $timezone A timezone string or object.
*
* @return array An array of arguments to apply to the query.
*/
public function filter_by_timezone( $timezone ) {
if ( $timezone instanceof DateTimeZone ) {
$timezone = $timezone->getName();
}
$is_utc = preg_match( '/^UTC((\\+|-)0)*$/i', $timezone );
if ( $is_utc ) {
return [
'meta_query' => [
'by-timezone' => [
'key' => '_EventTimezone',
'compare' => 'IN',
'value' => [ 'UTC', 'UTC+0', 'UTC-0' ],
],
],
];
}
return [
'meta_query' => [
'by-timezone' => [
'key' => '_EventTimezone',
'value' => $timezone,
],
],
];
}
/**
* Filters events whose start and end dates occur between a set of dates.
*
* Fetch is inclusive.
*
* @since 4.9
*
* @param string|DateTime|int $start_datetime A `strtotime` parse-able string, a DateTime object or
* a timestamp.
* @param string|DateTime|int $end_datetime A `strtotime` parse-able string, a DateTime object or
* a timestamp.
* @param string|DateTimeZone $timezone A timezone string, UTC offset or DateTimeZone object;
* defaults to the site timezone; this parameter is ignored
* if the `$datetime` parameter is a DatTime object.
*
* @return array An array of arguments that should be added to the WP_Query object.
*/
public function filter_by_starts_and_ends_between( $start_datetime, $end_datetime, $timezone = null ) {
$start_date = Tribe__Date_Utils::build_date_object( $start_datetime, $timezone )
->setTimezone( $this->normal_timezone )
->format( Tribe__Date_Utils::DBDATETIMEFORMAT );
$end_date = Tribe__Date_Utils::build_date_object( $end_datetime, $timezone )
->setTimezone( $this->normal_timezone )
->format( Tribe__Date_Utils::DBDATETIMEFORMAT );
$interval = [ $start_date, $end_date ];
// If this is a UTC date, use our UTC field, else whatever was specified.
$start_key = $this->normal_timezone->getName() === 'UTC' ? '_EventStartDateUTC' : $this->start_meta_key;
$end_key = $this->normal_timezone->getName() === 'UTC' ? '_EventEndDateUTC' : $this->end_meta_key;
return [
'meta_query' => [
'starts-ends-between' => [
'starts-between' => [
'key' => $start_key,
'value' => $interval,
'compare' => 'BETWEEN',
'type' => 'DATETIME',
],
'relation' => 'AND',
'ends-between' => [
'key' => $end_key,
'value' => $interval,
'compare' => 'BETWEEN',
'type' => 'DATETIME',
],
],
],
];
}
/**
* Filters events to include only those that match the provided featured state.
*
* @since 4.9
*
* @param bool $featured Whether the events should be featured or not.
*/
public function filter_by_featured( $featured = true ) {
$this->by( (bool) $featured ? 'meta_exists' : 'meta_not_exists', Tribe__Events__Featured_Events::FEATURED_EVENT_KEY, '#' );
}
/**
* Filters events to include only those that match the provided hidden state.
*
* @since 4.9
*
* @param bool $hidden Whether the events should be hidden or not.
*/
public function filter_by_hidden( $hidden = true ) {
$this->by( (bool) $hidden ? 'meta_exists' : 'meta_not_exists', '_EventHideFromUpcoming', '#' );
}
/**
* Filters events by specific event organizer(s).
*
* @since 4.9
*
* @param int|WP_Post|array $organizer Organizer(s).
*/
public function filter_by_organizer( $organizer ) {
$this->filter_by_linked_post( '_EventOrganizerID', $organizer );
}
/**
* Filters events to include only those that match the provided hidden state.
*
* @since 4.9
*
* @param string $linked_post_meta_key The linked post type meta key.
* @param int|WP_Post|array $linked_post Linked post(s).
*/
public function filter_by_linked_post( $linked_post_meta_key, $linked_post ) {
$linked_posts = (array) $linked_post;
$post_ids = array_map( [ 'Tribe__Main', 'post_id_helper' ], $linked_posts );
$post_ids = array_filter( $post_ids );
$post_ids = array_unique( $post_ids );
$this->by( 'meta_in', $linked_post_meta_key, $post_ids );
}
/**
* Filters events to include only those that match the provided sticky state.
*
* @since 4.9
*
* @param bool $sticky Whether the events should be sticky or not.
*/
public function filter_by_sticky( $sticky = true ) {
// Support negative menu_order lookups.
add_action( 'pre_get_posts', [ $this, 'support_negative_menu_order' ] );
$this->menu_order = (bool) $sticky ? - 1 : 0;
$this->by( 'menu_order', $this->menu_order );
}
/**
* Filters events by specific event venue(s).
*
* @since 4.9
*
* @param int|WP_Post|array $venue Venue(s).
*/
public function filter_by_venue( $venue ) {
$this->filter_by_linked_post( '_EventVenueID', $venue );
}
/**
* Hook into WP_Query pre_get_posts and support negative menu_order values.
*
* @param WP_Query $query Query object.
*/
public function support_negative_menu_order( $query ) {
// Send in the unmodified menu_order.
$query->query_vars['menu_order'] = (int) $this->menu_order;
// Remove hook.
remove_action( 'pre_get_posts', [ $this, 'support_negative_menu_order' ] );
}
/**
* Filters events that have a cost relative to the given value based on the $comparator.
* If Event Tickets is active, rather than looking at the event cost, all tickets attached
* to the event should used to reference cost; the event cost meta will be ignored.
*
* Providing the symbol parameter should limit event results to only those events whose cost is relative to
* the value AND the currency symbol matches. This way you can select posts that have a cost of 5 USD and
* not accidentally get events with 5 EUR.
*
* @since 4.9
*
* @param float|array $value The cost to use for the comparison; in the case of `BETWEEN`, `NOT BETWEEN`,
* `IN` and `NOT IN` operators this value should be an array.
* @param string $operator The comparison operator to use for the comparison, one of `<`, `<=`, `>`, `>=`,
* `=`, `BETWEEN`, `NOT BETWEEN`, `IN`, `NOT IN`.
* @param string $symbol The desired currency symbol or symbols; this symbol can be a currency ISO code,
* e.g. "USD" for U.S. dollars, or a currency symbol, e.g. "$".
* In the latter case results will include any event with the matching currency
* symbol, this might lead to ambiguous results.
*
* @return array An array of query arguments that will be added to the main query.
*
* @throws Tribe__Repository__Usage_Error If the comparison operator is not supported of is using the `BETWEEN`,
* `NOT BETWEEN` operators without passing a two element array `$value`.
*/
public function filter_by_cost( $value, $operator = '=', $symbol = null ) {
if ( ! in_array(
$operator,
[
'<',
'<=',
'>',
'>=',
'=',
'!=',
'BETWEEN',
'NOT BETWEEN',
'IN',
'NOT IN',
]
) ) {
throw Tribe__Repository__Usage_Error::because_this_comparison_operator_is_not_supported(
$operator,
'filter_by_cost'
);
}
if (
in_array(
$operator,
[
'BETWEEN',
'NOT BETWEEN',
]
)
&& ! ( is_array( $value ) && 2 === count( $value ) )
) {
throw Tribe__Repository__Usage_Error::because_this_comparison_operator_requires_an_value_of_type(
$operator,
'filter_by_cost',
'array'
);
}
if ( in_array( $operator, [ 'IN', 'NOT IN' ] ) ) {
$value = array_map( 'floatval', (array) $value );
}
$operator_name = Tribe__Utils__Array::get( self::$comparison_operators, $operator, '' );
$meta_query_key = 'by-cost-' . $operator_name;
// Do not add ANY spacing in the type: WordPress will only accept this format!
$meta_query_entry = [
$meta_query_key => [
'key' => '_EventCost',
'value' => is_array( $value ) ? $value : (float) $value,
'compare' => $operator,
'type' => 'DECIMAL(10,5)',
],
];
if ( null !== $symbol ) {
$meta_query_entry = array_merge( $meta_query_entry, $this->filter_by_cost_currency_symbol( $symbol )['meta_query'] );
}
return [ 'meta_query' => $meta_query_entry ];
}
/**
* Filters events that have a specific cost currency symbol.
*
* Events with a cost of `0` but a currency symbol set will be fetched when fetching
* by their symbols.
*
* @since 4.9
*
* @param string|array $symbol One or more currency symbols or currency ISO codes. E.g.
* "$" and "USD".
*
* @return array An array of arguments that will be added to the current query.
*/
public function filter_by_cost_currency_symbol( $symbol ) {
return [
'meta_query' => [
'by-cost-currency-symbol' => [
'key' => '_EventCurrencySymbol',
'value' => array_unique( (array) $symbol ),
'compare' => 'IN',
],
],
];
}
/**
* Filters events that have a cost between two given values.
*
* Cost search is inclusive.
*
* @since 4.9
*
* @param float $low The lower value of the search interval.
* @param float $high The high value of the search interval.
* @param string $symbol The desired currency symbol or symbols; this symbol can be a currency ISO code,
* e.g. "USD" for U.S. dollars, or a currency symbol, e.g. "$".
* In the latter case results will include any event with the matching currency symbol,
* this might lead to ambiguous results.
*
* @return array An array of query arguments that will be added to the main query.
*/
public function filter_by_cost_between( $low, $high, $symbol = null ) {
return $this->by( 'cost', [ $low, $high ], 'BETWEEN', $symbol );
}
/**
* Filters events that have a cost greater than the given value.
*
* Cost search is NOT inclusive.
*
* @since 4.9
*
* @param float $value The cost to use for the comparison.
* @param string $symbol The desired currency symbol or symbols; this symbol can be a currency ISO code,
* e.g. "USD" for U.S. dollars, or a currency symbol, e.g. "$".
* In the latter case results will include any event with the matching currency symbol,
* this might lead to ambiguous results.
*
* @return array An array of query arguments that will be added to the main query.
*/
public function filter_by_cost_greater_than( $value, $symbol = null ) {
return $this->by( 'cost', $value, '>', $symbol );
}
/**
* Filters events that have a cost less than the given value.
*
* Cost search is NOT inclusive.
*
* @since 4.9
*
* @param float $value The cost to use for the comparison.
* @param string $symbol The desired currency symbol or symbols; this symbol can be a currency ISO code,
* e.g. "USD" for U.S. dollars, or a currency symbol, e.g. "$".
* In the latter case results will include any event with the matching currency symbol,
* this might lead to ambiguous results.
*
* @return array An array of query arguments that will be added to the main query.
*/
public function filter_by_cost_less_than( $value, $symbol = null ) {
$this->by( 'cost', $value, '<', $symbol );
}
/**
* {@inheritdoc}
*/
public function filter_postarr_for_update( array $postarr, $post_id ) {
if ( isset( $postarr['meta_input'] ) ) {
$postarr = $this->filter_meta_input( $postarr, $post_id );
}
return parent::filter_postarr_for_update( $postarr, $post_id );
}
/**
* Filters and updates the event meta to make sure it makes sense.
*
* @since 4.9
*
* @param array $postarr The update post array, passed entirely for context purposes.
* @param int $post_id The ID of the event that's being updated.
*
* @return array The filtered postarr array.
*/
protected function filter_meta_input( array $postarr, $post_id = null ) {
$postarr = $this->update_date_meta( $postarr, $post_id );
$postarr = $this->update_linked_post_meta( $postarr );
$postarr = $this->update_accessory_meta( $postarr, $post_id );
return $postarr;
}
/**
*
*
* @since 4.9
*
* @param array $postarr
* @param $post_id
*
* @return array
*/
protected function update_date_meta( array $postarr, $post_id = null ) {
set_error_handler( [ $this, 'cast_error_to_exception' ] );
$was_all_day = (bool) get_post_meta( $post_id, '_EventAllDay', true );
$is_all_day = false;
if ( isset( $postarr['meta_input']['_EventAllDay'] ) && tribe_is_truthy( $postarr['meta_input']['_EventAllDay'] ) ) {
$postarr['meta_input']['_EventAllDay'] = 'yes';
$is_all_day = true;
} else {
unset( $postarr['meta_input']['_EventAllDay'] );
}
try {
$meta = $postarr['meta_input'];
$current_event_timezone_string = Tribe__Events__Timezones::get_event_timezone_string( $post_id );
$input_timezone = Tribe__Utils__Array::get(
$meta,
'_EventTimezone',
$current_event_timezone_string
);
// Empty strings will use the site timezone.
$input_timezone = $input_timezone ?: $current_event_timezone_string;
$timezone = Tribe__Timezones::build_timezone_object( $input_timezone );
$timezone_changed = $input_timezone !== $current_event_timezone_string;
$utc = new DateTimezone('UTC');
$dates_changed = [];
/**
* If both local date/time and UTC date/time are provided then the local one overrides the UTC one.
* If only one is provided the other one will be calculated and updated.
*/
$datetime_format = Tribe__Date_Utils::DBDATETIMEFORMAT;
foreach ( [ 'Start', 'End' ] as $check ) {
if ( isset( $meta[ "_Event{$check}Date" ] ) ) {
$meta_value = $meta[ "_Event{$check}Date" ];
$is_object = $meta_value instanceof DateTime
|| ( class_exists( 'DateTimeImmutable' ) && $meta_value instanceof DateTimeImmutable );
if ( $is_object ) {
$meta_value = $meta_value->format( Tribe__Date_Utils::DBDATETIMEFORMAT );
$postarr[ 'meta_input' ][ "_Event{$check}Date" ] = $meta_value;
}
$date = new DateTime( $meta_value, $timezone );
$postarr['meta_input']["_Event{$check}Date"] = $date->format( $datetime_format );
$utc_date = $date->setTimezone( $utc );
// Set the localized and UTC date/time from local date/time and timezone; if provided override it.
$postarr[ 'meta_input' ][ "_Event{$check}DateUTC" ] = $utc_date->format( $datetime_format );
$dates_changed[ $check ] = $utc_date;
}
/*
* If the UTC date is provided in place of the local date/time then build the
* local date/time.
*/
if ( empty( $utc_date ) && isset( $meta[ "_Event{$check}DateUTC" ] ) ) {
$utc_date = new DateTime( $meta[ "_Event{$check}DateUTC" ], $utc );
$the_date = clone $utc_date;
$the_date->setTimezone( $timezone )->format( $datetime_format );
$postarr[ 'meta_input' ][ "_Event{$check}Date" ] = $the_date;
$dates_changed[ $check ] = $utc_date;
}
}
if ( $timezone_changed && ! count( $dates_changed ) ) {
$start_string = get_post_meta( $post_id, '_EventStartDate', true );
$end_string = get_post_meta( $post_id, '_EventEndDate', true );
$start_date = Tribe__Date_Utils::build_date_object( $start_string, $timezone );
$end_date = Tribe__Date_Utils::build_date_object( $end_string, $timezone );
$postarr['meta_input']['_EventStartDateUTC'] = $start_date->setTimezone( $utc )->format( $datetime_format );
$postarr['meta_input']['_EventEndDateUTC'] = $end_date->setTimezone( $utc )->format( $datetime_format );
}
// Sanity check, an event should end after its start.
$start = $this->get_from_postarr_or_meta( $postarr, '_EventStartDate', $post_id );
$end = $this->get_from_postarr_or_meta( $postarr, '_EventEndDate', $post_id );
$duration = $this->get_from_postarr_or_meta( $postarr, '_EventDuration', $post_id );
if ( isset( $start, $duration ) && empty( $end ) ) {
// Let's work out the End from Start and Duration if not set.
$duration_interval = new DateInterval( 'PT' . (int) $duration . 'S' );
$end = Dates::build_date_object( $start, $timezone )
->add( $duration_interval )
->format( $datetime_format );
}
$dates_make_sense = true;
/*
* To support both "punctual" (i.e. start === end) events and all-day events (that might be punctual at
* this stage) do not make this check inclusive.
*/
if ( Tribe__Date_Utils::build_date_object( $end ) < Tribe__Date_Utils::build_date_object( $start ) ) {
unset(
$postarr['meta_input']['_EventStartDate'],
$postarr['meta_input']['_EventStartDateUTC'],
$postarr['meta_input']['_EventEndDate'],
$postarr['meta_input']['_EventEndDateUTC'],
$postarr['meta_input']['_EventDuration'],
$postarr['meta_input']['_EventTimezone']
);
$dates_make_sense = false;
}
if ( $dates_make_sense && 2 === count( $dates_changed ) ) {
/*
* If the dates are changed then update the duration to the new one; if the duration is set
* in the postarr it will be overridden.
*/
list( $start, $end ) = array_values( $dates_changed );
$postarr['meta_input']['_EventDuration'] = $end->getTimestamp() - $start->getTimestamp();
} elseif ( isset( $meta['_EventDuration'] ) ) {
if ( isset( $dates_changed['Start'] ) ) {
// If we have a duration and the start changed update the end.
$end_timestamp = $dates_changed['Start']->getTimestamp() + $meta['_EventDuration'];
$the_end = clone $dates_changed['Start'];
$the_end->setTimestamp( $end_timestamp );
$postarr['meta_input']['_EventEndDate'] = $the_end
->setTimezone( $timezone )
->format( $datetime_format );
$postarr['meta_input']['_EventEndDateUTC'] = $the_end
->setTimezone( $utc )
->format( $datetime_format );
} elseif ( isset( $dates_changed['End'] ) ) {
// If we have a duration and the end changed update the start.
$start_timestamp = $dates_changed['End']->getTimestamp() - $meta['_EventDuration'];
$the_start = clone $dates_changed['End'];
$the_start->setTimestamp( $start_timestamp );
$postarr['meta_input']['_EventStartDate'] = $the_start->format( $datetime_format );
$postarr['meta_input']['_EventStartDateUTC'] = $the_start
->setTimezone( $utc )
->format( $datetime_format );
}
}
// After all this, if the event is all day recalculate start and end.
if ( $is_all_day && ! $was_all_day ) {
// Create the start date object and set it to the end of day.
$event_start_date = $this->get_from_postarr_or_meta( $postarr, '_EventStartDate', $post_id );
$event_end_date = $this->get_from_postarr_or_meta( $postarr, '_EventEndDate', $post_id );
$start = new DateTime( tribe_beginning_of_day( $event_start_date ), $timezone );
$end = new DateTime( tribe_end_of_day( $event_end_date ), $timezone );
$postarr['meta_input']['_EventStartDate'] = $start->format( $datetime_format );
$postarr['meta_input']['_EventStartDateUTC'] = $start->setTimezone( $utc )->format( $datetime_format );
$postarr['meta_input']['_EventEndDate'] = $end->format( $datetime_format );
$postarr['meta_input']['_EventEndDateUTC'] = $end->setTimezone( $utc )->format( $datetime_format );
}
$postarr['meta_input']['_EventTimezoneAbbr'] = Tribe__Timezones::abbr(
$this->get_from_postarr_or_meta( $postarr, '_EventStartDate' ),
$timezone->getName()
);
$postarr['meta_input']['_EventTimezone'] = Timezones::build_timezone_object( $input_timezone )->getName();
} catch ( Exception $e ) {
tribe( 'logger' )->log(
'There was an error updating the dates for event ' . $post_id . ': ' . $e->getMessage(),
Tribe__Log::ERROR,
__CLASS__
);
// Something went wrong, let's not update dates at all.
unset(
$postarr['meta_input']['_EventStartDate'],
$postarr['meta_input']['_EventStartDateUTC'],
$postarr['meta_input']['_EventEndDate'],
$postarr['meta_input']['_EventEndDateUTC'],
$postarr['meta_input']['_EventDuration'],
$postarr['meta_input']['_EventTimezone'],
$postarr['meta_input']['_EventAllDay']
);
}
restore_error_handler();
return $postarr;
}
/**
* Filters the post array to make sure linked posts meta makes sense.
*
* @since 4.9
*
* @param array $postarr The update post array.
*
* @return array The filtered event post array.
*/
protected function update_linked_post_meta( array $postarr ) {
// @todo [BTRIA-592]: Create linked posts here?! Using ORM?
if ( isset( $postarr['meta_input']['_EventVenueID'] ) && ! tribe_is_venue( $postarr['meta_input']['_EventVenueID'] ) ) {
unset( $postarr['meta_input']['_EventVenueID'] );
}
if ( isset( $postarr['meta_input']['_EventOrganizerID'] ) ) {
$postarr['meta_input']['_EventOrganizerID'] = (array) $postarr['meta_input']['_EventOrganizerID'];
$valid = [];
foreach ( $postarr['meta_input']['_EventOrganizerID'] as $organizer ) {
if ( ! tribe_is_organizer( $organizer ) ) {
continue;
}
$valid[] = $organizer;
}
if ( ! count( $valid ) ) {
unset( $postarr['meta_input']['_EventOrganizerID'] );
} else {
$this->unpack_meta_on_update( '_EventOrganizerID' );
// Pass this to the function to have this value passed to the closure later.
$postarr['meta_input']['_EventOrganizerID'] = $valid;
}
}
return $postarr;
}
/**
* Updates an event accessory meta and attributes.
*
* @since 4.9
*
* @param array $postarr The candidate post array for the update or insertion.
* @param int $post_id The ID of the event that is being updated.
*
* @return array The updated post array for update or insertion.
*/
protected function update_accessory_meta( array $postarr, $post_id ) {
$postarr['meta_input']['_EventOrigin'] = 'events-calendar';
// Set the map-related settings, default to `true` for new events.
foreach ( [ '_EventShowMap', '_EventShowMapLink' ] as $meta_key ) {
$new_value = tribe_is_truthy( $this->get_from_postarr_or_meta( $postarr, $meta_key, $post_id, true ) );
if ( $new_value !== tribe_is_truthy( get_post_meta( $post_id, $meta_key, true ) ) ) {
$postarr['meta_input'][ $meta_key ] = $new_value;
}
}
$currency_symbol_positions = [ 'prefix', 'postfix' ];
if ( isset( $postarr['meta_input']['_EventCurrencyPosition'] )
&& ! in_array( $postarr['meta_input']['_EventCurrencyPosition'], $currency_symbol_positions, true )
) {
$postarr['meta_input']['_EventCurrencyPosition'] = 'prefix';
}
if ( isset( $postarr['meta_input']['_EventHideFromUpcoming'] ) ) {
if ( tribe_is_truthy( $postarr['meta_input']['_EventHideFromUpcoming'] ) ) {
$postarr['meta_input']['_EventHideFromUpcoming'] = 'yes';
} else {
unset( $postarr['meta_input']['_EventHideFromUpcoming'] );
}
}
if ( isset( $postarr['meta_input']['sticky'] ) ) {
if ( tribe_is_truthy( $postarr['meta_input']['sticky'] ) ) {
$postarr['menu_order'] = - 1;
} else {
$postarr['menu_order'] = 0;
}
unset( $postarr['meta_input']['sticky'] );
}
if ( isset( $postarr['meta_input']['_tribe_featured'] ) ) {
if ( tribe_is_truthy( $postarr['meta_input']['_tribe_featured'] ) ) {
$postarr['meta_input']['_tribe_featured'] = true;
} else {
unset( $postarr['meta_input']['_tribe_featured'] );
}
}
return $postarr;
}
/**
* {@inheritdoc}
*/
public function filter_postarr_for_create( array $postarr ) {
// Before checking on the meta integrity and coherency let's try to normalize it an fill the missing fields.
$postarr = $this->filter_meta_input( $postarr );
// Require some minimum fields.
if ( ! isset(
$postarr['post_title'],
$postarr['meta_input']['_EventEndDate']
) ) {
return false;
}
return parent::filter_postarr_for_create( $postarr );
}
/**
* Returns a filtered list of filters that are leveraging the event start and/or
* end dates.
*
* @since 4.9
*
* @return array The filtered list of filters that are leveraging the event start and/or end dates
*/
public function get_date_filters() {
$date_filters = [
'starts_before',
'starts_after',
'starts_between',
'ends_before',
'ends_after',
'ends_between',
'starts_and_ends_between',
'runs_between',
'start_date',
];
/**
* Filters the list of filters that should be considered related to an event start and/or end
* dates.
*
* @since 4.9
*
* @param array $date_filters The list of filters that should be considered related to an event start and/or end
* dates.
* @param Tribe__Events__Repositories__Event This repository instance.
*/
return apply_filters( "tribe_repository_{$this->filter_name}_date_filters", $date_filters, $this );
}
/**
* Whether the repository read operations have any kind of date-related filter
* applied or not.
*
* @since 4.9
*
* @return bool Whether the repository read operations have any kind of date-related filter applied or not.
*/
public function has_date_filters() {
foreach ( $this->get_date_filters() as $filter ) {
if ( $this->has_filter( $filter ) ) {
return true;
}
}
return false;
}
/**
* Filters events to include only those that start on a specific date.
*
* This method is a wrapper for the `filter_by_starts_between` one.
*
* @since 4.9
*
* @param int|string|\DateTime $date A date and time timestamp, string or object.
* @param null $timezone The timezone that should be used to filter events, if not passed
* the site one will be used. This parameter will be ignored if the
* `$date` parameter is an object.
*
* @throws Exception If the date and/or timezone provided for the filtering are not valid.
*/
public function filter_by_on_date( $date, $timezone = null ) {
$timezone = Tribe__Timezones::build_timezone_object( $timezone );
$date = Tribe__Date_Utils::build_date_object( $date, $timezone );
$begin = new DateTime( tribe_beginning_of_day( $date->format( Tribe__Date_Utils::DBDATETIMEFORMAT ) ), $timezone );
$end = new DateTime( tribe_end_of_day( $date->format( Tribe__Date_Utils::DBDATETIMEFORMAT ) ), $timezone );
// Add on second to the previous day to get the start of this day.
$this->filter_by_starts_between( $begin, $end );
}
/**
* Instructs the repository to use UTC dates and times for reading operations or not.
*
* By default the repository will use the events `_EventStartDateUTC` and `_EventEndDateUTC` meta keys
* depending on the site Time Zone Settings.
* This method allows overriding this behavior on a per-instance basis.
*
* @since 4.9
*
* @param bool $use_utc Whether to use the UTC dates and times to read events or not. If `true` then the
* `_EventStartDateUTC` and `_EventEndDateUTC` meta keys will be used, if `false` then the
* `_EventStartDate` and `_EventEndDate` meta keys will be used.
*
* @return static This repository instance.
*/
public function use_utc( $use_utc ) {
$this->normal_timezone = $use_utc ?
new DateTimeZone( 'UTC' )
: Timezones::build_timezone_object();
$this->start_meta_key = $use_utc ? '_EventStartDateUTC' : '_EventStartDate';
$this->end_meta_key = $use_utc ? '_EventEndDateUTC' : '_EventEndDate';
$this->using_utc = (bool) $use_utc;
return $this;
}
/**
* {@inheritDoc}
*/
protected function format_item( $id ) {
$formatted = null === $this->formatter
? tribe_get_event( $id )
: $this->formatter->format_item( $id );
/**
* Filters a single formatted event result.
*
* @since 4.9.7
*
* @param mixed|WP_Post $formatted The formatted event result, usually a post object.
* @param int $id The formatted post ID.
* @param Tribe__Repository__Interface $this The current repository object.
*/
$formatted = apply_filters( 'tribe_repository_events_format_item', $formatted, $id, $this );
return $formatted;
}
/**
* Handles the `order_by` clauses for events
*
* @since 4.9.7
*
* @param string $order_by The key used to order events; e.g. `event_date` to order events by start date.
*/
public function handle_order_by( $order_by ) {
$check_orderby = $order_by;
if ( ! is_array( $check_orderby ) ) {
$check_orderby = explode( ' ', $check_orderby );
}
$timestamp_key = 'TIMESTAMP(mt1.meta_value)';
$after = false;
$loop = 0;
foreach ( $check_orderby as $key => $value ) {
$order_by = is_numeric( $key ) ? $value : $key;
$default_order = Arr::get_in_any( [ $this->query_args, $this->default_args ], 'order', 'ASC' );
$order = is_numeric( $key ) ? $default_order : $value;
// Let the first applied ORDER BY clause override the existing ones, then stack the ORDER BY clauses.
$override = $loop === 0;
switch ( $order_by ) {
case 'event_date':
$this->order_by_date( false, $order, $after, $override );
break;
case 'event_date_utc':
$this->order_by_date( true, $order, $after, $override );
break;
case 'event_duration':
$this->order_by_duration( $order, $after, $override );
break;
case 'organizer':
$this->order_by_organizer( $order, $after, $override );
break;
case 'venue':
$this->order_by_venue( $order, $after, $override );
break;
case $timestamp_key:
$this->filter_query->orderby( [ $timestamp_key => $default_order ], null, null, $after );
break;
case '__none':
unset( $this->query_args['orderby'] );
unset( $this->query_args['order'] );
break;
default:
$after = $after || 1 === $loop;
if ( empty( $this->query_args['orderby'] ) ) {
// In some versions of WP, [ $order_by, $order ] doesn't work as expected. Using explicit value setting instead.
$this->query_args['orderby'] = $order_by;
$this->query_args['order'] = $order;
} else {
$add = [ $order_by => $order ];
// Make sure all `orderby` clauses have the shape `<orderby> => <order>`.
$normalized = [];
if ( ! is_array( $this->query_args['orderby'] ) ) {
$this->query_args['orderby'] = [
$this->query_args['orderby'] => $this->query_args['order']
];
}
foreach ( $this->query_args['orderby'] as $k => $v ) {
$the_order_by = is_numeric( $k ) ? $v : $k;
$the_order = is_numeric( $k ) ? $default_order : $v;
$normalized[ $the_order_by ] = $the_order;
}
$this->query_args['orderby'] = $normalized;
$this->query_args['orderby'] = array_merge( $this->query_args['orderby'], $add );
}
break;
}
}
}
/**
* Overrides the base method to correctly handle the `order_by` clauses before.
*
* The Event repository handles ordering with some non trivial logic and some query filtering.
* To avoid the "stacking" of `orderby` clauses and filters the query filters are added at the very last moment,
* right before building the query.
*
* @since 4.9.7
*
* @return WP_Query The built query object.
*/
protected function build_query_internally() {
$order_by = Arr::get_in_any( [ $this->query_args, $this->default_args ], 'orderby', 'event_date' );
unset( $this->query_args['orderby'], $this->default_args['order_by'] );
$this->handle_order_by( $order_by );
return parent::build_query_internally();
}
/**
* Applies start-date-based ordering to the query.
*
* @since 4.9.7
* @since 4.9.11 Added the `$after` parameter.
*
* @param bool $use_utc Whether to use the events UTC start dates or their localized dates.
* @param string $order The order direction, either `ASC` or `DESC`; defaults to `null` to use the order
* specified in the current query or default arguments.
* @param bool $after Whether to append the order by clause to the ones managed by WordPress or not.
* Defaults to `false`,to prepend them to the ones managed by WordPress.
* @param bool $override Whether to override existing ORDER BY clauses or not; default to `true` to override
* existing ORDER BY clauses.
*/
protected function order_by_date( $use_utc, $order = null, $after = false, $override = true ) {
global $wpdb;
$meta_alias = 'event_date';
$meta_key = '_EventStartDate';
/**
* When the "Use site timezone everywhere" option is checked in events settings,
* the UTC time for event start and end times will be used. This filter allows the
* disabling of that in certain contexts, so that local (not UTC) event times are used.
*
* @since 4.6.10
*
* @param boolean $force_local_tz Whether to force the local TZ.
*/
$force_local_tz = apply_filters( 'tribe_events_query_force_local_tz', false );
if ( null === $this->using_utc ) {
/*
* The setting is not being forced by means of a call to the `use_utc` method.
* First we check if we've got a UTC ordering request in the `orderby` clause.
* After that if the use of the local (to the event) timezone is being forced by a filter.
* Finally if the timezone setting is set to use the site-wide timezone or not.
*/
if (
$use_utc
|| ( ! $force_local_tz && Tribe__Events__Timezones::is_mode( 'site' ) )
) {
$meta_alias = 'event_date_utc';
$meta_key = '_EventStartDateUTC';
}
} elseif ( true === $this->using_utc ) {
// The setting is being forced by means of a call to the `use_utc` method; ignore anything else.
$meta_alias = 'event_date_utc';
$meta_key = '_EventStartDateUTC';
}
$postmeta_table = "orderby_{$meta_alias}_meta";
$filter_id = 'order_by_date';
$this->filter_query->join(
$wpdb->prepare(
"
LEFT JOIN {$wpdb->postmeta} AS {$postmeta_table}
ON (
{$postmeta_table}.post_id = {$wpdb->posts}.ID
AND {$postmeta_table}.meta_key = %s
)
",
$meta_key
),
$filter_id,
true
);
$order = $order === null
? Arr::get_in_any( [ $this->query_args, $this->default_args ], 'order', 'ASC' )
: $order;
$this->filter_query->orderby( [ $meta_alias => $order ], $filter_id, $override, $after );
$this->filter_query->fields( "CAST( {$postmeta_table}.meta_value AS DATETIME ) AS {$meta_alias}", $filter_id, $override );
}
/**
* Applies Organizer-based ordering to the query.
*
* @since 4.9.7
* @since 4.9.11 Added the `$after` parameter.
*
* @param string $order The order direction, either `ASC` or `DESC`; defaults to `null` to use the order
* specified in the current query or default arguments.
* @param bool $after Whether to append the order by clause to the ones managed by WordPress or not.
* Defaults to `false`,to prepend them to the ones managed by WordPress.
* @param bool $override Whether to override existing ORDER BY clauses with this one or not; default to
* `true` to override existing ORDER BY clauses.
*/
protected function order_by_organizer( $order = null, $after = false, $override = true ) {
global $wpdb;
$postmeta_table = 'orderby_organizer_meta';
$posts_table = 'orderby_organizer_posts';
$meta_key = '_EventOrganizerID';
$this->filter_query->join(
$wpdb->prepare(
"
LEFT JOIN {$wpdb->postmeta} AS {$postmeta_table}
ON (
{$postmeta_table}.post_id = {$wpdb->posts}.ID
AND {$postmeta_table}.meta_key = %s
)
LEFT JOIN {$wpdb->posts} AS {$posts_table}
ON {$wpdb->posts}.ID = {$postmeta_table}.meta_value
",
$meta_key
)
);
$filter_id = 'order_by_organizer';
$order = $order === null
? Arr::get_in_any( [ $this->query_args, $this->default_args ], 'order', 'ASC' )
: $order;
$this->filter_query->orderby( [ 'organizer' => $order ], $filter_id, $override, $after );
$this->filter_query->fields( "{$posts_table}.post_title AS organizer", $filter_id, $override );
}
/**
* Applies Venue-based ordering to the query.
*
* @since 4.9.7
* @since 4.9.11 Added the `$after` parameter.
*
* @param string $order The order direction, either `ASC` or `DESC`; defaults to `null` to use the order
* specified in the current query or default arguments.
* @param bool $after Whether to append the order by clause to the ones managed by WordPress or not.
* Defaults to `false`,to prepend them to the ones managed by WordPress.
* @param bool $override Whether to override existing ORDER BY clauses with this one or not; default to
* `true` to override existing ORDER BY clauses.
*/
protected function order_by_venue( $order = null,$after = false, $override = true ) {
global $wpdb;
$postmeta_table = 'orderby_venue_meta';
$posts_table = 'orderby_venue_posts';
$meta_key = '_EventVenueID';
$this->filter_query->join(
$wpdb->prepare(
"
LEFT JOIN {$wpdb->postmeta} AS {$postmeta_table}
ON (
{$postmeta_table}.post_id = {$wpdb->posts}.ID
AND {$postmeta_table}.meta_key = %s
)
LEFT JOIN {$wpdb->posts} AS {$posts_table}
ON {$wpdb->posts}.ID = {$postmeta_table}.meta_value
",
$meta_key
)
);
$filter_id = 'order_by_venue';
$order = $order === null
? Arr::get_in_any( [ $this->query_args, $this->default_args ], 'order', 'ASC' )
: $order;
$this->filter_query->orderby( [ 'venue' => $order ], $filter_id, $override, $after );
$this->filter_query->fields( "{$posts_table}.post_title AS venue", $filter_id, $override );
}
/**
* Overrides the base method to default the `order` to `ASC` for events.
*
* @since 4.9.7
*
* @param string $order_by The key to order events by.
* @param string|null $order The order direction, either `ASC` or `DESC`; defaults to `ASC`.
*
* @return Tribe__Repository|Tribe__Repository__Read_Interface This repository instance.
*/
public function order_by( $order_by, $order = 'ASC' ) {
return parent::order_by( $order_by, $order );
}
/**
* Filters events by their "Hidden from Event Listings" status.
*
* This method assumes that we keep the following structure:
* - if an event should be hidden its `_EventHideFromUpcoming` meta will be set to `yes` (or another truthy value).
* - if an event should not be hidden its `_EventHideFromUpcoming` meta will not be set at all.
*
* @since 4.9.11
*
* @param bool $hidden Whether the events should be hidden from event listings or not.
*/
public function filter_by_hidden_on_upcoming( $hidden ) {
$hidden = tribe_is_truthy( $hidden );
$hidden_posts = tribe( \Tribe\Events\Views\V2\Query\Hide_From_Upcoming_Controller::class )->get_hidden_post_ids();
if ( $hidden ) {
if ( isset( $this->query_args['post__in'] ) ) {
$hidden_posts = array_merge( (array) $this->query_args['post__in'], $hidden_posts );
}
$this->in( $hidden_posts );
} else {
if ( isset( $this->query_args['post__not_in'] ) ) {
$hidden_posts = array_merge( (array) $this->query_args['post__not_in'], $hidden_posts );
}
$this->not_in( $hidden_posts );
}
}
/**
* Sets up the query filters to order events by the duration (`_EventDuration`) custom field.
*
* @since 5.1.5
*
* @param string $order The order direction, either `ASC` or `DESC`; defaults to `null` to use the order
* specified in the current query or default arguments.
* @param bool $after Whether to append the duration ORDER BY clause to the existing clauses or not;
* defaults to `false` to prepend the duration clause to the existing ORDER BY
* clauses.
* @param bool $override Whether to override existing ORDER BY clauses with this one or not; default to
* `true` to override existing ORDER BY clauses.
*/
protected function order_by_duration( $order = null, $after = false, $override = true ) {
global $wpdb;
$meta_alias = 'event_duration';
$meta_key = '_EventDuration';
$postmeta_table = "orderby_{$meta_alias}_meta";
$filter_id = 'order_by_duration';
$this->filter_query->join(
$wpdb->prepare(
"
LEFT JOIN {$wpdb->postmeta} AS {$postmeta_table}
ON (
{$postmeta_table}.post_id = {$wpdb->posts}.ID
AND {$postmeta_table}.meta_key = %s
)
",
$meta_key
),
$filter_id,
true
);
$order = $order === null
? Arr::get_in_any( [ $this->query_args, $this->default_args ], 'order', 'ASC' )
: $order;
$this->filter_query->orderby( [ $meta_alias => $order ], $filter_id, $override, $after );
$this->filter_query->fields( "CAST( {$postmeta_table}.meta_value AS DECIMAL ) AS {$meta_alias}", $filter_id, $override );
}
}