_registry->get( 'vcalendar' ); if ( $cal->parse( $arguments['source'] ) ) { try { $result = $this->add_vcalendar_events_to_db( $cal, $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' ); } /* (non-PHPdoc) * @see Ai1ec_Import_Export_Engine::export() */ public function export( array $arguments, array $params = array() ) { $c = new vcalendar(); $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 ); iCalUtilityFunctions::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(); $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; $v->sort(); // Reverse the sort order, so that RECURRENCE-IDs are listed before the // defining recurrence events, and therefore take precedence during // caching. $v->components = array_reverse( $v->components ); // 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; if ( $allday ) { $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 ) { 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 = // ======================================= 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() ) { $rdate = explode( ':', $rdate ); $rdate = trim( end( $rdate ) ); } // =================== // = 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( '/\d/', $el ) ) { $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 = // ====================================================== // 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 ); } 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']; } 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 ) ); //Adding Ticket URL to the Description field if ( $event->get( 'ticket_url' ) ) { $url = $event->get( 'ticket_url' ); $content = $this->_remove_ticket_url( $content ); $content = $content . '

' . __( 'Tickets: ', AI1EC_PLUGIN_NAME ) . '' . $url . '.

'; } $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' ); $matches = $avatar->get_image_from_content( $content ); // if no img is already present - add thumbnail if ( empty( $matches ) ) { if ( $img_url = $avatar->get_post_thumbnail_url( $event, $size ) ) { $content = '
' . $content; } } if ( isset( $params['no_html'] ) && $params['no_html'] ) { $e->setProperty( 'description', $this->_sanitize_value( strip_tags( strip_shortcodes( $content ) ) ) ); if ( ! empty( $content ) ) { $html_content = '\n' . '\n\n\n\n' . $content . ''; $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 ) { $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( '/

[^<>]+]+class=[\'"]?ai1ec-ticket-url-exported[\'"]?[^<>]+>.[^<>]+<\/a>[\.\s]*<\/p>/' , '' , $description ); } }