801 lines
30 KiB
PHP
801 lines
30 KiB
PHP
<?php
|
|
|
|
/**
|
|
* The ics import engine to import feeds from API
|
|
*
|
|
* @author Time.ly Network Inc.
|
|
* @since 2.4
|
|
*
|
|
* @package AI1EC
|
|
* @subpackage AI1EC.Import-export
|
|
*/
|
|
class Ai1ec_Api_Ics_Import_Export_Engine
|
|
extends Ai1ec_Base
|
|
implements Ai1ec_Import_Export_Engine {
|
|
|
|
/**
|
|
* @var Ai1ec_Taxonomy
|
|
*/
|
|
protected $_taxonomy_model = null;
|
|
|
|
/**
|
|
* Recurrence rule class. Contains filter method.
|
|
*
|
|
* @var Ai1ec_Recurrence_Rule
|
|
*/
|
|
protected $_rule_filter = null;
|
|
|
|
protected function is_valid( $cal ) {
|
|
if ( is_null( $cal ) || ! is_array( $cal ) ) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* (non-PHPdoc)
|
|
* @see Ai1ec_Import_Export_Engine::import()
|
|
*/
|
|
public function import( array $arguments ) {
|
|
$cal = $arguments['source'];
|
|
if ( $this->is_valid( $cal ) ) {
|
|
try {
|
|
$result = $this->add_vcalendar_events_to_db( $arguments );
|
|
} catch ( Ai1ec_Parse_Exception $exception ) {
|
|
throw new Ai1ec_Parse_Exception(
|
|
'Processing "' . $arguments['source'] .
|
|
'" triggered error: ' . $exception->getMessage()
|
|
);
|
|
}
|
|
return $result;
|
|
}
|
|
throw new Ai1ec_Parse_Exception( 'The passed string is not a valid ics feed' );
|
|
}
|
|
|
|
public function events_order_comparator( $e1, $e2 ) {
|
|
return strcmp( $e1->dtstart, $e2->dtstart );
|
|
}
|
|
|
|
/**
|
|
* Process vcalendar instance - add events to database.
|
|
*
|
|
* @param vcalendar $v Calendar to retrieve data from.
|
|
* @param array $args Arbitrary arguments map.
|
|
*
|
|
* @throws Ai1ec_Parse_Exception
|
|
*
|
|
* @internal param array $args Array with events and configuration
|
|
*
|
|
* @return int Count of events added to database.
|
|
*/
|
|
public function add_vcalendar_events_to_db( array $args ) {
|
|
|
|
$cal = $this->_normalize_google_uids( $args['source'] );
|
|
|
|
$feed = isset( $args['feed'] ) ? $args['feed'] : null;
|
|
$comment_status = isset( $args['comment_status'] ) ? $args['comment_status'] : 'open';
|
|
$do_show_map = isset( $args['do_show_map'] ) ? $args['do_show_map'] : 0;
|
|
$count = 0;
|
|
$events_in_db = isset( $args['events_in_db'] ) ? $args['events_in_db'] : 0;
|
|
$feed_name = $feed->feed_url;
|
|
|
|
// Fetch default timezone in case individual properties don't define it
|
|
$local_timezone = $this->_registry->get( 'date.timezone' )->get_default_timezone();
|
|
|
|
$messages = array();
|
|
|
|
$current_timestamp = $this->_registry->get( 'date.time' )->format_to_gmt();
|
|
|
|
// initialize empty custom exclusions structure
|
|
$exclusions = array();
|
|
// go over each event
|
|
foreach ( $cal as $e ) {
|
|
|
|
// Event data array.
|
|
$data = array();
|
|
// =====================
|
|
// = Start & end times =
|
|
// =====================
|
|
$start = (array)$e->dtstart;
|
|
$end = (array)$e->dtend;
|
|
// For cases where a "VEVENT" calendar component
|
|
// specifies a "DTSTART" property with a DATE value type but none
|
|
// of "DTEND" nor "DURATION" property, the event duration is taken to
|
|
// be one day. For cases where a "VEVENT" calendar component
|
|
// specifies a "DTSTART" property with a DATE-TIME value type but no
|
|
// "DTEND" property, the event ends on the same calendar date and
|
|
// time of day specified by the "DTSTART" property.
|
|
if ( empty( $end ) ) {
|
|
// #1 if duration is present, assign it to end time
|
|
$end = $e->duration;
|
|
if ( empty( $end ) ) {
|
|
// #2 if only DATE value is set for start, set duration to 1 day
|
|
if ( ! isset( $start['time']['hour'] ) ) {
|
|
$end = array(
|
|
'date' => array(
|
|
'year' => $start['date']['year'],
|
|
'month' => $start['date']['month'],
|
|
'day' => $start['date']['day'] + 1,
|
|
),
|
|
'time' => array(
|
|
'hour' => 0,
|
|
'minute' => 0,
|
|
'second' => 0,
|
|
),
|
|
);
|
|
// #3 set end date to start time
|
|
$end = $start;
|
|
}
|
|
}
|
|
}
|
|
$categories = $e->categories;
|
|
$imported_cat = array( Ai1ec_Event_Taxonomy::CATEGORIES => array() );
|
|
// If the user chose to preserve taxonomies during import, add categories.
|
|
if( $categories && $feed->keep_tags_categories ) {
|
|
$imported_cat = $this->add_categories_and_tags(
|
|
$categories,
|
|
$imported_cat,
|
|
false,
|
|
true
|
|
);
|
|
}
|
|
$feed_categories = $feed->feed_category;
|
|
if( ! empty( $feed_categories ) ) {
|
|
$imported_cat = $this->add_categories_and_tags(
|
|
$feed_categories,
|
|
$imported_cat,
|
|
false,
|
|
false
|
|
);
|
|
}
|
|
$tags = $e->x_tags;
|
|
$imported_tags = array( Ai1ec_Event_Taxonomy::TAGS => array() );
|
|
// If the user chose to preserve taxonomies during import, add tags.
|
|
if( $tags && $feed->keep_tags_categories ) {
|
|
$imported_tags = $this->add_categories_and_tags(
|
|
$tags,
|
|
$imported_tags,
|
|
true,
|
|
true
|
|
);
|
|
}
|
|
$feed_tags = $feed->feed_tags;
|
|
if( ! empty( $feed_tags ) ) {
|
|
$imported_tags = $this->add_categories_and_tags(
|
|
$feed_tags,
|
|
$imported_tags,
|
|
true,
|
|
true
|
|
);
|
|
}
|
|
// Event is all-day if no time components are defined
|
|
$allday = $this->_is_timeless( (array)$start['time'] ) &&
|
|
$this->_is_timeless( (array)$end['time'] );
|
|
// Also check the proprietary MS all-day field.
|
|
$ms_allday = $e->x_microsoft_cdo_alldayevent;
|
|
if ( ! empty( $ms_allday ) && 'TRUE' == $ms_allday ) {
|
|
$allday = true;
|
|
}
|
|
$event_timezone = $e->timezone;
|
|
|
|
// Check if the timezone is a recognized TZ in PHP
|
|
// Note: the TZ may be perfectly valid, but it may not be an accepted value in the PHP version the plugin is running on
|
|
try {
|
|
$tz = @timezone_open( $event_timezone );
|
|
} catch ( Exception $e ) {
|
|
// Invalid timzeone
|
|
$tz = null;
|
|
}
|
|
|
|
if ( ! $tz || $allday || preg_match( "/GMT[+|-][0-9]{4}.*/", $event_timezone ) ) {
|
|
$event_timezone = $local_timezone;
|
|
}
|
|
$start = $this->_time_array_to_datetime(
|
|
(array)$start['date'],
|
|
(array)$start['time'],
|
|
$event_timezone,
|
|
$feed->import_timezone ? $local_timezone : null
|
|
);
|
|
$end = $this->_time_array_to_datetime(
|
|
(array)$end['date'],
|
|
(array)$end['time'],
|
|
$event_timezone,
|
|
$feed->import_timezone ? $local_timezone : null
|
|
);
|
|
if ( false === $start || false === $end ) {
|
|
throw new Ai1ec_Parse_Exception(
|
|
'Failed to parse one or more dates given timezone "' .
|
|
var_export( $event_timezone, true ) . '"'
|
|
);
|
|
continue;
|
|
}
|
|
|
|
// If all-day, and start and end times are equal, then this event has
|
|
// invalid end time (happens sometimes with poorly implemented iCalendar
|
|
// exports, such as in The Event Calendar), so set end time to 1 day
|
|
// after start time.
|
|
if ( $allday && $start->format() === $end->format() ) {
|
|
$end->adjust_day( +1 );
|
|
}
|
|
|
|
$data += compact( 'start', 'end', 'allday' );
|
|
|
|
// =======================================
|
|
// = Recurrence rules & recurrence dates =
|
|
// =======================================
|
|
$rrule = $e->rrule;
|
|
$rdate = $e->rdate;
|
|
|
|
// =======================================
|
|
// = Exclusion rules & exclusion dates =
|
|
// =======================================
|
|
$exrule = $e->exrule;
|
|
$exdate = $e->exdate;
|
|
|
|
// Add custom exclusions if there any
|
|
$recurrence_id = $e->recurrence_id;
|
|
if (
|
|
'' === $recurrence_id &&
|
|
! empty( $exclusions[$e->uid] )
|
|
) {
|
|
if ( isset( $exdate{0} ) ) {
|
|
$exdate .= ',';
|
|
}
|
|
$exdate .= implode( ',', $exclusions[$e->uid] );
|
|
}
|
|
// ========================
|
|
// = Latitude & longitude =
|
|
// ========================
|
|
$latitude = $longitude = NULL;
|
|
$geo_tag = isset( $e->geo ) ? $e->geo : null;
|
|
if ( ! empty( $geo_tag ) && false !== strpos( $geo_tag, ',' ) ) {
|
|
list( $latitude, $longitude ) = explode( ',', $geo_tag, 2 );
|
|
$latitude = (float)$latitude;
|
|
$longitude = (float)$longitude;
|
|
}
|
|
unset( $geo_tag );
|
|
if ( NULL !== $latitude ) {
|
|
$data += compact( 'latitude', 'longitude' );
|
|
// Check the input coordinates checkbox, otherwise lat/long data
|
|
// is not present on the edit event page
|
|
$data['show_coordinates'] = 1;
|
|
}
|
|
// ===================
|
|
// = Venue & address =
|
|
// ===================
|
|
$data['country'] = '';
|
|
$data['city'] = '';
|
|
$data['province'] = '';
|
|
$data['postal_code'] = '';
|
|
|
|
$address = '';
|
|
$venue = '';
|
|
$location = $e->location;
|
|
$matches = array();
|
|
// This regexp matches a venue / address in the format
|
|
// "venue @ address" or "venue - address".
|
|
preg_match( '/\s*(.*\S)\s+[\-@]\s+(.*)\s*/', $location, $matches );
|
|
// if there is no match, it's not a combined venue + address
|
|
if ( empty( $matches ) ) {
|
|
// temporary fix for Mac ICS import. Se AIOEC-2187
|
|
// and https://github.com/iCalcreator/iCalcreator/issues/13
|
|
$location = str_replace( '\n', "\n", $location );
|
|
// if there is a comma, probably it's an address
|
|
if ( false === strpos( $location, ',' ) ) {
|
|
$venue = $location;
|
|
} else {
|
|
$address = $location;
|
|
}
|
|
} else {
|
|
$venue = isset( $matches[1] ) ? $matches[1] : '';
|
|
$address = isset( $matches[2] ) ? $matches[2] : '';
|
|
}
|
|
|
|
// =====================================================
|
|
// = Set show map status based on presence of location =
|
|
// =====================================================
|
|
$event_do_show_map = $do_show_map;
|
|
if (
|
|
1 === $do_show_map &&
|
|
NULL === $latitude &&
|
|
empty( $address )
|
|
) {
|
|
$event_do_show_map = 0;
|
|
}
|
|
|
|
// ==================
|
|
// = Cost & tickets =
|
|
// ==================
|
|
$cost = $e->x_cost;
|
|
$ticket_url = $e->x_tickets_url;
|
|
|
|
// ===============================
|
|
// = Contact name, phone, e-mail =
|
|
// ===============================
|
|
// Initialize default values
|
|
$data['contact_email'] = '';
|
|
$data['contact_url'] = '';
|
|
$data['contact_phone'] = '';
|
|
$data['contact_name'] = '';
|
|
|
|
$organizer = $e->organizer;
|
|
if (
|
|
'MAILTO:' === substr( $organizer, 0, 7 ) &&
|
|
false === strpos( $organizer, '@' )
|
|
) {
|
|
$organizer = substr( $organizer, 7 );
|
|
}
|
|
$contact = $e->contact;
|
|
$elements = explode( ';', $contact, 4 );
|
|
|
|
foreach ( $elements as $el ) {
|
|
$el = trim( $el );
|
|
// Detect e-mail address.
|
|
if ( false !== strpos( $el, '@' ) ) {
|
|
$data['contact_email'] = $el;
|
|
}
|
|
// Detect URL.
|
|
elseif ( false !== strpos( $el, '://' ) ) {
|
|
$data['contact_url'] = $el;
|
|
}
|
|
// Detect phone number.
|
|
elseif ( preg_match( '/^[\+0-9\-\(\)\s]*$/', $el ) && strlen( $el ) <= 32 ) {
|
|
$data['contact_phone'] = $el;
|
|
}
|
|
// Default to name.
|
|
else {
|
|
$data['contact_name'] = $el;
|
|
}
|
|
}
|
|
if ( ! isset( $data['contact_name'] ) || ! $data['contact_name'] ) {
|
|
// If no contact name, default to organizer property.
|
|
$data['contact_name'] = $organizer;
|
|
}
|
|
|
|
$description = stripslashes(
|
|
str_replace(
|
|
'\n',
|
|
"\n",
|
|
$e->description
|
|
));
|
|
|
|
$description = $this->_remove_ticket_url( $description );
|
|
|
|
// Store yet-unsaved values to the $data array.
|
|
$data += array(
|
|
'recurrence_rules' => $rrule,
|
|
'exception_rules' => $exrule,
|
|
'recurrence_dates' => $rdate,
|
|
'exception_dates' => $exdate,
|
|
'venue' => $venue,
|
|
'address' => $address,
|
|
'cost' => $cost,
|
|
'ticket_url' => $ticket_url,
|
|
'show_map' => $event_do_show_map,
|
|
'ical_feed_url' => $feed->feed_url,
|
|
'ical_source_url' => $e->url,
|
|
'ical_organizer' => $organizer,
|
|
'ical_contact' => $contact,
|
|
'ical_uid' => $this->_get_ical_uid( $e ),
|
|
'categories' => array_keys( $imported_cat[Ai1ec_Event_Taxonomy::CATEGORIES] ),
|
|
'tags' => array_keys( $imported_tags[Ai1ec_Event_Taxonomy::TAGS] ),
|
|
'feed' => $feed,
|
|
'post' => array(
|
|
'post_status' => 'publish',
|
|
'comment_status' => $comment_status,
|
|
'post_type' => AI1EC_POST_TYPE,
|
|
'post_author' => 1,
|
|
'post_title' => $e->summary,
|
|
'post_content' => $description
|
|
)
|
|
);
|
|
// register any custom exclusions for given event
|
|
$exclusions = $this->_add_recurring_events_exclusions(
|
|
$e,
|
|
$exclusions,
|
|
$start
|
|
);
|
|
|
|
// Create event object.
|
|
$data = apply_filters(
|
|
'ai1ec_pre_init_event_from_feed',
|
|
$data,
|
|
$e,
|
|
$feed
|
|
);
|
|
|
|
$event = $this->_registry->get( 'model.event', $data );
|
|
|
|
// Instant Event
|
|
$is_instant = $e->x_instant_event;
|
|
if ( $is_instant ) {
|
|
$event->set_no_end_time();
|
|
}
|
|
|
|
$recurrence = $event->get( 'recurrence_rules' );
|
|
$search = $this->_registry->get( 'model.search' );
|
|
// first let's check by UID
|
|
$matching_event_id = $search
|
|
->get_matching_event_by_uid_and_url(
|
|
$event->get( 'ical_uid' ),
|
|
$event->get( 'ical_feed_url' )
|
|
);
|
|
// if no result, perform the legacy check.
|
|
if ( null === $matching_event_id ) {
|
|
$matching_event_id = $search
|
|
->get_matching_event_id(
|
|
$event->get( 'ical_uid' ),
|
|
$event->get( 'ical_feed_url' ),
|
|
$event->get( 'start' ),
|
|
! empty( $recurrence )
|
|
);
|
|
}
|
|
if ( null === $matching_event_id ) {
|
|
|
|
// =================================================
|
|
// = Event was not found, so store it and the post =
|
|
// =================================================
|
|
$event->save();
|
|
$count++;
|
|
} else {
|
|
// ======================================================
|
|
// = Event was found, let's store the new event details =
|
|
// ======================================================
|
|
$uid_cal = $e->uid;
|
|
if ( ! ai1ec_is_blank( $uid_cal ) ) {
|
|
$uid_cal_original = sprintf( $event->get_uid_pattern(), $matching_event_id );
|
|
if ( $uid_cal_original === $uid_cal ) {
|
|
//avoiding cycle import
|
|
//ignore the event, it belongs to site
|
|
unset( $events_in_db[$matching_event_id] );
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Update the post
|
|
$post = get_post( $matching_event_id );
|
|
|
|
if ( null !== $post ) {
|
|
$post->post_title = $event->get( 'post' )->post_title;
|
|
$post->post_content = $event->get( 'post' )->post_content;
|
|
wp_update_post( $post );
|
|
|
|
// Update the event
|
|
$event->set( 'post_id', $matching_event_id );
|
|
$event->set( 'post', $post );
|
|
$event->save( true );
|
|
$count++;
|
|
}
|
|
}
|
|
do_action( 'ai1ec_ics_event_saved', $event, $feed );
|
|
|
|
// import not standard taxonomies.
|
|
unset( $imported_cat[Ai1ec_Event_Taxonomy::CATEGORIES] );
|
|
foreach ( $imported_cat as $tax_name => $ids ) {
|
|
wp_set_post_terms( $event->get( 'post_id' ), array_keys( $ids ), $tax_name );
|
|
}
|
|
|
|
unset( $imported_tags[Ai1ec_Event_Taxonomy::TAGS] );
|
|
foreach ( $imported_tags as $tax_name => $ids ) {
|
|
wp_set_post_terms( $event->get( 'post_id' ), array_keys( $ids ), $tax_name );
|
|
}
|
|
|
|
// import the metadata used by ticket events
|
|
|
|
$cost_type = $e->x_cost_type;
|
|
if ( $cost_type && false === ai1ec_is_blank( $cost_type ) ) {
|
|
update_post_meta( $event->get( 'post_id' ), '_ai1ec_cost_type', $cost_type );
|
|
}
|
|
|
|
$api_event_id = $e->x_api_event_id;
|
|
if ( $api_event_id && false === ai1ec_is_blank( $api_event_id ) ) {
|
|
$api_event_id = $api_event_id;
|
|
} else {
|
|
$api_event_id = null;
|
|
}
|
|
|
|
$api_url = $e->x_api_url;
|
|
if ( $api_url && false === ai1ec_is_blank( $api_url ) ) {
|
|
$api_url = $api_url;
|
|
} else {
|
|
$api_url = null;
|
|
}
|
|
|
|
$checkout_url = $e->x_checkout_url;
|
|
if ( $checkout_url && false === ai1ec_is_blank( $checkout_url ) ) {
|
|
$checkout_url = $checkout_url;
|
|
} else {
|
|
$checkout_url = null;
|
|
}
|
|
|
|
$currency = $e->x_api_event_currency;
|
|
if ( ! $currency || ai1ec_is_blank( $currency ) ) {
|
|
$currency = null;
|
|
}
|
|
if ( $api_event_id || $api_url || $checkout_url || $currency ) {
|
|
if ( ! isset( $api ) ) {
|
|
$api = $this->_registry->get( 'model.api.api-ticketing' );
|
|
}
|
|
$api->save_api_event_data( $event->get( 'post_id' ), $api_event_id, $api_url, $checkout_url, $currency );
|
|
}
|
|
|
|
$wp_images_url = $e->x_wp_image_url;
|
|
if ( $wp_images_url && false === ai1ec_is_blank( $wp_images_url ) ) {
|
|
$images_arr = explode( ',', $wp_images_url );
|
|
foreach ( $images_arr as $key => $value ) {
|
|
$images_arr[ $key ] = explode( ';', $value );
|
|
}
|
|
if ( count( $images_arr ) > 0 ) {
|
|
update_post_meta( $event->get( 'post_id' ), '_featured_image', $images_arr );
|
|
}
|
|
}
|
|
|
|
unset( $events_in_db[$event->get( 'post_id' )] );
|
|
} //close while iteration
|
|
|
|
return array(
|
|
'count' => $count,
|
|
'events_to_delete' => $events_in_db,
|
|
'messages' => $messages,
|
|
'name' => $feed_name,
|
|
);
|
|
|
|
}
|
|
|
|
/**
|
|
* Returns modified ical uid for google recurring edited events.
|
|
*
|
|
* @param vevent $e Vevent object.
|
|
*
|
|
* @return string ICAL uid.
|
|
*/
|
|
protected function _get_ical_uid( $e ) {
|
|
$ical_uid = $e->uid;
|
|
$recurrence_id = $e->recurrence_id;
|
|
if ( '' !== $recurrence_id ) {
|
|
$ical_uid = $recurrence_id . '-' . $ical_uid;
|
|
}
|
|
|
|
return $ical_uid;
|
|
}
|
|
|
|
/**
|
|
* time_array_to_timestamp function
|
|
*
|
|
* Converts time array to time string.
|
|
* Passed array: Array( 'year', 'month', 'day', ['hour', 'minute', 'second', ['tz']] )
|
|
* Return int: UNIX timestamp in GMT
|
|
*
|
|
* @param array $time iCalcreator time property array
|
|
* (*full* format expected)
|
|
* @param string $def_timezone Default time zone in case not defined
|
|
* in $time
|
|
* @param null|string $forced_timezone Timezone to use instead of UTC.
|
|
*
|
|
* @return int UNIX timestamp
|
|
**/
|
|
protected function _time_array_to_datetime(
|
|
array $date,
|
|
array $time,
|
|
$def_timezone,
|
|
$forced_timezone = null
|
|
) {
|
|
$timezone = $def_timezone;
|
|
|
|
$date_time = $this->_registry->get( 'date.time' );
|
|
|
|
if ( ! empty( $timezone ) ) {
|
|
$parser = $this->_registry->get( 'date.timezone' );
|
|
$timezone = $parser->get_name( $timezone );
|
|
if ( false === $timezone ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Set default timezone
|
|
$date_time->set_timezone( $timezone );
|
|
|
|
if ( ! isset( $time['hour'] ) ) {
|
|
$time['hour'] = $time['minute'] = $time['second'] = 0;
|
|
}
|
|
|
|
$date_time
|
|
->set_date(
|
|
$date['year'],
|
|
$date['month'],
|
|
$date['day']
|
|
)
|
|
->set_time(
|
|
$time['hour'],
|
|
$time['minute'],
|
|
$time['second']
|
|
);
|
|
|
|
// Set choosen timezone
|
|
if ( null !== $forced_timezone) {
|
|
$date_time->set_timezone( $forced_timezone );
|
|
}
|
|
|
|
return $date_time;
|
|
}
|
|
|
|
/**
|
|
* Check if date-time specification has no (empty) time component.
|
|
*
|
|
* @param array $datetime Datetime array returned by iCalcreator.
|
|
*
|
|
* @return bool Timelessness.
|
|
*/
|
|
protected function _is_timeless( array $datetime ) {
|
|
$timeless = true;
|
|
foreach ( array( 'hour', 'minute', 'second' ) as $field ) {
|
|
$timeless &= (
|
|
isset( $datetime[$field] ) &&
|
|
0 != $datetime[$field]
|
|
)
|
|
? false
|
|
: true;
|
|
}
|
|
return $timeless;
|
|
}
|
|
|
|
/**
|
|
* Parse importable feed timezone to sensible value.
|
|
*
|
|
* @param string $def_timezone Timezone value from feed.
|
|
*
|
|
* @return string Valid timezone name to use.
|
|
*/
|
|
protected function _get_import_timezone( $def_timezone ) {
|
|
$parser = $this->_registry->get( 'date.timezone' );
|
|
$timezone = $parser->get_name( $def_timezone );
|
|
if ( false === $timezone ) {
|
|
return 'sys.default';
|
|
}
|
|
return $timezone;
|
|
}
|
|
|
|
/**
|
|
* _string_to_datetime function
|
|
*
|
|
* Converts time string "Y-m-d H:i:s" to DateTime object.
|
|
* Passed array: Array( 'year', 'month', 'day', ['hour', 'minute', 'second', ['tz']] )
|
|
* Return int: UNIX timestamp in GMT
|
|
*
|
|
* @param array $time iCalcreator time property array
|
|
* (*full* format expected)
|
|
* @param string $def_timezone Default time zone in case not defined
|
|
* in $time
|
|
* @param null|string $forced_timezone Timezone to use instead of UTC.
|
|
*
|
|
* @return int UNIX timestamp
|
|
**/
|
|
protected function _string_to_datetime(
|
|
$time,
|
|
$def_timezone,
|
|
$forced_timezone = null
|
|
) {
|
|
$date_time = null;
|
|
if ( 0 === strcasecmp( $def_timezone, 'utc' ) &&
|
|
null !== $forced_timezone ) {
|
|
$date_time = $this->_registry->get( 'date.time', $time, $forced_timezone );
|
|
} else {
|
|
$date_time = $this->_registry->get( 'date.time', $time, $def_timezone );
|
|
}
|
|
return $date_time;
|
|
}
|
|
|
|
|
|
/**
|
|
* Takes a comma-separated list of tags or categories.
|
|
* If they exist, reuses
|
|
* the existing ones. If not, creates them.
|
|
*
|
|
* The $imported_terms array uses keys to store values rather than values to
|
|
* speed up lookups (using isset() insted of in_array()).
|
|
*
|
|
* @param string $terms
|
|
* @param array $imported_terms
|
|
* @param boolean $is_tag
|
|
* @param boolean $use_name
|
|
*
|
|
* @return array
|
|
*/
|
|
public function add_categories_and_tags(
|
|
$terms,
|
|
array $imported_terms,
|
|
$is_tag,
|
|
$use_name
|
|
) {
|
|
$taxonomy = $is_tag ? 'events_tags' : 'events_categories';
|
|
$categories = explode( ',', $terms );
|
|
$event_taxonomy = $this->_registry->get( 'model.event.taxonomy' );
|
|
|
|
foreach ( $categories as $cat_name ) {
|
|
$cat_name = trim( $cat_name );
|
|
if ( empty( $cat_name ) ) {
|
|
continue;
|
|
}
|
|
$term = $event_taxonomy->initiate_term( $cat_name, $taxonomy, ! $use_name );
|
|
if ( false !== $term ) {
|
|
if ( ! isset( $imported_terms[$term['taxonomy']] ) ) {
|
|
$imported_terms[$term['taxonomy']] = array();
|
|
}
|
|
$imported_terms[$term['taxonomy']][$term['term_id']] = true;
|
|
}
|
|
}
|
|
return $imported_terms;
|
|
}
|
|
|
|
/**
|
|
* Remove the Ticket URL that maybe exists inside the field Description of the Event
|
|
*/
|
|
protected function _remove_ticket_url( $description ) {
|
|
return preg_replace( '/<p>[^<>]+<a[^<>]+class=[\'"]?ai1ec-ticket-url-exported[\'"]?[^<>]+>.[^<>]+<\/a>[\.\s]*<\/p>/'
|
|
, ''
|
|
, $description );
|
|
}
|
|
|
|
/* (non-PHPdoc)
|
|
* @see Ai1ec_Import_Export_Engine::export()
|
|
*/
|
|
public function export( array $arguments, array $params = array() ) {
|
|
throw new Exception( 'Export not supported' );
|
|
}
|
|
|
|
/**
|
|
* Returns modified exclusions structure for given event.
|
|
*
|
|
* @param vcalendar $e Vcalendar event object.
|
|
* @param array $exclusions Exclusions.
|
|
* @param Ai1ec_Date_Time $start Date time object.
|
|
*
|
|
* @return array Modified exclusions structure.
|
|
*/
|
|
protected function _add_recurring_events_exclusions( $e, $exclusions, $start ) {
|
|
$recurrence_id = $e->recurrence_id;
|
|
|
|
if ( '' === $recurrence_id ||
|
|
! preg_match('/^[0-9]{4}[0-1][0-9][0-3][0-9]/', $recurrence_id)) {
|
|
return $exclusions;
|
|
}
|
|
|
|
$exclusions[$e->uid][] = $recurrence_id;
|
|
return $exclusions;
|
|
}
|
|
|
|
/**
|
|
* Strips Google duplicated events created when editing recurrent ones
|
|
*
|
|
* Code contributed by cmezzett (https://wordpress.org/support/profile/cmezzett)
|
|
*/
|
|
protected function _normalize_google_uids ( array $calendar ) {
|
|
|
|
foreach ( $calendar as $event ) {
|
|
if ( strpos( $event->uid, '@google.com' ) !== false && strpos( $event->uid, '_R' ) !== false ) {
|
|
preg_match( "/^(.*)_R.*@google.com$/i", $event->uid, $matches );
|
|
$tmp_uid = $matches[1] . '@google.com';
|
|
if ( ( $e = $this->_search_for_id( $tmp_uid, $calendar ) ) !== null ) {
|
|
$suspect = $calendar[$e];
|
|
if ( empty( $suspect->recurrence_id ) ) {
|
|
//error_log( 'Discarded UID without recurrence-id: ' . $suspect->uid . '; Object: ' . serialize( $suspect ) );
|
|
unset( $calendar[$e] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $calendar;
|
|
}
|
|
|
|
protected function _search_for_id( $id, $array ) {
|
|
|
|
foreach ( $array as $key => $val ) {
|
|
if ( $val->uid === $id ) {
|
|
return $key;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
}
|