/// <summary> /// Validates the request data. /// </summary> /// <param name="context">The context.</param> /// <returns></returns> private CalendarProps ValidateRequestData(HttpContext context) { CalendarProps calendarProps = new CalendarProps(); // Security check made sure the calendar ID is good so no need to check it again. calendarProps.CalendarId = int.Parse(request.QueryString["calendarid"]); string campusIdQueryString = request.QueryString["campusids"] != null ? request.QueryString["campusids"] : string.Empty; calendarProps.CampusIds = ParseIds(campusIdQueryString); string audienceIdQueryString = request.QueryString["audienceids"] != null ? request.QueryString["audienceids"] : string.Empty; calendarProps.AudienceIds = ParseIds(audienceIdQueryString); string startDate = request.QueryString["startdate"]; if (!string.IsNullOrWhiteSpace(startDate)) { calendarProps.StartDate = DateTime.ParseExact(startDate, "yyyyMMdd", CultureInfo.InvariantCulture); } string endDate = request.QueryString["enddate"]; if (!string.IsNullOrWhiteSpace(endDate)) { calendarProps.EndDate = DateTime.ParseExact(endDate, "yyyyMMdd", CultureInfo.InvariantCulture); } return(calendarProps); }
/// <summary> /// Processes the request. /// </summary> /// <param name="httpContext">The HTTP context.</param> public void ProcessRequest(HttpContext httpContext) { string interactionDeviceType = InteractionDeviceType.GetClientType(httpContext.Request.UserAgent); try { CalendarProps calendarProps = ValidateRequestData(httpContext); if (calendarProps == null) { return; } iCalendar icalendar = CreateICalendar(calendarProps, interactionDeviceType); iCalendarSerializer serializer = new iCalendarSerializer(); string s = serializer.SerializeToString(icalendar); httpContext.Response.Clear(); httpContext.Response.ClearHeaders(); httpContext.Response.ClearContent(); httpContext.Response.AddHeader("content-disposition", string.Format("attachment; filename={0}_ical.ics", DateTime.Now.ToString("yyyy-MM-dd_hhmmss"))); httpContext.Response.ContentType = "text/calendar"; httpContext.Response.Write(s); } catch (Exception ex) { ExceptionLogService.LogException(ex, httpContext); SendBadRequest(httpContext); } }
/// <summary> /// Uses the filter information in the CalendarProps object to get a list of events /// </summary> /// <param name="calendarProps">The calendar props.</param> /// <returns></returns> private List <EventItem> GetEventItems(CalendarProps calendarProps) { RockContext rockContext = new RockContext(); EventCalendarItemService eventCalendarItemService = new EventCalendarItemService(rockContext); var eventIdsForCalendar = eventCalendarItemService .Queryable() .Where(i => i.EventCalendarId == calendarProps.CalendarId) .Select(i => i.EventItemId) .ToList(); EventItemService eventItemService = new EventItemService(rockContext); var eventQueryable = eventItemService .Queryable("EventItemAudiences, EventItemOccurrences.Schedule") .Where(e => eventIdsForCalendar.Contains(e.Id)) .Where(e => e.EventItemOccurrences.Any(o => o.Schedule.EffectiveStartDate <= calendarProps.EndDate && calendarProps.StartDate <= o.Schedule.EffectiveEndDate)) .Where(e => e.IsActive == true) .Where(e => e.IsApproved); // For Campus if (calendarProps.CampusIds.Any()) { eventQueryable = eventQueryable.Where(e => e.EventItemOccurrences.Any(c => !c.CampusId.HasValue || calendarProps.CampusIds.Contains(c.CampusId.Value))); } // For Audience if (calendarProps.AudienceIds.Any()) { eventQueryable = eventQueryable.Where(e => e.EventItemAudiences.Any(c => calendarProps.AudienceIds.Contains(c.DefinedValueId))); } return(eventQueryable.ToList()); }
/// <summary> /// Uses the filter information in the CalendarProps object to get a list of attendance records and orders them by schedule ID. /// </summary> /// <param name="calendarProps">The calendar props.</param> /// <returns></returns> private List <Attendance> GetAttendances(CalendarProps calendarProps) { using (var rockContext = new RockContext()) { var attendanceService = new AttendanceService(rockContext); var attendances = attendanceService .GetConfirmedScheduled() .AsNoTracking() .Where(a => a.PersonAlias.PersonId == calendarProps.PersonId) .Where(a => a.Occurrence.OccurrenceDate >= calendarProps.StartDate) .Where(a => a.Occurrence.OccurrenceDate <= calendarProps.EndDate) .OrderBy(a => a.Occurrence.ScheduleId); return(attendances.ToList()); } }
/// <summary> /// Validates the request data. /// </summary> /// <param name="context">The context.</param> /// <returns></returns> private CalendarProps ValidateRequestData(HttpContext httpContext) { CalendarProps calendarProps = new CalendarProps(); Guid?personAliasGuid = httpContext.Request.QueryString["paguid"].AsGuidOrNull(); if (personAliasGuid == null) { SendBadRequest(httpContext); return(null); } using (var rockContext = new RockContext()) { var personAliasService = new PersonAliasService(rockContext); int?personId = personAliasService.Queryable().AsNoTracking().Where(pa => pa.Guid == personAliasGuid).Select(pa => pa.PersonId).Cast <int?>().FirstOrDefault(); if (personId == null) { SendBadRequest(httpContext); return(null); } calendarProps.PersonId = personId.Value; } string startDate = httpContext.Request.QueryString["startdate"]; if (!string.IsNullOrWhiteSpace(startDate)) { calendarProps.StartDate = DateTime.ParseExact(startDate, "yyyyMMdd", CultureInfo.InvariantCulture); } string endDate = httpContext.Request.QueryString["enddate"]; if (!string.IsNullOrWhiteSpace(endDate)) { calendarProps.EndDate = DateTime.ParseExact(endDate, "yyyyMMdd", CultureInfo.InvariantCulture); } return(calendarProps); }
/// <summary> /// Creates the iCalendar object and populates it with events /// </summary> /// <param name="calendarProps">The calendar props.</param> /// <returns></returns> private iCalendar CreateICalendar(CalendarProps calendarProps) { // Get a list of Rock Calendar Events filtered by calendarProps List <EventItem> eventItems = GetEventItems(calendarProps); // Create the iCalendar iCalendar icalendar = new iCalendar(); icalendar.AddLocalTimeZone(); // Create each of the events for the calendar(s) foreach (EventItem eventItem in eventItems) { foreach (EventItemOccurrence occurrence in eventItem.EventItemOccurrences) { if (occurrence.Schedule == null) { continue; } iCalendarSerializer serializer = new iCalendarSerializer(); iCalendarCollection ical = (iCalendarCollection)serializer.Deserialize(occurrence.Schedule.iCalendarContent.ToStreamReader()); foreach (var icalEvent in ical[0].Events) { // We get all of the schedule info from Schedule.iCalendarContent Event ievent = icalEvent.Copy <Event>(); ievent.Summary = !string.IsNullOrEmpty(eventItem.Name) ? eventItem.Name : string.Empty; ievent.Location = !string.IsNullOrEmpty(occurrence.Location) ? occurrence.Location : string.Empty; ievent.DTStart.SetTimeZone(icalendar.TimeZones[0]); ievent.DTEnd.SetTimeZone(icalendar.TimeZones[0]); // Rock has more descriptions than iCal so lets concatenate them string description = CreateEventDescription(eventItem, occurrence); // Don't set the description prop for outlook to force it to use the X-ALT-DESC property which can have markup. if (interactionDeviceType != "Outlook") { ievent.Description = description.ConvertBrToCrLf() .Replace("</P>", "") .Replace("</p>", "") .Replace("<P>", Environment.NewLine) .Replace("<p>", Environment.NewLine) .Replace(" ", " ") .SanitizeHtml(); } // HTML version of the description for outlook ievent.AddProperty("X-ALT-DESC;FMTTYPE=text/html", "<html>" + description + "</html>"); // classification: "PUBLIC", "PRIVATE", "CONFIDENTIAL" ievent.Class = "PUBLIC"; if (!string.IsNullOrEmpty(eventItem.DetailsUrl)) { Uri result; if (Uri.TryCreate(eventItem.DetailsUrl, UriKind.Absolute, out result)) { ievent.Url = result; } else if (Uri.TryCreate("http://" + eventItem.DetailsUrl, UriKind.Absolute, out result)) { ievent.Url = result; } } // add contact info if it exists if (occurrence.ContactPersonAlias != null) { ievent.Organizer = new Organizer(string.Format("MAILTO:{0}", occurrence.ContactPersonAlias.Person.Email)); ievent.Organizer.CommonName = occurrence.ContactPersonAlias.Person.FullName; // Outlook doesn't seems to use Contacts or Comments string contactName = !string.IsNullOrEmpty(occurrence.ContactPersonAlias.Person.FullName) ? "Name: " + occurrence.ContactPersonAlias.Person.FullName : string.Empty; string contactEmail = !string.IsNullOrEmpty(occurrence.ContactEmail) ? ", Email: " + occurrence.ContactEmail : string.Empty; string contactPhone = !string.IsNullOrEmpty(occurrence.ContactPhone) ? ", Phone: " + occurrence.ContactPhone : string.Empty; string contactInfo = contactName + contactEmail + contactPhone; ievent.Contacts.Add(contactInfo); ievent.Comments.Add(contactInfo); } // TODO: categories - comma delimited list of whatever, might use audience foreach (var a in eventItem.EventItemAudiences) { ievent.Categories.Add(a.DefinedValue.Value); } //// No attachments for now. ////if ( eventItem.PhotoId != null ) ////{ //// // The DDay Attachment obj doesn't allow you to name the attachment. Nice huh? So just add prop manually... //// ievent.AddProperty( "ATTACH;VALUE=BINARY;ENCODING=BASE64;X-FILENAME=\"" + eventItem.Photo.FileName + "\"", Convert.ToBase64String( eventItem.Photo.ContentStream.ReadBytesToEnd().ToArray() ) ); ////} icalendar.Events.Add(ievent); } } } return(icalendar); }
/// <summary> /// Creates the iCalendar object and populates it with events /// </summary> /// <param name="calendarProps">The calendar props.</param> /// <returns></returns> private iCalendar CreateICalendar(CalendarProps calendarProps, string interactionDeviceType) { // Get a list of confirmed attendances filtered by calendarProps List <Attendance> attendances = GetAttendances(calendarProps); // Create the iCalendar iCalendar icalendar = new iCalendar(); icalendar.AddLocalTimeZone(); TimeSpan duration = TimeSpan.MinValue; int currentScheduleId = -1; // Create each of the attendances foreach (var attendance in attendances) { using (var rockContext = new RockContext()) { var attendanceOccurrenceService = new AttendanceOccurrenceService(rockContext); var schedule = attendanceOccurrenceService .Queryable() .AsNoTracking() .Where(a => a.Id == attendance.OccurrenceId) .Select(a => a.Schedule) .FirstOrDefault(); string scheduleName = schedule.Name; // TODO: Construct a description that includes the group leader contact info and the URL to the schedule toolbox. string description = schedule.Description; string locationName = attendanceOccurrenceService .Queryable() .AsNoTracking() .Where(a => a.Id == attendance.OccurrenceId) .Select(a => a.Location.Name) .FirstOrDefault() ?? string.Empty; if (schedule.Id != currentScheduleId) { // We have to get the duration from Schedule.iCal for this attendance. // Attendances are ordered by scheduleId so this only happens once for each unique schedule. iCalendarSerializer serializer = new iCalendarSerializer(); iCalendarCollection ical = ( iCalendarCollection )serializer.Deserialize(schedule.iCalendarContent.ToStreamReader()); duration = ical[0].Events[0].Duration; currentScheduleId = schedule.Id; } var iCalEvent = new DDay.iCal.Event(); iCalEvent.Summary = scheduleName; iCalEvent.Location = locationName; iCalEvent.DTStart = new DDay.iCal.iCalDateTime(attendance.StartDateTime); iCalEvent.DTStart.SetTimeZone(icalendar.TimeZones[0]); iCalEvent.Duration = duration; // Don't set the description prop for outlook to force it to use the X-ALT-DESC property which can have markup. if (interactionDeviceType != "Outlook") { iCalEvent.Description = description.ConvertBrToCrLf().SanitizeHtml(); } // HTML version of the description for outlook iCalEvent.AddProperty("X-ALT-DESC;FMTTYPE=text/html", "<html>" + description + "</html>"); // classification: "PUBLIC", "PRIVATE", "CONFIDENTIAL" iCalEvent.Class = "PUBLIC"; //Add contact info for the group leader int groupId = attendanceOccurrenceService .Queryable() .AsNoTracking() .Where(a => a.Id == attendance.OccurrenceId) .Select(a => a.GroupId) .FirstOrDefault() ?? -1; Person groupLeader = new GroupMemberService(rockContext).GetLeaders(groupId).AsNoTracking().Select(m => m.Person).FirstOrDefault() ?? null; if (groupLeader != null) { iCalEvent.Organizer = new Organizer(string.Format("MAILTO:{0}", groupLeader.Email)); iCalEvent.Organizer.CommonName = groupLeader.FullName; // Outlook doesn't seem to use Contacts or Comments string contactName = !string.IsNullOrEmpty(groupLeader.FullName) ? "Name: " + groupLeader.FullName : string.Empty; string contactEmail = !string.IsNullOrEmpty(groupLeader.Email) ? ", Email: " + groupLeader.Email : string.Empty; string contactInfo = contactName + contactEmail; iCalEvent.Contacts.Add(contactInfo); iCalEvent.Comments.Add(contactInfo); } icalendar.Events.Add(iCalEvent); } } return(icalendar); }
/// <summary> /// Creates the iCalendar object and populates it with events /// </summary> /// <param name="calendarProps">The calendar props.</param> /// <returns></returns> private Calendar CreateICalendar(CalendarProps calendarProps) { // Get a list of Rock Calendar Events filtered by calendarProps List <EventItem> eventItems = GetEventItems(calendarProps); // Create the iCalendar. // Allow ICal to create the VTimeZone object from the local time zone to ensure that we get the correct name and daylight saving offset. var icalendar = new Calendar(); var vtz = VTimeZone.FromLocalTimeZone(); icalendar.AddTimeZone(vtz); var timeZoneId = vtz.TzId; // Create each of the events for the calendar(s) foreach (EventItem eventItem in eventItems) { foreach (EventItemOccurrence occurrence in eventItem.EventItemOccurrences) { if (occurrence.Schedule == null) { continue; } var serializer = new CalendarSerializer(); var ical = (CalendarCollection)serializer.Deserialize(occurrence.Schedule.iCalendarContent.ToStreamReader()); foreach (var icalEvent in ical[0].Events) { // We get all of the schedule info from Schedule.iCalendarContent var ievent = icalEvent.Copy <Ical.Net.Event>(); ievent.Summary = !string.IsNullOrEmpty(eventItem.Name) ? eventItem.Name : string.Empty; ievent.Location = !string.IsNullOrEmpty(occurrence.Location) ? occurrence.Location : string.Empty; // Create the list of exceptions. // Exceptions must meet RFC 5545 iCalendar specifications to be correctly processed by third-party calendar applications // such as Microsoft Outlook and Google Calendar. Specifically, an exception must have exactly the same start time // and time zone as the template event, and the time zone must be expressed as an IANA name. // The most recent version of iCal.Net (v2.3.5) that supports .NET framework v4.5.2 has some inconsistencies in the // iCalendar serialization process, so we need to force the Start, End and Exception dates to render in exactly the same format. ievent.Start = new CalDateTime(icalEvent.Start.Value, timeZoneId); ievent.Start.IsUniversalTime = false; ievent.End = new CalDateTime(icalEvent.End.Value, timeZoneId); ievent.End.IsUniversalTime = false; var eventStartTime = new TimeSpan(ievent.DtStart.Hour, ievent.DtStart.Minute, ievent.DtStart.Second); var newExceptionDatesList = new List <Ical.Net.Interfaces.DataTypes.IPeriodList>(); foreach (var exceptionDateList in ievent.ExceptionDates) { var newDateList = new PeriodList() { TzId = timeZoneId }; foreach (var exceptionDate in exceptionDateList) { var newDateTime = exceptionDate.StartTime.HasTime ? exceptionDate.StartTime.Value : exceptionDate.StartTime.Value.Add(eventStartTime); newDateTime = new DateTime(newDateTime.Year, newDateTime.Month, newDateTime.Day, newDateTime.Hour, newDateTime.Minute, newDateTime.Second, newDateTime.Millisecond, DateTimeKind.Local); var newDate = new CalDateTime(newDateTime); newDate.IsUniversalTime = false; newDateList.Add(newDate); } newExceptionDatesList.Add(newDateList); } ievent.ExceptionDates = newExceptionDatesList; // Rock has more descriptions than iCal so lets concatenate them string description = CreateEventDescription(eventItem, occurrence); // Don't set the description prop for outlook to force it to use the X-ALT-DESC property which can have markup. if (interactionDeviceType != "Outlook") { ievent.Description = description.ConvertBrToCrLf() .Replace("</P>", "") .Replace("</p>", "") .Replace("<P>", Environment.NewLine) .Replace("<p>", Environment.NewLine) .Replace(" ", " ") .SanitizeHtml(); } // HTML version of the description for outlook ievent.AddProperty("X-ALT-DESC;FMTTYPE=text/html", "<html>" + description + "</html>"); // classification: "PUBLIC", "PRIVATE", "CONFIDENTIAL" ievent.Class = "PUBLIC"; if (!string.IsNullOrEmpty(eventItem.DetailsUrl)) { Uri result; if (Uri.TryCreate(eventItem.DetailsUrl, UriKind.Absolute, out result)) { ievent.Url = result; } else if (Uri.TryCreate("http://" + eventItem.DetailsUrl, UriKind.Absolute, out result)) { ievent.Url = result; } } // add contact info if it exists if (occurrence.ContactPersonAlias != null) { ievent.Organizer = new Organizer(string.Format("MAILTO:{0}", occurrence.ContactPersonAlias.Person.Email)); ievent.Organizer.CommonName = occurrence.ContactPersonAlias.Person.FullName; // Outlook doesn't seems to use Contacts or Comments string contactName = !string.IsNullOrEmpty(occurrence.ContactPersonAlias.Person.FullName) ? "Name: " + occurrence.ContactPersonAlias.Person.FullName : string.Empty; string contactEmail = !string.IsNullOrEmpty(occurrence.ContactEmail) ? ", Email: " + occurrence.ContactEmail : string.Empty; string contactPhone = !string.IsNullOrEmpty(occurrence.ContactPhone) ? ", Phone: " + occurrence.ContactPhone : string.Empty; string contactInfo = contactName + contactEmail + contactPhone; ievent.Contacts.Add(contactInfo); ievent.Comments.Add(contactInfo); } // TODO: categories - comma delimited list of whatever, might use audience foreach (var a in eventItem.EventItemAudiences) { ievent.Categories.Add(a.DefinedValue.Value); } //// No attachments for now. ////if ( eventItem.PhotoId != null ) ////{ //// // The DDay Attachment obj doesn't allow you to name the attachment. Nice huh? So just add prop manually... //// ievent.AddProperty( "ATTACH;VALUE=BINARY;ENCODING=BASE64;X-FILENAME=\"" + eventItem.Photo.FileName + "\"", Convert.ToBase64String( eventItem.Photo.ContentStream.ReadBytesToEnd().ToArray() ) ); ////} icalendar.Events.Add(ievent); } } } return(icalendar); }