1454 lines
54 KiB
PHP
1454 lines
54 KiB
PHP
<?php
|
|
|
|
use kigkonsult\iCalcreator\iCalXML;
|
|
use kigkonsult\iCalcreator\timezoneHandler;
|
|
use kigkonsult\iCalcreator\vcalendar;
|
|
|
|
/**
|
|
* The ics import/export engine.
|
|
*
|
|
* @author Time.ly Network Inc.
|
|
* @since 2.0
|
|
*
|
|
* @package AI1EC
|
|
* @subpackage AI1EC.Import-export
|
|
*/
|
|
class Ai1ec_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;
|
|
|
|
/* (non-PHPdoc)
|
|
* @see Ai1ec_Import_Export_Engine::import()
|
|
*/
|
|
public function import( array $arguments ) {
|
|
throw new Exception( 'Import not supported' );
|
|
}
|
|
|
|
/* (non-PHPdoc)
|
|
* @see Ai1ec_Import_Export_Engine::export()
|
|
*/
|
|
public function export( array $arguments, array $params = array() ) {
|
|
$vparams = array();
|
|
|
|
$c = new vcalendar( $vparams );
|
|
$c->setProperty( 'calscale', 'GREGORIAN' );
|
|
$c->setProperty( 'method', 'PUBLISH' );
|
|
// if no post id are specified do not export those properties
|
|
// as they would create a new calendar in outlook.
|
|
// a user reported this in AIOEC-982 and said this would fix it
|
|
if( true === $arguments['do_not_export_as_calendar'] ) {
|
|
$c->setProperty( 'X-WR-CALNAME', get_bloginfo( 'name' ) );
|
|
$c->setProperty( 'X-WR-CALDESC', get_bloginfo( 'description' ) );
|
|
}
|
|
$c->setProperty( 'X-FROM-URL', home_url() );
|
|
// Timezone setup
|
|
$tz = $this->_registry->get( 'date.timezone' )->get_default_timezone();
|
|
if ( $tz ) {
|
|
$c->setProperty( 'X-WR-TIMEZONE', $tz );
|
|
$tz_xprops = array( 'X-LIC-LOCATION' => $tz );
|
|
timezoneHandler::createTimezone( $c, $tz, $tz_xprops );
|
|
}
|
|
|
|
$this->_taxonomy_model = $this->_registry->get( 'model.taxonomy' );
|
|
$post_ids = array();
|
|
foreach ( $arguments['events'] as $event ) {
|
|
$post_ids[] = $event->get( 'post_id' );
|
|
}
|
|
$this->_taxonomy_model->prepare_meta_for_ics( $post_ids );
|
|
$this->_registry->get( 'controller.content-filter' )
|
|
->clear_the_content_filters();
|
|
foreach ( $arguments['events'] as $event ) {
|
|
$c = $this->_insert_event_in_calendar(
|
|
$event,
|
|
$c,
|
|
true,
|
|
$params
|
|
);
|
|
}
|
|
$this->_registry->get( 'controller.content-filter' )
|
|
->restore_the_content_filters();
|
|
|
|
if ( isset( $params['xml'] ) && true === $params['xml'] ) {
|
|
$str = iCalXML::iCal2XML( $c );
|
|
} else {
|
|
$str = ltrim( $c->createCalendar() );
|
|
}
|
|
|
|
return $str;
|
|
}
|
|
|
|
/**
|
|
* 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', 'min', 'sec' ) as $field ) {
|
|
$timeless &= (
|
|
isset( $datetime[$field] ) &&
|
|
0 != $datetime[$field]
|
|
)
|
|
? false
|
|
: true;
|
|
}
|
|
return $timeless;
|
|
}
|
|
|
|
/**
|
|
* 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 stdClass $feed Instance of feed (see Ai1ecIcs plugin).
|
|
* @internal param string $comment_status WP comment status: 'open' or 'closed'.
|
|
* @internal param int $do_show_map Map display status (DB boolean: 0 or 1).
|
|
*
|
|
* @return int Count of events added to database.
|
|
*/
|
|
public function add_vcalendar_events_to_db(
|
|
vcalendar $v,
|
|
array $args
|
|
) {
|
|
$forced_timezone = null;
|
|
$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;
|
|
|
|
//sort by event date function _cmpfcn of iCalcreator.class.php
|
|
$v->sort();
|
|
|
|
// TODO: select only VEVENT components that occur after, say, 1 month ago.
|
|
// Maybe use $v->selectComponents(), which takes into account recurrence
|
|
|
|
// Fetch default timezone in case individual properties don't define it
|
|
$tz = $v->getComponent( 'vtimezone' );
|
|
$local_timezone = $this->_registry->get( 'date.timezone' )->get_default_timezone();
|
|
$timezone = $local_timezone;
|
|
if ( ! empty( $tz ) ) {
|
|
$timezone = $tz->getProperty( 'TZID' );
|
|
}
|
|
|
|
$feed_name = $v->getProperty( 'X-WR-CALNAME' );
|
|
$x_wr_timezone = $v->getProperty( 'X-WR-TIMEZONE' );
|
|
if (
|
|
isset( $x_wr_timezone[1] ) &&
|
|
is_array( $x_wr_timezone )
|
|
) {
|
|
$forced_timezone = (string)$x_wr_timezone[1];
|
|
$timezone = $forced_timezone;
|
|
}
|
|
|
|
$messages = array();
|
|
if ( empty( $forced_timezone ) ) {
|
|
$forced_timezone = $local_timezone;
|
|
}
|
|
$current_timestamp = $this->_registry->get( 'date.time' )->format_to_gmt();
|
|
// initialize empty custom exclusions structure
|
|
$exclusions = array();
|
|
// go over each event
|
|
while ( $e = $v->getComponent( 'vevent' ) ) {
|
|
|
|
// Event data array.
|
|
$data = array();
|
|
// =====================
|
|
// = Start & end times =
|
|
// =====================
|
|
$start = $e->getProperty( 'dtstart', 1, true );
|
|
$end = $e->getProperty( 'dtend', 1, true );
|
|
// 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->getProperty( 'duration', 1, true, true );
|
|
if ( empty( $end ) ) {
|
|
// #2 if only DATE value is set for start, set duration to 1 day
|
|
if ( ! isset( $start['value']['hour'] ) ) {
|
|
$end = array(
|
|
'value' => array(
|
|
'year' => $start['value']['year'],
|
|
'month' => $start['value']['month'],
|
|
'day' => $start['value']['day'] + 1,
|
|
'hour' => 0,
|
|
'min' => 0,
|
|
'sec' => 0,
|
|
),
|
|
);
|
|
if ( isset( $start['value']['tz'] ) ) {
|
|
$end['value']['tz'] = $start['value']['tz'];
|
|
}
|
|
} else {
|
|
// #3 set end date to start time
|
|
$end = $start;
|
|
}
|
|
}
|
|
}
|
|
$categories = $e->getProperty( "CATEGORIES", false, true );
|
|
$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['value'],
|
|
$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->getProperty( "X-TAGS", false, true );
|
|
$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[1]['value'],
|
|
$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( $start['value'] ) &&
|
|
$this->_is_timeless( $end['value'] );
|
|
// Also check the proprietary MS all-day field.
|
|
$ms_allday = $e->getProperty( 'X-MICROSOFT-CDO-ALLDAYEVENT' );
|
|
if ( ! empty( $ms_allday ) && $ms_allday[1] == 'TRUE' ) {
|
|
$allday = true;
|
|
}
|
|
$event_timezone = $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
|
|
$tztest = @timezone_open( $event_timezone );
|
|
|
|
if ( ! $tztest || $allday || preg_match( "/GMT[+|-][0-9]{4}.*/", $event_timezone ) ) {
|
|
$event_timezone = $local_timezone;
|
|
}
|
|
$start = $this->_time_array_to_datetime(
|
|
$start,
|
|
$event_timezone,
|
|
$feed->import_timezone ? $forced_timezone : null
|
|
);
|
|
$end = $this->_time_array_to_datetime(
|
|
$end,
|
|
$event_timezone,
|
|
$feed->import_timezone ? $forced_timezone : null
|
|
);
|
|
if ( false === $start || false === $end ) {
|
|
array_push ( $messages, $e->getProperty( 'summary' )." - Failed to parse one or more dates to 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 =
|
|
// =======================================
|
|
if ( $rrule = $e->createRrule() ) {
|
|
$rrule = explode( ':', $rrule );
|
|
$rrule = trim( end( $rrule ) );
|
|
}
|
|
|
|
if ( $exrule = $e->createExrule() ) {
|
|
$exrule = explode( ':', $exrule );
|
|
$exrule = trim( end( $exrule ) );
|
|
}
|
|
|
|
if ( $rdate = $e->createRdate() ) {
|
|
$arr = explode( 'RDATE', $rdate );
|
|
$matches = null;
|
|
foreach ( $arr as $value ) {
|
|
$arr2 = explode( ':', $value );
|
|
if ( 2 === count( $arr2 ) ) {
|
|
$matches[] = $arr2[1];
|
|
}
|
|
}
|
|
if ( null !== $matches ) {
|
|
$rdate = implode( ',', $matches );
|
|
unset( $matches );
|
|
unset( $arr );
|
|
} else {
|
|
$rdate = null;
|
|
}
|
|
}
|
|
|
|
// ===================
|
|
// = Exception dates =
|
|
// ===================
|
|
$exdate = '';
|
|
if ( $exdates = $e->createExdate() ){
|
|
// We may have two formats:
|
|
// one exdate with many dates ot more EXDATE rules
|
|
$exdates = explode( 'EXDATE', $exdates );
|
|
$def_timezone = $this->_get_import_timezone( $event_timezone );
|
|
foreach ( $exdates as $exd ) {
|
|
if ( empty( $exd ) ) {
|
|
continue;
|
|
}
|
|
$exploded = explode( ':', $exd );
|
|
$excpt_timezone = $def_timezone;
|
|
$excpt_date = null;
|
|
foreach ( $exploded as $particle ) {
|
|
if ( ';TZID=' === substr( $particle, 0, 6 ) ) {
|
|
$excpt_timezone = substr( $particle, 6 );
|
|
} else {
|
|
$excpt_date = trim( $particle );
|
|
}
|
|
}
|
|
$exploded = explode( ',', $excpt_date );
|
|
foreach ( $exploded as $particle ) {
|
|
// Google sends YYYYMMDD for all-day excluded events
|
|
if (
|
|
$allday &&
|
|
8 === strlen( $particle )
|
|
) {
|
|
$particle .= 'T000000Z';
|
|
$excpt_timezone = 'UTC';
|
|
}
|
|
$ex_dt = $this->_registry->get(
|
|
'date.time',
|
|
$particle,
|
|
$excpt_timezone
|
|
);
|
|
if ( $ex_dt ) {
|
|
if ( isset( $exdate{0} ) ) {
|
|
$exdate .= ',';
|
|
}
|
|
$exdate .= $ex_dt->format( 'Ymd\THis', $excpt_timezone );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Add custom exclusions if there any
|
|
$recurrence_id = $e->getProperty( 'recurrence-id' );
|
|
if (
|
|
false === $recurrence_id &&
|
|
! empty( $exclusions[$e->getProperty( 'uid' )] )
|
|
) {
|
|
if ( isset( $exdate{0} ) ) {
|
|
$exdate .= ',';
|
|
}
|
|
$exdate .= implode( ',', $exclusions[$e->getProperty( 'uid' )] );
|
|
}
|
|
// ========================
|
|
// = Latitude & longitude =
|
|
// ========================
|
|
$latitude = $longitude = NULL;
|
|
$geo_tag = $e->getProperty( 'geo' );
|
|
if ( is_array( $geo_tag ) ) {
|
|
if (
|
|
isset( $geo_tag['latitude'] ) &&
|
|
isset( $geo_tag['longitude'] )
|
|
) {
|
|
$latitude = (float)$geo_tag['latitude'];
|
|
$longitude = (float)$geo_tag['longitude'];
|
|
}
|
|
} else 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 =
|
|
// ===================
|
|
$address = $venue = '';
|
|
$location = $e->getProperty( '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->getProperty( 'X-COST' );
|
|
$cost = $cost ? $cost[1] : '';
|
|
$ticket_url = $e->getProperty( 'X-TICKETS-URL' );
|
|
$ticket_url = $ticket_url ? $ticket_url[1] : '';
|
|
|
|
// ===============================
|
|
// = Contact name, phone, e-mail =
|
|
// ===============================
|
|
$organizer = $e->getProperty( 'organizer' );
|
|
if (
|
|
'MAILTO:' === substr( $organizer, 0, 7 ) &&
|
|
false === strpos( $organizer, '@' )
|
|
) {
|
|
$organizer = substr( $organizer, 7 );
|
|
}
|
|
$contact = $e->getProperty( '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->getProperty( '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->getProperty( '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->getProperty( '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->getProperty( '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->getProperty( '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->getProperty( 'X-COST-TYPE' );
|
|
if ( $cost_type && false === ai1ec_is_blank( $cost_type[1] ) ) {
|
|
update_post_meta( $event->get( 'post_id' ), '_ai1ec_cost_type', $cost_type[1] );
|
|
}
|
|
|
|
$api_event_id = $e->getProperty( 'X-API-EVENT-ID' );
|
|
if ( $api_event_id && false === ai1ec_is_blank( $api_event_id[1] ) ) {
|
|
$api_event_id = $api_event_id[1];
|
|
} else {
|
|
$api_event_id = null;
|
|
}
|
|
|
|
$api_url = $e->getProperty( 'X-API-URL' );
|
|
if ( $api_url && false === ai1ec_is_blank( $api_url[1] ) ) {
|
|
$api_url = $api_url[1];
|
|
} else {
|
|
$api_url = null;
|
|
}
|
|
|
|
$checkout_url = $e->getProperty( 'X-CHECKOUT-URL' );
|
|
if ( $checkout_url && false === ai1ec_is_blank( $checkout_url[1] ) ) {
|
|
$checkout_url = $checkout_url[1];
|
|
} else {
|
|
$checkout_url = null;
|
|
}
|
|
|
|
$currency = $e->getProperty( 'X-API-EVENT-CURRENCY' );
|
|
if ( $currency && false === ai1ec_is_blank( $currency[1] ) ) {
|
|
$currency = $currency[1];
|
|
} else {
|
|
$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->getProperty( 'X-WP-IMAGES-URL' );
|
|
if ( $wp_images_url && false === ai1ec_is_blank( $wp_images_url[1] ) ) {
|
|
$images_arr = explode( ',', $wp_images_url[1] );
|
|
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,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* time_array_to_timestamp function
|
|
*
|
|
* Converts time array to time string.
|
|
* Passed array: Array( 'year', 'month', 'day', ['hour', 'min', 'sec', ['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 $time,
|
|
$def_timezone,
|
|
$forced_timezone = null
|
|
) {
|
|
$timezone = '';
|
|
if ( isset( $time['params']['TZID'] ) ) {
|
|
$timezone = $time['params']['TZID'];
|
|
$tzid_values = explode( ':', $timezone );
|
|
if ( 2 === count( $tzid_values ) &&
|
|
15 === strlen ( $tzid_values[1] ) ) {
|
|
//the $e->getProperty('DTSTART') or getProperty('DTEND') for the strings below
|
|
//is not returning the value TZID only with the timezone name
|
|
//DTSTART;TZID=America/Halifax:20160502T180000
|
|
//DTEND;TZID=America/Halifax:20160502T200000
|
|
$timezone = $tzid_values[0];
|
|
$tzid_values = explode( 'T', $tzid_values[1] );
|
|
if ( 2 === count( $tzid_values ) ) {
|
|
//01234567 012345
|
|
//20160607 180000
|
|
$time['value']['year'] = substr( $tzid_values[0], 0, 4 );
|
|
$time['value']['month'] = substr( $tzid_values[0], 4, 2 );
|
|
$time['value']['day'] = substr( $tzid_values[0], 6, 4 );
|
|
$time['value']['hour'] = substr( $tzid_values[1], 0, 2 );
|
|
$time['value']['min'] = substr( $tzid_values[1], 2, 2 );
|
|
$time['value']['sec'] = substr( $tzid_values[1], 4, 2 );
|
|
}
|
|
}
|
|
} elseif (
|
|
isset( $time['value']['tz'] ) &&
|
|
'Z' === $time['value']['tz']
|
|
) {
|
|
$timezone = 'UTC';
|
|
}
|
|
if ( empty( $timezone ) ) {
|
|
$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;
|
|
}
|
|
$date_time->set_timezone( $timezone );
|
|
}
|
|
|
|
if ( ! isset( $time['value']['hour'] ) ) {
|
|
$time['value']['hour'] = $time['value']['min'] =
|
|
$time['value']['sec'] = 0;
|
|
}
|
|
|
|
$date_time->set_date(
|
|
$time['value']['year'],
|
|
$time['value']['month'],
|
|
$time['value']['day']
|
|
)->set_time(
|
|
$time['value']['hour'],
|
|
$time['value']['min'],
|
|
$time['value']['sec']
|
|
);
|
|
if (
|
|
'UTC' === $timezone &&
|
|
null !== $forced_timezone
|
|
) {
|
|
$date_time->set_timezone( $forced_timezone );
|
|
}
|
|
return $date_time;
|
|
}
|
|
|
|
/**
|
|
* Convert an event from a feed into a new Ai1ec_Event object and add it to
|
|
* the calendar.
|
|
*
|
|
* @param Ai1ec_Event $event Event object.
|
|
* @param vcalendar $calendar Calendar object.
|
|
* @param bool $export States whether events are created for export.
|
|
* @param array $params Additional parameters for export.
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function _insert_event_in_calendar(
|
|
Ai1ec_Event $event,
|
|
vcalendar $calendar,
|
|
$export = false,
|
|
array $params = array()
|
|
) {
|
|
|
|
$tz = $this->_registry->get( 'date.timezone' )
|
|
->get_default_timezone();
|
|
|
|
$e = $calendar->newComponent( 'vevent' );
|
|
$uid = '';
|
|
if ( $event->get( 'ical_uid' ) ) {
|
|
$uid = addcslashes( $event->get( 'ical_uid' ), "\\;,\n" );
|
|
} else {
|
|
$uid = $event->get_uid();
|
|
$event->set( 'ical_uid', $uid );
|
|
$event->save( true );
|
|
}
|
|
$e->setProperty( 'uid', $this->_sanitize_value( $uid ) );
|
|
$e->setProperty(
|
|
'url',
|
|
get_permalink( $event->get( 'post_id' ) )
|
|
);
|
|
|
|
// =========================
|
|
// = Summary & description =
|
|
// =========================
|
|
$e->setProperty(
|
|
'summary',
|
|
$this->_sanitize_value(
|
|
html_entity_decode(
|
|
apply_filters( 'the_title', $event->get( 'post' )->post_title ),
|
|
ENT_QUOTES,
|
|
'UTF-8'
|
|
)
|
|
)
|
|
);
|
|
|
|
$content = apply_filters(
|
|
'ai1ec_the_content',
|
|
apply_filters(
|
|
'the_content',
|
|
$event->get( 'post' )->post_content
|
|
)
|
|
);
|
|
|
|
$post_meta_values = get_post_meta( $event->get( 'post_id' ), '', false );
|
|
$cost_type = null;
|
|
if ( $post_meta_values ) {
|
|
foreach ($post_meta_values as $key => $value) {
|
|
if ( '_ai1ec_cost_type' === $key ) {
|
|
$cost_type = $value[0];
|
|
}
|
|
if (
|
|
isset( $params['xml'] ) &&
|
|
$params['xml'] &&
|
|
false !== preg_match( '/^x\-meta\-/i', $key )
|
|
) {
|
|
$e->setProperty(
|
|
$key,
|
|
$this->_sanitize_value( $value )
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( false === ai1ec_is_blank( $cost_type ) ) {
|
|
$e->setProperty(
|
|
'X-COST-TYPE',
|
|
$this->_sanitize_value( $cost_type )
|
|
);
|
|
}
|
|
|
|
$url = '';
|
|
$api = $this->_registry->get( 'model.api.api-ticketing' );
|
|
$api_event_id = $api->get_api_event_id( $event->get( 'post_id' ) );
|
|
if ( $api_event_id ) {
|
|
//getting all necessary informations that will be necessary on imported ticket events
|
|
$e->setProperty( 'X-API-EVENT-ID' , $api_event_id );
|
|
$e->setProperty( 'X-API-URL' , $api->get_api_event_url( $event->get( 'post_id' ) ) );
|
|
$e->setProperty( 'X-CHECKOUT-URL' , $api->get_api_event_checkout_url( $event->get( 'post_id' ) ) );
|
|
$e->setProperty( 'X-API-EVENT-CURRENCY', $api->get_api_event_currency( $event->get( 'post_id' ) ) );
|
|
} else if ( $event->get( 'ticket_url' ) ) {
|
|
$url = $event->get( 'ticket_url' );
|
|
}
|
|
|
|
//Adding Ticket URL to the Description field
|
|
if ( false === ai1ec_is_blank( $url ) ) {
|
|
$content = $this->_remove_ticket_url( $content );
|
|
$content = $content
|
|
. '<p>' . __( 'Tickets: ', AI1EC_PLUGIN_NAME )
|
|
. '<a class="ai1ec-ticket-url-exported" href="'
|
|
. $url . '">' . $url
|
|
. '</a>.</p>';
|
|
}
|
|
|
|
$content = str_replace(']]>', ']]>', $content);
|
|
$content = html_entity_decode( $content, ENT_QUOTES, 'UTF-8' );
|
|
|
|
// Prepend featured image if available.
|
|
$size = null;
|
|
$avatar = $this->_registry->get( 'view.event.avatar' );
|
|
|
|
$images = null;
|
|
// try to find a featured image
|
|
if ( has_post_thumbnail( $event->get( 'post_id' ) ) ) {
|
|
|
|
$post_id = get_post_thumbnail_id( $event->get( 'post_id' ) );
|
|
$added = null;
|
|
foreach ( array( 'thumbnail', 'medium', 'large', 'full' ) as $_size ) {
|
|
$attributes = wp_get_attachment_image_src( $post_id, $_size );
|
|
if ( false !== $attributes ) {
|
|
$key_str = sprintf( '%d_%d', $attributes[1], $attributes[2]);
|
|
if ( null === $added || false === isset( $added[$key_str] ) ) {
|
|
$added[$key_str] = true;
|
|
array_unshift( $attributes, $_size );
|
|
$images[] = implode( ';', $attributes );
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if ( $img_url = $avatar->get_post_thumbnail_url( $event, $size ) ) {
|
|
$content = '<div class="ai1ec-event-avatar alignleft timely"><img src="' .
|
|
esc_attr( $img_url ) . '" width="' . $size[0] . '" height="' .
|
|
$size[1] . '" /></div>' . $content;
|
|
}
|
|
} else {
|
|
|
|
$matches = $avatar->get_image_from_content( $content );
|
|
|
|
if ( ! empty( $matches ) ) {
|
|
|
|
$patternWidth = '/(width="|width=\')(?P<width>\w+)/i';
|
|
preg_match( $patternWidth, $matches[0], $matchesWidth );
|
|
|
|
$patternHeight = '/(height="|height=\')(?P<height>\w+)/i';
|
|
preg_match( $patternHeight, $matches[0], $matchesHeight );
|
|
|
|
if ( ! empty( $matchesWidth ) && ! empty( $matchesHeight ) ) {
|
|
|
|
foreach ( array( 'thumbnail', 'medium', 'large', 'full' ) as $_size ) {
|
|
$attributes = array( $_size, $matches[2], $matchesWidth['width'], $matchesHeight['height'] );
|
|
$images[] = implode( ';', $attributes );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( null !== $images ) {
|
|
$e->setProperty(
|
|
'X-WP-IMAGES-URL',
|
|
$this->_sanitize_value(
|
|
implode( ',', $images )
|
|
)
|
|
);
|
|
}
|
|
|
|
if ( isset( $params['no_html'] ) && $params['no_html'] ) {
|
|
$e->setProperty(
|
|
'description',
|
|
$this->_sanitize_value(
|
|
strip_tags( strip_shortcodes( $content ) )
|
|
)
|
|
);
|
|
if ( ! empty( $content ) ) {
|
|
$html_content = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">\n' .
|
|
'<HTML>\n<HEAD>\n<TITLE></TITLE>\n</HEAD>\n<BODY>' . $content .
|
|
'</BODY></HTML>';
|
|
$e->setProperty(
|
|
'X-ALT-DESC',
|
|
$this->_sanitize_value( $html_content ),
|
|
array(
|
|
'FMTTYPE' => 'text/html',
|
|
)
|
|
);
|
|
unset( $html_content );
|
|
}
|
|
} else {
|
|
$e->setProperty( 'description', $this->_sanitize_value( $content ) );
|
|
}
|
|
$revision = (int)current(
|
|
array_keys(
|
|
wp_get_post_revisions( $event->get( 'post_id' ) )
|
|
)
|
|
);
|
|
$e->setProperty( 'sequence', $revision );
|
|
|
|
// =====================
|
|
// = Start & end times =
|
|
// =====================
|
|
$dtstartstring = '';
|
|
$dtstart = $dtend = array();
|
|
if ( $event->is_allday() ) {
|
|
$dtstart['VALUE'] = $dtend['VALUE'] = 'DATE';
|
|
// For exporting all day events, don't set a timezone
|
|
if ( $tz && ! $export ) {
|
|
$dtstart['TZID'] = $dtend['TZID'] = $tz;
|
|
}
|
|
|
|
// For exportin' all day events, only set the date not the time
|
|
if ( $export ) {
|
|
$e->setProperty(
|
|
'dtstart',
|
|
$this->_sanitize_value(
|
|
$event->get( 'start' )->format( 'Ymd' )
|
|
),
|
|
$dtstart
|
|
);
|
|
$e->setProperty(
|
|
'dtend',
|
|
$this->_sanitize_value(
|
|
$event->get( 'end' )->format( 'Ymd' )
|
|
),
|
|
$dtend
|
|
);
|
|
} else {
|
|
$e->setProperty(
|
|
'dtstart',
|
|
$this->_sanitize_value(
|
|
$event->get( 'start' )->format( "Ymd\T" )
|
|
),
|
|
$dtstart
|
|
);
|
|
$e->setProperty(
|
|
'dtend',
|
|
$this->_sanitize_value(
|
|
$event->get( 'end' )->format( "Ymd\T" )
|
|
),
|
|
$dtend
|
|
);
|
|
}
|
|
} else {
|
|
if ( $tz ) {
|
|
$dtstart['TZID'] = $dtend['TZID'] = $tz;
|
|
}
|
|
// This is used later.
|
|
$dtstartstring = $event->get( 'start' )->format( "Ymd\THis" );
|
|
$e->setProperty(
|
|
'dtstart',
|
|
$this->_sanitize_value( $dtstartstring ),
|
|
$dtstart
|
|
);
|
|
|
|
if ( false === (bool)$event->get( 'instant_event' ) ) {
|
|
$e->setProperty(
|
|
'dtend',
|
|
$this->_sanitize_value(
|
|
$event->get( 'end' )->format( "Ymd\THis" )
|
|
),
|
|
$dtend
|
|
);
|
|
}
|
|
}
|
|
|
|
// ========================
|
|
// = Latitude & longitude =
|
|
// ========================
|
|
if (
|
|
floatval( $event->get( 'latitude' ) ) ||
|
|
floatval( $event->get( 'longitude' ) )
|
|
) {
|
|
$e->setProperty(
|
|
'geo',
|
|
$event->get( 'latitude' ),
|
|
$event->get( 'longitude' )
|
|
);
|
|
}
|
|
|
|
// ===================
|
|
// = Venue & address =
|
|
// ===================
|
|
if ( $event->get( 'venue' ) || $event->get( 'address' ) ) {
|
|
$location = array(
|
|
$event->get( 'venue' ),
|
|
$event->get( 'address' )
|
|
);
|
|
$location = array_filter( $location );
|
|
$location = implode( ' @ ', $location );
|
|
$e->setProperty( 'location', $this->_sanitize_value( $location ) );
|
|
}
|
|
|
|
$categories = array();
|
|
$language = get_bloginfo( 'language' );
|
|
|
|
foreach (
|
|
$this->_taxonomy_model->get_post_categories(
|
|
$event->get( 'post_id' )
|
|
)
|
|
as $cat
|
|
) {
|
|
if ( 'events_categories' === $cat->taxonomy ) {
|
|
$categories[] = $cat->name;
|
|
}
|
|
}
|
|
$e->setProperty(
|
|
'categories',
|
|
implode( ',', $categories ),
|
|
array( "LANGUAGE" => $language )
|
|
);
|
|
$tags = array();
|
|
foreach (
|
|
$this->_taxonomy_model->get_post_tags( $event->get( 'post_id' ) )
|
|
as $tag
|
|
) {
|
|
$tags[] = $tag->name;
|
|
}
|
|
if( ! empty( $tags) ) {
|
|
$e->setProperty(
|
|
'X-TAGS',
|
|
implode( ',', $tags ),
|
|
array( "LANGUAGE" => $language )
|
|
);
|
|
}
|
|
// ==================
|
|
// = Cost & tickets =
|
|
// ==================
|
|
if ( $event->get( 'cost' ) ) {
|
|
$e->setProperty(
|
|
'X-COST',
|
|
$this->_sanitize_value( $event->get( 'cost' ) )
|
|
);
|
|
}
|
|
if ( $event->get( 'ticket_url' ) ) {
|
|
$e->setProperty(
|
|
'X-TICKETS-URL',
|
|
$this->_sanitize_value(
|
|
$event->get( 'ticket_url' )
|
|
)
|
|
);
|
|
}
|
|
// =================
|
|
// = Instant Event =
|
|
// =================
|
|
if ( $event->is_instant() ) {
|
|
$e->setProperty(
|
|
'X-INSTANT-EVENT',
|
|
$this->_sanitize_value( $event->is_instant() )
|
|
);
|
|
}
|
|
|
|
// ====================================
|
|
// = Contact name, phone, e-mail, URL =
|
|
// ====================================
|
|
$contact = array(
|
|
$event->get( 'contact_name' ),
|
|
$event->get( 'contact_phone' ),
|
|
$event->get( 'contact_email' ),
|
|
$event->get( 'contact_url' ),
|
|
);
|
|
$contact = array_filter( $contact );
|
|
$contact = implode( '; ', $contact );
|
|
$e->setProperty( 'contact', $this->_sanitize_value( $contact ) );
|
|
|
|
// ====================
|
|
// = Recurrence rules =
|
|
// ====================
|
|
$rrule = array();
|
|
$recurrence = $event->get( 'recurrence_rules' );
|
|
$recurrence = $this->_filter_rule( $recurrence );
|
|
if ( ! empty( $recurrence ) ) {
|
|
$rules = array();
|
|
foreach ( explode( ';', $recurrence ) as $v) {
|
|
if ( strpos( $v, '=' ) === false ) {
|
|
continue;
|
|
}
|
|
|
|
list( $k, $v ) = explode( '=', $v );
|
|
$k = strtoupper( $k );
|
|
// If $v is a comma-separated list, turn it into array for iCalcreator
|
|
switch ( $k ) {
|
|
case 'BYSECOND':
|
|
case 'BYMINUTE':
|
|
case 'BYHOUR':
|
|
case 'BYDAY':
|
|
case 'BYMONTHDAY':
|
|
case 'BYYEARDAY':
|
|
case 'BYWEEKNO':
|
|
case 'BYMONTH':
|
|
case 'BYSETPOS':
|
|
$exploded = explode( ',', $v );
|
|
break;
|
|
default:
|
|
$exploded = $v;
|
|
break;
|
|
}
|
|
// iCalcreator requires a more complex array structure for BYDAY...
|
|
if ( $k == 'BYDAY' ) {
|
|
$v = array();
|
|
foreach ( $exploded as $day ) {
|
|
$v[] = array( 'DAY' => $day );
|
|
}
|
|
} else {
|
|
$v = $exploded;
|
|
}
|
|
$rrule[ $k ] = $v;
|
|
}
|
|
}
|
|
|
|
// ===================
|
|
// = Exception rules =
|
|
// ===================
|
|
$exceptions = $event->get( 'exception_rules' );
|
|
$exceptions = $this->_filter_rule( $exceptions );
|
|
$exrule = array();
|
|
if ( ! empty( $exceptions ) ) {
|
|
$rules = array();
|
|
|
|
foreach ( explode( ';', $exceptions ) as $v) {
|
|
if ( strpos( $v, '=' ) === false ) {
|
|
continue;
|
|
}
|
|
|
|
list($k, $v) = explode( '=', $v );
|
|
$k = strtoupper( $k );
|
|
// If $v is a comma-separated list, turn it into array for iCalcreator
|
|
switch ( $k ) {
|
|
case 'BYSECOND':
|
|
case 'BYMINUTE':
|
|
case 'BYHOUR':
|
|
case 'BYDAY':
|
|
case 'BYMONTHDAY':
|
|
case 'BYYEARDAY':
|
|
case 'BYWEEKNO':
|
|
case 'BYMONTH':
|
|
case 'BYSETPOS':
|
|
$exploded = explode( ',', $v );
|
|
break;
|
|
default:
|
|
$exploded = $v;
|
|
break;
|
|
}
|
|
// iCalcreator requires a more complex array structure for BYDAY...
|
|
if ( $k == 'BYDAY' ) {
|
|
$v = array();
|
|
foreach ( $exploded as $day ) {
|
|
$v[] = array( 'DAY' => $day );
|
|
}
|
|
} else {
|
|
$v = $exploded;
|
|
}
|
|
$exrule[ $k ] = $v;
|
|
}
|
|
}
|
|
|
|
// add rrule to exported calendar
|
|
if ( ! empty( $rrule ) && ! isset( $rrule['RDATE'] ) ) {
|
|
$e->setProperty( 'rrule', $this->_sanitize_value( $rrule ) );
|
|
}
|
|
// add exrule to exported calendar
|
|
if ( ! empty( $exrule ) && ! isset( $exrule['EXDATE'] ) ) {
|
|
$e->setProperty( 'exrule', $this->_sanitize_value( $exrule ) );
|
|
}
|
|
|
|
// ===================
|
|
// = Exception dates =
|
|
// ===================
|
|
// For all day events that use a date as DTSTART, date must be supplied
|
|
// For other other events which use DATETIME, we must use that as well
|
|
// We must also match the exact starting time
|
|
$recurrence_dates = $event->get( 'recurrence_dates' );
|
|
$recurrence_dates = $this->_filter_rule( $recurrence_dates );
|
|
if ( ! empty( $recurrence_dates ) ) {
|
|
$params = array(
|
|
'VALUE' => 'DATE-TIME',
|
|
'TZID' => $tz,
|
|
);
|
|
$dt_suffix = $event->get( 'start' )->format( '\THis' );
|
|
foreach (
|
|
explode( ',', $recurrence_dates )
|
|
as $exdate
|
|
) {
|
|
// date-time string in EXDATES is formatted as 'Ymd\THis\Z', that
|
|
// means - in UTC timezone, thus we use `format_to_gmt` here.
|
|
$exdate = $this->_registry->get( 'date.time', $exdate )
|
|
->format_to_gmt( 'Ymd' );
|
|
$e->setProperty(
|
|
'rdate',
|
|
array( $exdate . $dt_suffix ),
|
|
$params
|
|
);
|
|
}
|
|
}
|
|
$exception_dates = $event->get( 'exception_dates' );
|
|
$exception_dates = $this->_filter_rule( $exception_dates );
|
|
if ( ! empty( $exception_dates ) ) {
|
|
$params = array(
|
|
'VALUE' => 'DATE-TIME',
|
|
'TZID' => $tz,
|
|
);
|
|
$dt_suffix = $event->get( 'start' )->format( '\THis' );
|
|
foreach (
|
|
explode( ',', $exception_dates )
|
|
as $exdate
|
|
) {
|
|
// date-time string in EXDATES is formatted as 'Ymd\THis\Z', that
|
|
// means - in UTC timezone, thus we use `format_to_gmt` here.
|
|
$exdate = $this->_registry->get( 'date.time', $exdate )
|
|
->format_to_gmt( 'Ymd' );
|
|
$e->setProperty(
|
|
'exdate',
|
|
array( $exdate . $dt_suffix ),
|
|
$params
|
|
);
|
|
}
|
|
}
|
|
return $calendar;
|
|
}
|
|
|
|
/**
|
|
* _sanitize_value method
|
|
*
|
|
* Convert value, so it be safe to use on ICS feed. Used before passing to
|
|
* iCalcreator methods, for rendering.
|
|
*
|
|
* @param string $value Text to be sanitized
|
|
*
|
|
* @return string Safe value, for use in HTML
|
|
*/
|
|
protected function _sanitize_value( $value ) {
|
|
if ( ! is_scalar( $value ) ) {
|
|
return $value;
|
|
}
|
|
$safe_eol = "\n";
|
|
$value = strtr(
|
|
trim( $value ),
|
|
array(
|
|
"\r\n" => $safe_eol,
|
|
"\r" => $safe_eol,
|
|
"\n" => $safe_eol,
|
|
)
|
|
);
|
|
$value = addcslashes( $value, '\\' );
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* 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->getProperty( 'uid' );
|
|
$recurrence_id = $e->getProperty( 'recurrence-id' );
|
|
if ( false !== $recurrence_id ) {
|
|
$ical_uid = implode( '', array_values( $recurrence_id ) ) . '-' .
|
|
$ical_uid;
|
|
}
|
|
|
|
return $ical_uid;
|
|
}
|
|
|
|
/**
|
|
* 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->getProperty( 'recurrence-id' );
|
|
if (
|
|
false === $recurrence_id ||
|
|
! isset( $recurrence_id['year'] ) ||
|
|
! isset( $recurrence_id['month'] ) ||
|
|
! isset( $recurrence_id['day'] )
|
|
) {
|
|
return $exclusions;
|
|
}
|
|
$year = $month = $day = $hour = $min = $sec = null;
|
|
extract( $recurrence_id, EXTR_IF_EXISTS );
|
|
$timezone = '';
|
|
$exdate = sprintf( '%04d%02d%02d', $year, $month, $day );
|
|
if (
|
|
null === $hour ||
|
|
null === $min ||
|
|
null === $sec
|
|
) {
|
|
$hour = $min = $sec = '00';
|
|
$timezone = 'Z';
|
|
}
|
|
$exdate .= sprintf(
|
|
'T%02d%02d%02d%s',
|
|
$hour,
|
|
$min,
|
|
$sec,
|
|
$timezone
|
|
);
|
|
$exclusions[$e->getProperty( 'uid' )][] = $exdate;
|
|
return $exclusions;
|
|
}
|
|
|
|
/**
|
|
* Filter recurrence / exclusion rule or dates. Avoid throwing exception for old, malformed values.
|
|
*
|
|
* @param string $rule Rule or dates value.
|
|
*
|
|
* @return string Fixed rule or dates value.
|
|
*/
|
|
protected function _filter_rule( $rule ) {
|
|
if ( null === $this->_rule_filter ) {
|
|
$this->_rule_filter = $this->_registry->get( 'recurrence.rule' );
|
|
}
|
|
return $this->_rule_filter->filter_rule( $rule );
|
|
}
|
|
|
|
/**
|
|
* 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 );
|
|
}
|
|
|
|
}
|