File "File_Importer_Events.php"
Full Path: /home/romayxjt/public_html/wp-content/plugins/the-events-calendar/src/Tribe/Importer/File_Importer_Events.php
File size: 20.45 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* Class Tribe__Events__Importer__File_Importer_Events
*/
class Tribe__Events__Importer__File_Importer_Events extends Tribe__Events__Importer__File_Importer {
protected $required_fields = [ 'event_name', 'event_start_date' ];
/**
* Searches the database for an existing event matching the one described
* by the specified record.
*
* @since 3.2.0
*
* @param array<mixed> $record An array of values from the Events CSV file.
*
* @return int An event matching the one described by the record or `0` if no matching
* events are found.
*/
protected function match_existing_post( array $record ) {
$start_date = $this->get_event_start_date( $record );
$end_date = $this->get_event_end_date( $record );
$all_day = $this->get_boolean_value_by_key( $record, 'event_all_day' );
// Base query - only the meta query will be different.
$query_args = [
'post_type' => Tribe__Events__Main::POSTTYPE,
'post_title' => $this->get_value_by_key( $record, 'event_name' ),
'fields' => 'ids',
'posts_per_page' => 1,
'suppress_filters' => false,
'post_status' => 'any',
];
// When trying to find matches for all day events, the comparison should only be against the date
// component only since a) the time is irrelevant and b) the time may have been adjusted to match
// The end-of-day cutoff setting.
if ( Tribe__Date_Utils::is_all_day( $all_day ) ) {
$meta_query = [
[
'key' => '_EventStartDate',
'value' => $this->get_event_start_date( $record, true ),
'compare' => 'LIKE',
],
[
'key' => '_EventAllDay',
'value' => 'yes',
],
];
// For regular, non-all day events, use the full date *and* time in the start date comparison.
} else {
$meta_query = [
[
'key' => '_EventStartDate',
'value' => $start_date,
],
];
}
// Optionally use the end date/time for matching, where available.
if ( ! empty( $end_date ) && ! $all_day ) {
$meta_query[] = [
'key' => '_EventEndDate',
'value' => $end_date,
];
}
$query_args['meta_query'] = $meta_query;
$query_args['tribe_remove_date_filters'] = true;
$query_args['tribe_suppress_query_filters'] = true;
add_filter( 'posts_search', [ $this, 'filter_query_for_title_search' ], 10, 2 );
/**
* Add an option to change the $matches that are duplicates.
*
* @since 4.6.15
*
* @param array<int> $matches Array with the duplicate matches.
* @param array<string,mixed> $query_args Array with the arguments used to get the posts.
*/
$matches = (array) apply_filters( 'tribe_events_import_event_duplicate_matches', get_posts( $query_args ), $query_args );
remove_filter( 'posts_search', [ $this, 'filter_query_for_title_search' ], 10 );
if ( empty( $matches ) ) {
return 0;
}
return reset( $matches );
}
/**
* Update an event with the imported information.
*
* @since 3.2.0
*
* @param integer $post_id The event ID to update.
* @param array<mixed> $record An event record from the import.
*
* @return false False if the update authority is set to retain or void if the update completes.
*/
protected function update_post( $post_id, array $record ) {
$update_authority_setting = tribe( 'events-aggregator.settings' )->default_update_authority( 'csv' );
$this->watch_term_creation();
$event = $this->build_event_array( $post_id, $record );
if ( 'retain' === $update_authority_setting ) {
$this->skipped[] = $event;
if ( $this->is_aggregator && ! empty( $this->aggregator_record ) ) {
$this->aggregator_record->meta['activity']->add( 'event', 'skipped', $post_id );
}
$this->stop_watching_term_creation();
return false;
}
if ( 'preserve_changes' === $update_authority_setting ) {
$event['ID'] = $post_id;
$event = Tribe__Events__Aggregator__Event::preserve_changed_fields( $event );
}
add_filter( 'tribe_tracker_enabled', '__return_false' );
Tribe__Events__API::updateEvent( $post_id, $event );
$this->stop_watching_term_creation();
if ( $this->is_aggregator && ! empty( $this->aggregator_record ) ) {
$this->aggregator_record->meta['activity']->add( 'event', 'updated', $post_id );
foreach ( $this->created_terms( Tribe__Events__Main::TAXONOMY ) as $term_id ) {
$this->aggregator_record->meta['activity']->add( 'category', 'created', $term_id );
}
foreach ( $this->created_terms( 'post_tag' ) as $term_id ) {
$this->aggregator_record->meta['activity']->add( 'tag', 'created', $term_id );
}
}
remove_filter( 'tribe_tracker_enabled', '__return_false' );
}
/**
* Create an event with the imported information.
*
* @since 3.2.0
*
* @param array<mixed> $record An event record from the import.
*
* @return integer The new event's post id.
*/
protected function create_post( array $record ) {
$this->watch_term_creation();
$event = $this->build_event_array( false, $record );
$id = Tribe__Events__API::createEvent( $event );
$this->stop_watching_term_creation();
if ( $this->is_aggregator && ! empty( $this->aggregator_record ) ) {
Tribe__Events__Aggregator__Records::instance()->add_record_to_event( $id, $this->aggregator_record->id, 'csv' );
$this->aggregator_record->meta['activity']->add( 'event', 'created', $id );
foreach ( $this->created_terms( Tribe__Events__Main::TAXONOMY ) as $term_id ) {
$this->aggregator_record->meta['activity']->add( 'category', 'created', $term_id );
}
foreach ( $this->created_terms( 'post_tag' ) as $term_id ) {
$this->aggregator_record->meta['activity']->add( 'tag', 'created', $term_id );
}
}
return $id;
}
/**
* Get the event start date from the import record.
*
* @since 3.2.0
*
* @param array<mixed> $record An event record from the import.
* @param boolean $date_only An optional setting to include the date only and no time.
*
* @return string $start_date The start date time string.
*/
private function get_event_start_date( array $record, $date_only = false ) {
$start_date = $this->get_value_by_key( $record, 'event_start_date' );
$start_time = $this->get_value_by_key( $record, 'event_start_time' );
if ( ! $date_only && ! empty( $start_time ) ) {
$start_date .= ' ' . $start_time;
}
$start_date = $date_only
? date( Tribe__Date_Utils::DBDATEFORMAT, strtotime( $start_date ) )
: date( Tribe__Date_Utils::DBDATETIMEFORMAT, strtotime( $start_date ) );
return $start_date;
}
/**
* Get the event end date from the import record.
*
* @since 3.2.0
*
* @param array<mixed> $record An event record from the import.
*
* @return string $end_date The end date time string.
*/
private function get_event_end_date( array $record ) {
$start_date = $this->get_event_start_date( $record );
$end_date = $this->get_value_by_key( $record, 'event_end_date' );
$end_time = $this->get_value_by_key( $record, 'event_end_time' );
if ( empty( $end_date ) ) {
$end_date = $start_date;
}
if ( ! empty( $end_time ) ) {
$end_date .= ' ' . $end_time;
}
if ( ! empty( $end_date ) ) {
$end_date = date( 'Y-m-d H:i:s', strtotime( $end_date ) );
}
if ( $end_date < $start_date ) {
$end_date = $start_date;
}
return $end_date;
}
/**
* Build an event array from import record.
*
* @since 3.2.0
*
* @param integer $event_id The event ID to update.
* @param array<mixed> $record An event record from the import.
*
* @return array<string|mixed> An array of information to save or update an event.
*/
private function build_event_array( $event_id, array $record ) {
$start_date = strtotime( $this->get_event_start_date( $record ) );
$end_date = strtotime( $this->get_event_end_date( $record ) );
if ( $this->default_post_status ) {
$post_status_setting = $this->default_post_status;
} else {
$post_status_setting = tribe( 'events-aggregator.settings' )->default_post_status( 'csv' );
}
$event = [
'post_type' => Tribe__Events__Main::POSTTYPE,
'post_title' => $this->get_value_by_key( $record, 'event_name' ),
'post_status' => $post_status_setting,
'post_content' => $this->get_post_text_field( $event_id, $record, 'event_description', 'post_content' ),
'comment_status' => $this->get_boolean_value_by_key( $record, 'event_comment_status', 'open', 'closed' ),
'ping_status' => $this->get_boolean_value_by_key( $record, 'event_ping_status', 'open', 'closed' ),
'post_excerpt' => $this->get_post_text_field( $event_id, $record, 'event_excerpt', 'post_excerpt' ),
'menu_order' => $this->get_boolean_value_by_key( $record, 'event_sticky', '-1', '0' ),
'EventStartDate' => date( 'Y-m-d', $start_date ),
'EventStartHour' => date( 'h', $start_date ),
'EventStartMinute' => date( 'i', $start_date ),
'EventStartMeridian' => date( 'a', $start_date ),
'EventEndDate' => date( 'Y-m-d', $end_date ),
'EventEndHour' => date( 'h', $end_date ),
'EventEndMinute' => date( 'i', $end_date ),
'EventEndMeridian' => date( 'a', $end_date ),
'EventShowMapLink' => $this->get_boolean_value_by_key( $record, 'event_show_map_link', '1', '' ),
'EventShowMap' => $this->get_boolean_value_by_key( $record, 'event_show_map', '1', '' ),
'EventCost' => $this->get_value_by_key( $record, 'event_cost' ),
'EventCurrencyCode' => $this->get_value_by_key( $record, 'event_currency_code' ),
'EventAllDay' => $this->get_boolean_value_by_key( $record, 'event_all_day', 'yes' ),
'EventHideFromUpcoming' => $this->get_boolean_value_by_key( $record, 'event_hide', 'yes', '' ),
'EventURL' => $this->get_value_by_key( $record, 'event_website' ),
'EventCurrencySymbol' => $this->get_value_by_key( $record, 'event_currency_symbol' ),
'EventCurrencyPosition' => $this->get_currency_position( $record ),
'EventTimezone' => $this->get_timezone( $this->get_value_by_key( $record, 'event_timezone' ) ),
'feature_event' => $this->get_boolean_value_by_key( $record, 'feature_event', '1', '' ),
];
if ( $organizer_id = $this->find_matching_organizer_id( $record ) ) {
$event['organizer'] = is_array( $organizer_id ) ? $organizer_id : [ 'OrganizerID' => $organizer_id ];
}
if ( $venue_id = $this->find_matching_venue_id( $record ) ) {
$event['venue'] = [ 'VenueID' => $venue_id ];
}
$cats = $this->get_value_by_key( $record, 'event_category' );
if ( $this->is_aggregator && ! empty( $this->default_category ) ) {
$cats = $cats ? $cats . ',' . $this->default_category : $this->default_category;
} elseif ( $category_setting = tribe( 'events-aggregator.settings' )->default_category( 'csv' ) ) {
$cats = $cats ? $cats . ',' . $category_setting : $category_setting;
}
if ( $cats ) {
$events_cat = Tribe__Events__Main::TAXONOMY;
$event['tax_input'][ $events_cat ] = Tribe__Terms::translate_terms_to_ids( explode( ',', $cats ), $events_cat );
}
if ( $tags = $this->get_value_by_key( $record, 'event_tags' ) ) {
$event['tax_input']['post_tag'] = $tags;
}
// Don't create the _EventHideFromUpcoming meta key/value pair if it doesn't need to be created.
if ( ! $event['EventHideFromUpcoming'] ) {
unset( $event['EventHideFromUpcoming'] );
}
if ( $event['menu_order'] == '-1' ) {
$event['EventShowInCalendar'] = 'yes';
}
/**
* Filters the additional fields available during the CSV import of events.
*
* @since 3.12.0
*
* @param array<string,mixed> $additional_fields An array of additional fields for the event.
*
* @return array<string,mixed> Modified list of additional fields.
*/
$additional_fields = apply_filters( 'tribe_events_csv_import_event_additional_fields', [] );
if ( ! empty ( $additional_fields ) ) {
foreach ( $additional_fields as $key => $csv_column ) {
$value = $this->get_value_by_key( $record, $key );
if ( strpos( $value, '|' ) > -1 ) {
$event[ $key ] = explode( '|', $value );
} else {
$event[ $key ] = $value;
}
}
}
/**
* Filters the event metadata during CSV import, allowing developers to modify the event data before it's processed.
*
* @since 5.12.4
*
* @param array<string,mixed> $event An array of event meta fields.
* @param array<mixed> $record An event record from the import.
* @param object $this The class instance.
*
* @return array<string,mixed> An array of the autodetect results.
*/
return apply_filters( 'tec_events_csv_import_event_meta', $event, $record, $this );
}
/**
* Retrieves the separator used between multiple organizers during event import.
* Defaults to comma ','.
*
* @since 4.6.19
*
* @return string The separator used between organizers. Default is a comma (,).
*/
private function get_separator() {
/**
* Filters the separator used between multiple organizers during event import.
*
* @param string $separator The separator used between organizers. Default is a comma (,).
*
* @return string Modified separator for multiple organizers.
*/
return apply_filters( 'tribe_get_event_import_organizer_separator', ',' );
}
/**
* Find organizer matches from separated string.
* Attempts to compensate for names with separators in them - Like "Woodhouse, Chelsea S."
*
* @since 4.6.19
*
* @param array<array<mixed>|string>|bool|false $organizers An array of organizers or false if empty.
*
* @return array An array of the post IDs of matching organizers.
*/
private function match_organizers( $organizers ) {
$matches = [];
$separator = $this->get_separator(); // We allow this to be filtered.
$skip = false; // For concatenation checks.
for ( $i = 0, $len = count( $organizers ); $i < $len; $i++ ) {
if ( $skip ) {
$skip = false;
continue;
}
$potential_match = $this->find_matching_post_id( trim( $organizers[ $i ] ), Tribe__Events__Organizer::POSTTYPE, 'any' );
// We've got a match, so we add it and move on.
if ( ! empty( $potential_match ) ) {
$matches[] = $potential_match;
$skip = false;
continue;
}
// No match - test for separator in name by grabbing the next item and concatenating.
$test_organizer = trim( $organizers[ $i ] ) . $separator . ' ' . trim( $organizers[ $i + 1 ] );
$potential_match = $this->find_matching_post_id( $test_organizer, Tribe__Events__Organizer::POSTTYPE, 'any' );
// Still no match, skip this item and move on.
if ( empty( $potential_match ) ) {
$skip = false;
continue;
}
// We got a match when combined with the next, so we flag to skip the next item.
$skip = true;
$matches[] = $potential_match;
}
$matches = array_filter( array_unique( $matches ) );
// Bail if we get something outlandish - like no organizers or more organizers than expected.
if ( empty( $matches ) || count( $matches ) > count( $organizers ) ) {
return [];
}
$organizer_ids = [ 'OrganizerID' => [] ];
foreach ( $matches as $id ) {
$organizer_ids[ 'OrganizerID' ][] = $id;
}
return $organizer_ids;
}
/**
* Determine if organizer is a list of space-separated IDs.
*
* @since 4.6.19
*
* @param string $organizer The organizer name(s) in the record.
*
* @return array<array<mixed>|string>|bool|false An array of organizer names, or false if empty.
*/
private function organizer_is_space_separated_ids( $organizer ) {
$pattern = '/\s+/';
if (
preg_match( $pattern, $organizer )
&& is_numeric( preg_replace( $pattern, '', $organizer ) )
) {
return preg_split( $pattern, $organizer );
}
return false;
}
/**
* Determine if organizer is a list of $separator-separated IDs.
*
* @since 4.6.19
*
* @param string $organizer The organizer names in the record.
*
* @return array[]|bool|false|string[] An array of organizer names, or false if empty.
*/
private function maybe_organizer_is_separated_list( $organizer ) {
$separator = $this->get_separator();
// When we require php > 5.5 we can combine these.
$cleared_separator = trim( $separator ); // Clear whitespace.
$pattern = ! empty( $cleared_separator ) ? '/' . $cleared_separator . '+/' : '/\s+/';
// event_organizer_name is a list of $separator-separated names and/or IDs.
if ( false !== stripos( $organizer, $separator ) ) {
return preg_split( $pattern, $organizer );
}
return false;
}
/**
* Handle finding the matching organizer(s) for the event.
*
* @since 3.2.0
*
* @param array<mixed> $record The event record from the import.
*
* @return array An array of post IDs that match the organizer being imported.
*/
private function find_matching_organizer_id( $record ) {
$organizer = $this->get_value_by_key( $record, 'event_organizer_name' );
// Test for space-separated IDs separately.
if ( $maybe_spaced_organizers = $this->organizer_is_space_separated_ids( $organizer ) ) {
return $this->match_organizers( $maybe_spaced_organizers );
}
// Check for $separator list.
if ( $maybe_separated_organizers = $this->maybe_organizer_is_separated_list( $organizer ) ) {
return $this->match_organizers( $maybe_separated_organizers );
}
// Just in case something went wrong.
// We've likely got a single item - either a number or a name (with optional spaces).
$matching_post_ids = $this->find_matching_post_id( $organizer, Tribe__Events__Organizer::POSTTYPE, 'any' );
if ( ! is_array( $matching_post_ids ) ) {
$matching_post_ids = [ $matching_post_ids ];
}
return [ 'OrganizerID' => $matching_post_ids ];
}
/**
* Handle finding the matching venue(s) for the event.
*
* @since 3.2.0
*
* @param array<mixed> $record The event record from the import.
*
* @return false|float|int|string|WP_Post 0 if $name is empty or there's no match.
* $name if it's numeric and there is a match.
* An array of post IDs matching the venue being imported.
*/
private function find_matching_venue_id( $record ) {
$name = $this->get_value_by_key( $record, 'event_venue_name' );
return $this->find_matching_post_id( $name, Tribe__Events__Venue::POSTTYPE, 'any' );
}
/**
* Parses a timezone string candidate and returns a TEC supported timezone string.
*
* @since 4.2.0
*
* @param string $timezone_candidate The string representing the time zone of the event.
*
* @return bool|string Either the timezone string or `false` if the timezone candidate is invalid.
*/
private function get_timezone( $timezone_candidate ) {
if ( Tribe__Timezones::is_utc_offset( $timezone_candidate ) ) {
return $timezone_candidate;
}
return Tribe__Timezones::get_timezone( $timezone_candidate, false ) ? $timezone_candidate : false;
}
/**
* Get Post Text from Import or Existing Value using the provided field name and post field.
*
* @since 5.1.6
*
* @param int $event_id The event id being updated by import.
* @param array<string> $record An event record from the import.
* @param string $field The import field name.
* @param string $post_field The post field name.
*
* @return string The description value to update the event with.
*/
protected function get_post_text_field( $event_id, $record, $field, $post_field ) {
$import_exists = $this->has_value_by_key( $record, $field );
// If the import field is not being imported and there is no id, return an empty string.
if ( ! $import_exists && empty( $event_id ) ) {
return '';
}
// If the import field is not being imported and there is an id, return current description.
if ( ! $import_exists && $event_id ) {
$post = get_post( $event_id );
if ( ! $post instanceof \WP_Post ) {
return '';
}
return $post->{$post_field};
}
$import_description = $this->get_value_by_key( $record, $field );
// If there is no event id we return the imported description, even if empty.
return $import_description;
}
/**
* Allows the user to specify the currency position using alias terms.
*
* @since 4.2.0
*
* @param array<mixed> $record An event record from the import.
*
* @return string Either `prefix` or `suffix`; will fall back on the first if the specified position is not
* a recognized alias.
*/
private function get_currency_position( array $record ) {
$currency_position = $this->get_value_by_key( $record, 'event_currency_position' );
$after_aliases = [ 'suffix', 'after' ];
foreach ( $after_aliases as $after_alias ) {
if ( preg_match( '/' . $after_alias . '/i', $currency_position ) ) {
return 'suffix';
}
}
return 'prefix';
}
}