예제 #1
0
        public void EventItemService_GetActiveEventsAtSpecificDate_ReturnsEventScheduledForSingleDateOnOrAfterSpecifiedDate()
        {
            var rockContext = new RockContext();

            // Get an instance of Event "Warrior Youth Event", which has a single occurrence scheduled for 02-May-2018.
            var eventOccurrenceDate = new DateTime(2018, 5, 2);
            var eventItemService    = new EventItemService(rockContext);

            var warriorEvent = eventItemService.Queryable().FirstOrDefault(x => x.Name == "Warrior Youth Event");

            Assert.That.IsNotNull(warriorEvent, "Target event not found.");

            ForceUpdateScheduleEffectiveDates(rockContext, warriorEvent);

            // The "Warrior Youth Event" has a single occurrence on 02-May-2018.
            // Verify that the filter returns the event on the date of the single occurrence.
            var validEvents = eventItemService.Queryable()
                              .Where(x => x.Name == "Warrior Youth Event")
                              .HasOccurrencesOnOrAfterDate(eventOccurrenceDate)
                              .ToList();

            Assert.That.IsTrue(validEvents.Count() == 1, "Expected Event not found.");

            // ... but not after the date of the single occurrence.
            var invalidEvents = eventItemService.Queryable()
                                .Where(x => x.Name == "Warrior Youth Event")
                                .HasOccurrencesOnOrAfterDate(eventOccurrenceDate.AddDays(1))
                                .ToList();

            Assert.That.IsTrue(invalidEvents.Count() == 0, "Unexpected Event found.");
        }
예제 #2
0
        /// <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());
        }
        private EventItem ResolveEventSettingOrThrow(RockContext rockContext, string eventSettingValue)
        {
            var eventItemService = new EventItemService(rockContext);

            EventItem eventItem = null;

            // Verify that an Event reference has been provided.
            if (string.IsNullOrWhiteSpace(eventSettingValue))
            {
                throw new Exception($"An Event reference must be specified.");
            }

            // Get by ID.
            var eventId = eventSettingValue.AsIntegerOrNull();

            if (eventId != null)
            {
                eventItem = eventItemService.Get(eventId.Value);
            }

            // Get by Guid.
            if (eventItem == null)
            {
                var eventGuid = eventSettingValue.AsGuidOrNull();

                if (eventGuid != null)
                {
                    eventItem = eventItemService.Get(eventGuid.Value);
                }
            }

            // Get By Name.
            if (eventItem == null)
            {
                var eventName = eventSettingValue.ToString();

                if (!string.IsNullOrWhiteSpace(eventName))
                {
                    eventItem = eventItemService.Queryable()
                                .Where(x => x.Name != null && x.Name.Equals(eventName, StringComparison.OrdinalIgnoreCase))
                                .FirstOrDefault();
                }
            }

            if (eventItem == null)
            {
                throw new Exception($"Cannot find an Event matching the reference \"{ eventSettingValue }\".");
            }

            return(eventItem);
        }
예제 #4
0
        /// <summary>
        /// Handles the Click event of the btnSave control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        protected void btnSave_Click(object sender, EventArgs e)
        {
            EventItem eventItem = null;

            using (var rockContext = new RockContext())
            {
                var validationMessages = new List <string>();

                var eventItemService         = new EventItemService(rockContext);
                var eventCalendarItemService = new EventCalendarItemService(rockContext);
                var eventItemAudienceService = new EventItemAudienceService(rockContext);

                int eventItemId = hfEventItemId.ValueAsInt();
                if (eventItemId != 0)
                {
                    eventItem = eventItemService
                                .Queryable("EventItemAudiences,EventItemOccurrences.Linkages,EventItemOccurrences")
                                .Where(i => i.Id == eventItemId)
                                .FirstOrDefault();
                }

                if (eventItem == null)
                {
                    eventItem = new EventItem();
                    eventItemService.Add(eventItem);
                }

                eventItem.Name     = tbName.Text;
                eventItem.IsActive = cbIsActive.Checked;

                if (!eventItem.IsApproved && cbIsApproved.Checked)
                {
                    eventItem.ApprovedByPersonAliasId = CurrentPersonAliasId;
                    eventItem.ApprovedOnDateTime      = RockDateTime.Now;
                }
                eventItem.IsApproved = cbIsApproved.Checked;
                if (!eventItem.IsApproved)
                {
                    eventItem.ApprovedByPersonAliasId = null;
                    eventItem.ApprovedByPersonAlias   = null;
                    eventItem.ApprovedOnDateTime      = null;
                }
                eventItem.Description = htmlDescription.Text;
                eventItem.Summary     = tbSummary.Text;
                eventItem.DetailsUrl  = tbDetailUrl.Text;

                int?orphanedImageId = null;
                if (eventItem.PhotoId != imgupPhoto.BinaryFileId)
                {
                    orphanedImageId   = eventItem.PhotoId;
                    eventItem.PhotoId = imgupPhoto.BinaryFileId;
                }

                // Remove any audiences that were removed in the UI
                foreach (var eventItemAudience in eventItem.EventItemAudiences.Where(r => !AudiencesState.Contains(r.DefinedValueId)).ToList())
                {
                    eventItem.EventItemAudiences.Remove(eventItemAudience);
                    eventItemAudienceService.Delete(eventItemAudience);
                }

                // Add or Update audiences from the UI
                foreach (int audienceId in AudiencesState)
                {
                    EventItemAudience eventItemAudience = eventItem.EventItemAudiences.Where(a => a.DefinedValueId == audienceId).FirstOrDefault();
                    if (eventItemAudience == null)
                    {
                        eventItemAudience = new EventItemAudience();
                        eventItemAudience.DefinedValueId = audienceId;
                        eventItem.EventItemAudiences.Add(eventItemAudience);
                    }
                }

                // remove any calendar items that removed in the UI
                var calendarIds = new List <int>();
                calendarIds.AddRange(cblCalendars.SelectedValuesAsInt);
                var uiCalendarGuids = ItemsState.Where(i => calendarIds.Contains(i.EventCalendarId)).Select(a => a.Guid);
                foreach (var eventCalendarItem in eventItem.EventCalendarItems.Where(a => !uiCalendarGuids.Contains(a.Guid)).ToList())
                {
                    // Make sure user is authorized to remove calendar (they may not have seen every calendar due to security)
                    if (UserCanEdit || eventCalendarItem.EventCalendar.IsAuthorized(Authorization.EDIT, CurrentPerson))
                    {
                        eventItem.EventCalendarItems.Remove(eventCalendarItem);
                        eventCalendarItemService.Delete(eventCalendarItem);
                    }
                }

                // Add or Update calendar items from the UI
                foreach (var calendar in ItemsState.Where(i => calendarIds.Contains(i.EventCalendarId)))
                {
                    var eventCalendarItem = eventItem.EventCalendarItems.Where(a => a.Guid == calendar.Guid).FirstOrDefault();
                    if (eventCalendarItem == null)
                    {
                        eventCalendarItem = new EventCalendarItem();
                        eventItem.EventCalendarItems.Add(eventCalendarItem);
                    }
                    eventCalendarItem.CopyPropertiesFrom(calendar);
                }

                if (!eventItem.EventCalendarItems.Any())
                {
                    validationMessages.Add("At least one calendar is required.");
                }

                if (!Page.IsValid)
                {
                    return;
                }

                if (!eventItem.IsValid)
                {
                    // Controls will render the error messages
                    return;
                }

                if (validationMessages.Any())
                {
                    nbValidation.Text    = "Please Correct the Following<ul><li>" + validationMessages.AsDelimited("</li><li>") + "</li></ul>";
                    nbValidation.Visible = true;
                    return;
                }

                // use WrapTransaction since SaveAttributeValues does it's own RockContext.SaveChanges()
                rockContext.WrapTransaction(() =>
                {
                    rockContext.SaveChanges();
                    foreach (EventCalendarItem eventCalendarItem in eventItem.EventCalendarItems)
                    {
                        eventCalendarItem.LoadAttributes();
                        Rock.Attribute.Helper.GetEditValues(phAttributes, eventCalendarItem);
                        eventCalendarItem.SaveAttributeValues();
                    }

                    if (orphanedImageId.HasValue)
                    {
                        BinaryFileService binaryFileService = new BinaryFileService(rockContext);
                        var binaryFile = binaryFileService.Get(orphanedImageId.Value);
                        if (binaryFile != null)
                        {
                            // marked the old images as IsTemporary so they will get cleaned up later
                            binaryFile.IsTemporary = true;
                            rockContext.SaveChanges();
                        }
                    }
                });


                // Redirect back to same page so that item grid will show any attributes that were selected to show on grid
                var qryParams = new Dictionary <string, string>();
                if (_calendarId.HasValue)
                {
                    qryParams["EventCalendarId"] = _calendarId.Value.ToString();
                }
                qryParams["EventItemId"] = eventItem.Id.ToString();
                NavigateToPage(RockPage.Guid, qryParams);
            }
        }
예제 #5
0
        private void DisplayDetails()
        {
            RockContext rockContext = new RockContext();

            EventItemService eventItemService = new EventItemService(rockContext);
            var qry = eventItemService
                      .Queryable();

            // get the eventItem id if the event item is set via block attribute
            var eventItemAttGuid = GetAttributeValue("EventItem").AsGuid();
            int eventItemId      = qry.Where(i => i.Guid == eventItemAttGuid).Select(i => i.Id).FirstOrDefault();

            // get the eventItem id if the event item block attribute isn't set
            if (eventItemId == 0 && !string.IsNullOrWhiteSpace(PageParameter("EventItemId")))
            {
                eventItemId = Convert.ToInt32(PageParameter("EventItemId"));
            }
            if (eventItemId > 0)
            {
                /*var qry = eventItemService
                 *  .Queryable()
                 *  ;*/
                qry = qry.Where(i => i.Id == eventItemId);
            }
            else
            {
                // Get the Slug Attribute
                var slugAttribute = AttributeCache.Get(GetAttributeValue("URLSlugAttribute").AsGuid());

                // get the slug
                if (!string.IsNullOrWhiteSpace(PageParameter("URLSlug")) && slugAttribute != null)
                {
                    int slugAttributeId = slugAttribute.Id;
                    EventCalendarItemService eventCalendarItemService = new EventCalendarItemService(rockContext);
                    AttributeValueService    attributeValueService    = new AttributeValueService(rockContext);

                    var tmQry = qry.Join(eventCalendarItemService.Queryable(),
                                         ei => ei.Id,
                                         aci => aci.EventItemId,
                                         (ei, aci) => new { EventItem = ei, EventCalendarItem = aci }
                                         )
                                .Join(attributeValueService.Queryable(),
                                      ei => new { Id = ei.EventCalendarItem.Id, AttributeId = slugAttributeId },
                                      av => new { Id = av.EntityId ?? 0, AttributeId = av.AttributeId },
                                      (ei, av) => new { EventItem = ei.EventItem, EventCalendarItem = ei.EventCalendarItem, Slug = av });

                    string urlSlug = PageParameter("URLSlug");

                    tmQry = tmQry.Where(obj => obj.Slug.Value.Contains(urlSlug));

                    // The page parameter could contain something like 'camp' while the slug value list contains 'camp-freedom' so we need to double-check
                    // to make sure we have an exact match
                    qry = tmQry.ToList().AsQueryable().Where(obj => obj.Slug.Value.ToLower().Split('|').Contains(urlSlug.ToLower())).Select(obj => obj.EventItem);
                }
                else
                {
                    // If we don't have an eventItemId or slug we shouldn't get the first item in the database.  That would be . . . not good
                    qry = null;
                }
            }

            if (qry != null)
            {
                var eventItem = qry.FirstOrDefault();

                if (eventItem != null)
                {
                    // removing any occurrences that don't have a start time in the next twelve months
                    var occurrenceList = eventItem.EventItemOccurrences.ToList();
                    occurrenceList.RemoveAll(o => o.GetStartTimes(RockDateTime.Now, RockDateTime.Now.AddYears(1)).Count() == 0);

                    //Check for Campus Id Parameter
                    var campusId = PageParameter("CampusId").AsIntegerOrNull();
                    if (campusId.HasValue)
                    {
                        //check if there's a campus with this id.
                        var campus = CampusCache.Get(campusId.Value);
                        if (campus != null)
                        {
                            occurrenceList.RemoveAll(o => o.CampusId != null && o.CampusId != campus.Id);
                        }
                    }

                    //Check for Campus
                    var campusStr = PageParameter("Campus");
                    if (!string.IsNullOrEmpty(campusStr))
                    {
                        //check if there's a campus with this name.
                        var campus = CampusCache.All().Where(c => c.Name.ToLower().RemoveSpaces() == campusStr.ToLower().RemoveSpaces()).FirstOrDefault();
                        if (campus != null)
                        {
                            occurrenceList.RemoveAll(o => o.CampusId != null && o.CampusId != campus.Id);
                        }
                        else
                        {
                            // check one more time to see if there's a campus slug that matches
                            campus = CampusCache.All().Where(c => c.AttributeValues["Slug"].ToString() == campusStr.ToLower()).FirstOrDefault();
                            if (campus != null)
                            {
                                occurrenceList.RemoveAll(o => o.CampusId != null && o.CampusId != campus.Id);
                            }
                        }
                    }

                    eventItem.EventItemOccurrences = occurrenceList.OrderBy(o => o.NextStartDateTime.HasValue ? o.NextStartDateTime : DateTime.Now).ToList();

                    var mergeFields = new Dictionary <string, object>();
                    mergeFields.Add("RegistrationPage", LinkedPageRoute("RegistrationPage"));

                    var campusEntityType = EntityTypeCache.Get("Rock.Model.Campus");
                    var contextCampus    = RockPage.GetCurrentContext(campusEntityType) as Campus;

                    if (contextCampus != null)
                    {
                        mergeFields.Add("CampusContext", contextCampus);
                    }

                    // determine registration status (Register, Full, or Join Wait List) for each unique registration instance
                    Dictionary <int, string> registrationStatusLabels = new Dictionary <int, string>();
                    foreach (var occurance in eventItem.EventItemOccurrences)
                    {
                        foreach (var registrationInstance in occurance.Linkages.Select(a => a.RegistrationInstance).Distinct().ToList())
                        {
                            if (registrationStatusLabels.ContainsKey(registrationInstance.Id))
                            {
                                continue;
                            }
                            int?maxRegistrantCount       = 0;
                            var currentRegistrationCount = 0;

                            if (registrationInstance != null)
                            {
                                if (registrationInstance.MaxAttendees != 0)
                                {
                                    maxRegistrantCount = registrationInstance.MaxAttendees;
                                }
                            }


                            int?registrationSpotsAvailable = null;
                            if (maxRegistrantCount.HasValue && maxRegistrantCount != 0)
                            {
                                currentRegistrationCount = new RegistrationRegistrantService(rockContext).Queryable().AsNoTracking()
                                                           .Where(r =>
                                                                  r.Registration.RegistrationInstanceId == registrationInstance.Id &&
                                                                  r.OnWaitList == false)
                                                           .Count();
                                registrationSpotsAvailable = maxRegistrantCount - currentRegistrationCount;
                            }

                            string registrationStatusLabel = "Register";

                            if (registrationSpotsAvailable.HasValue && registrationSpotsAvailable.Value < 1)
                            {
                                if (registrationInstance.RegistrationTemplate.WaitListEnabled)
                                {
                                    registrationStatusLabel = "Join Wait List";
                                }
                                else
                                {
                                    registrationStatusLabel = "Full";
                                }
                            }

                            registrationStatusLabels.Add(registrationInstance.Id, registrationStatusLabel);
                        }
                    }

                    // Status of each registration instance
                    mergeFields.Add("RegistrationStatusLabels", registrationStatusLabels);

                    mergeFields.Add("Event", eventItem);
                    mergeFields.Add("CurrentPerson", CurrentPerson);

                    lOutput.Text = GetAttributeValue("LavaTemplate").ResolveMergeFields(mergeFields);

                    if (GetAttributeValue("SetPageTitle").AsBoolean())
                    {
                        string pageTitle = eventItem != null ? eventItem.Name : "Event";
                        RockPage.PageTitle    = pageTitle;
                        RockPage.BrowserTitle = String.Format("{0} | {1}", pageTitle, RockPage.Site.Name);
                        RockPage.Header.Title = String.Format("{0} | {1}", pageTitle, RockPage.Site.Name);
                    }
                }
                else
                {
                    lOutput.Text = EventNotFoundLava();
                }
            }
            else
            {
                lOutput.Text = EventNotFoundLava();
            }
        }
예제 #6
0
        /// <summary>
        /// Handles the Click event of the btnSave control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        protected void btnSave_Click( object sender, EventArgs e )
        {
            EventItem eventItem = null;

            using ( var rockContext = new RockContext() )
            {
                var validationMessages = new List<string>();

                var eventItemService = new EventItemService( rockContext );
                var eventCalendarItemService = new EventCalendarItemService( rockContext );
                var eventItemAudienceService = new EventItemAudienceService( rockContext );

                int eventItemId = hfEventItemId.ValueAsInt();
                if ( eventItemId != 0 )
                {
                    eventItem = eventItemService
                        .Queryable( "EventItemAudiences,EventItemOccurrences.Linkages,EventItemOccurrences" )
                        .Where( i => i.Id == eventItemId )
                        .FirstOrDefault();
                }

                if ( eventItem == null )
                {
                    eventItem = new EventItem();
                    eventItemService.Add( eventItem );
                }

                eventItem.Name = tbName.Text;
                eventItem.IsActive = cbIsActive.Checked;

                if ( !eventItem.IsApproved && cbIsApproved.Checked )
                {
                    eventItem.ApprovedByPersonAliasId = CurrentPersonAliasId;
                    eventItem.ApprovedOnDateTime = RockDateTime.Now;
                }
                eventItem.IsApproved = cbIsApproved.Checked;
                if ( !eventItem.IsApproved )
                {
                    eventItem.ApprovedByPersonAliasId = null;
                    eventItem.ApprovedByPersonAlias = null;
                    eventItem.ApprovedOnDateTime = null;
                }
                eventItem.Description = htmlDescription.Text;
                eventItem.Summary = tbSummary.Text;
                eventItem.DetailsUrl = tbDetailUrl.Text;

                int? orphanedImageId = null;
                if ( eventItem.PhotoId != imgupPhoto.BinaryFileId )
                {
                    orphanedImageId = eventItem.PhotoId;
                    eventItem.PhotoId = imgupPhoto.BinaryFileId;
                }

                // Remove any audiences that were removed in the UI
                foreach ( var eventItemAudience in eventItem.EventItemAudiences.Where( r => !AudiencesState.Contains( r.DefinedValueId ) ).ToList() )
                {
                    eventItem.EventItemAudiences.Remove( eventItemAudience );
                    eventItemAudienceService.Delete( eventItemAudience );
                }

                // Add or Update audiences from the UI
                foreach ( int audienceId in AudiencesState )
                {
                    EventItemAudience eventItemAudience = eventItem.EventItemAudiences.Where( a => a.DefinedValueId == audienceId ).FirstOrDefault();
                    if ( eventItemAudience == null )
                    {
                        eventItemAudience = new EventItemAudience();
                        eventItemAudience.DefinedValueId = audienceId;
                        eventItem.EventItemAudiences.Add( eventItemAudience );
                    }
                }

                // remove any calendar items that removed in the UI
                var calendarIds = new List<int>();
                calendarIds.AddRange( cblCalendars.SelectedValuesAsInt );
                var uiCalendarGuids = ItemsState.Where( i => calendarIds.Contains( i.EventCalendarId ) ).Select( a => a.Guid );
                foreach ( var eventCalendarItem in eventItem.EventCalendarItems.Where( a => !uiCalendarGuids.Contains( a.Guid ) ).ToList() )
                {
                    // Make sure user is authorized to remove calendar (they may not have seen every calendar due to security)
                    if ( UserCanEdit || eventCalendarItem.EventCalendar.IsAuthorized( Authorization.EDIT, CurrentPerson ) )
                    {
                        eventItem.EventCalendarItems.Remove( eventCalendarItem );
                        eventCalendarItemService.Delete( eventCalendarItem );
                    }
                }

                // Add or Update calendar items from the UI
                foreach ( var calendar in ItemsState.Where( i => calendarIds.Contains( i.EventCalendarId ) ) )
                {
                    var eventCalendarItem = eventItem.EventCalendarItems.Where( a => a.Guid == calendar.Guid ).FirstOrDefault();
                    if ( eventCalendarItem == null )
                    {
                        eventCalendarItem = new EventCalendarItem();
                        eventItem.EventCalendarItems.Add( eventCalendarItem );
                    }
                    eventCalendarItem.CopyPropertiesFrom( calendar );
                }

                if ( !eventItem.EventCalendarItems.Any() )
                {
                    validationMessages.Add( "At least one calendar is required." );
                }

                if ( !Page.IsValid )
                {
                    return;
                }

                if ( !eventItem.IsValid )
                {
                    // Controls will render the error messages
                    return;
                }

                if ( validationMessages.Any() )
                {
                    nbValidation.Text = "Please Correct the Following<ul><li>" + validationMessages.AsDelimited( "</li><li>" ) + "</li></ul>";
                    nbValidation.Visible = true;
                    return;
                }

                // use WrapTransaction since SaveAttributeValues does it's own RockContext.SaveChanges()
                rockContext.WrapTransaction( () =>
                {
                    rockContext.SaveChanges();
                    foreach ( EventCalendarItem eventCalendarItem in eventItem.EventCalendarItems )
                    {
                        eventCalendarItem.LoadAttributes();
                        Rock.Attribute.Helper.GetEditValues( phAttributes, eventCalendarItem );
                        eventCalendarItem.SaveAttributeValues();
                    }

                    if ( orphanedImageId.HasValue )
                    {
                        BinaryFileService binaryFileService = new BinaryFileService( rockContext );
                        var binaryFile = binaryFileService.Get( orphanedImageId.Value );
                        if ( binaryFile != null )
                        {
                            // marked the old images as IsTemporary so they will get cleaned up later
                            binaryFile.IsTemporary = true;
                            rockContext.SaveChanges();
                        }
                    }
                } );

                // Redirect back to same page so that item grid will show any attributes that were selected to show on grid
                var qryParams = new Dictionary<string, string>();
                if ( _calendarId.HasValue )
                {
                    qryParams["EventCalendarId"] = _calendarId.Value.ToString();
                }
                qryParams["EventItemId"] = eventItem.Id.ToString();
                NavigateToPage( RockPage.Guid, qryParams );
            }
        }
예제 #7
0
        public async Task ExecuteAsync(IJobExecutionContext context)
        {
            // Get the configuration for the job
            JobDataMap dataMap = context.JobDetail.JobDataMap;

            var approvalAttributeGuid = dataMap.GetString(AttributeKey_ApprovedAttribute).AsGuid();
            var approvalAttribute     = AttributeCache.Get(approvalAttributeGuid);

            // Collect some values for matching
            var campuses  = CampusCache.All();
            var audiences = DefinedTypeCache.Get(Rock.SystemGuid.DefinedType.MARKETING_CAMPAIGN_AUDIENCE_TYPE).DefinedValues;

            // Get the calendars
            var calendarGuid       = dataMap.GetString(AttributeKey_Calendar).AsGuid();
            var publicCalendarGuid = dataMap.GetString(AttributeKey_PublicCalendar).AsGuidOrNull();

            var calendar       = EventCalendarCache.Get(calendarGuid);
            var publicCalendar = publicCalendarGuid.HasValue ? EventCalendarCache.Get(publicCalendarGuid.Value) : null;

            // Get the login credentials
            string eSpaceUsername = dataMap.GetString(AttributeKey_eSpaceUsername);
            string eSpacePassword = dataMap.GetString(AttributeKey_eSpacePassword);

            // Decrypt the password
            eSpacePassword = Rock.Security.Encryption.DecryptString(eSpacePassword);

            // Create a new api client
            var client = new Client();

            // Log in
            client.SetCredentials(eSpaceUsername, eSpacePassword);

            // Get all future events
            var eSpaceEvents = await client.GetEvents(new GetEventsOptions { StartDate = DateTime.Now, TopX = 2000 });

            // Group by event id (the eSpace api returns "events" as a merged event and schedule)
            var eSpaceEventsById = eSpaceEvents.GroupBy(e => e.EventId);
            var eventTotalCount  = eSpaceEventsById.Count();

            // Loop through each eSpace event group
            var eventSyncedCount = 0;
            var eventErrorCount  = 0;

            foreach (var eSpaceEventGroup in eSpaceEventsById)
            {
                eventSyncedCount++;

                try
                {
                    // Use the first item as the main event - Note that some properties
                    // here are actually part of the schedule, not the event
                    var eSpaceEvent = eSpaceEventGroup.FirstOrDefault();

                    // Skip draft events
                    if (eSpaceEvent.Status == "Draft")
                    {
                        continue;
                    }

                    // Update the job status
                    context.UpdateLastStatusMessage($@"Syncing event {eventSyncedCount} of {eventTotalCount} ({Math.Round( (double) eventSyncedCount / eventTotalCount * 100, 0 )}%, {eventErrorCount} events with errors)");

                    // Sync the event
                    await SyncHelper.SyncEvent(
                        client,
                        eSpaceEvent,
                        new GetEventOccurrencesOptions
                    {
                        StartDate = DateTime.Now
                    },
                        calendar,
                        publicCalendar,
                        null,
                        approvalAttribute?.Key
                        );
                }
                catch (Exception ex)
                {
                    ExceptionLogService.LogException(ex, null);
                    eventErrorCount++;
                }
            }

            var eSpaceEventIds = eSpaceEvents.Select(e => e.EventId).ToArray();

            var rockContext      = new RockContext();
            var eventItemService = new EventItemService(rockContext);
            var desyncedEvents   = eventItemService.Queryable().Where(e => e.ForeignKey == SyncHelper.ForeignKey_eSpaceEventId && !eSpaceEventIds.Contains(e.ForeignId));

            if (desyncedEvents.Any())
            {
                eventItemService.DeleteRange(desyncedEvents);
                rockContext.SaveChanges();
            }

            // Update the job status
            context.UpdateLastStatusMessage($@"Synced {eventSyncedCount} events with {eventErrorCount} errors.");
        }