Esempio n. 1
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)
        {
            if (CurrentPersonId.HasValue && CurrentPersonAliasId.HasValue)
            {
                var service = new FollowingEventSubscriptionService(_rockContext);
                var existingSubscriptions = service.Queryable()
                                            .Where(s => s.PersonAlias.PersonId == CurrentPersonId)
                                            .ToList();

                foreach (RepeaterItem entityTypeItem in rptEntityType.Items)
                {
                    var rptEvent = entityTypeItem.FindControl("rptEvent") as Repeater;
                    if (rptEvent != null)
                    {
                        foreach (RepeaterItem eventItem in rptEvent.Items)
                        {
                            var hfEvent = eventItem.FindControl("hfEvent") as HiddenField;
                            var cbEvent = eventItem.FindControl("cbEvent") as RockCheckBox;
                            if (hfEvent != null && cbEvent != null)
                            {
                                int eventTypeId = hfEvent.ValueAsInt();
                                if (cbEvent.Checked)
                                {
                                    if (!existingSubscriptions.Any(s => s.EventTypeId == eventTypeId))
                                    {
                                        var subscription = new FollowingEventSubscription();
                                        subscription.EventTypeId   = eventTypeId;
                                        subscription.PersonAliasId = CurrentPersonAliasId.Value;
                                        service.Add(subscription);
                                    }
                                }
                                else
                                {
                                    foreach (var subscription in existingSubscriptions
                                             .Where(s => s.EventTypeId == eventTypeId))
                                    {
                                        service.Delete(subscription);
                                    }
                                }
                            }
                        }
                    }
                }

                _rockContext.SaveChanges();
                nbSaved.Visible = true;
            }
        }
Esempio n. 2
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 )
        {
            if ( CurrentPersonId.HasValue && CurrentPersonAliasId.HasValue )
            {
                var service = new FollowingEventSubscriptionService( _rockContext );
                var existingSubscriptions = service.Queryable()
                    .Where( s => s.PersonAlias.PersonId == CurrentPersonId )
                    .ToList();

                foreach ( RepeaterItem entityTypeItem in rptEntityType.Items )
                {
                    var rptEvent = entityTypeItem.FindControl( "rptEvent" ) as Repeater;
                    if ( rptEvent != null )
                    {
                        foreach ( RepeaterItem eventItem in rptEvent.Items )
                        {
                            var hfEvent = eventItem.FindControl( "hfEvent" ) as HiddenField;
                            var cbEvent = eventItem.FindControl( "cbEvent" ) as RockCheckBox;
                            if ( hfEvent != null && cbEvent != null )
                            {
                                int eventTypeId = hfEvent.ValueAsInt();
                                if ( cbEvent.Checked )
                                {
                                    if ( !existingSubscriptions.Any( s => s.EventTypeId == eventTypeId ) )
                                    {
                                        var subscription = new FollowingEventSubscription();
                                        subscription.EventTypeId = eventTypeId;
                                        subscription.PersonAliasId = CurrentPersonAliasId.Value;
                                        service.Add( subscription );
                                    }
                                }
                                else
                                {
                                    foreach ( var subscription in existingSubscriptions
                                        .Where( s => s.EventTypeId == eventTypeId ) )
                                    {
                                        service.Delete( subscription );
                                    }
                                }
                            }
                        }
                    }
                }

                _rockContext.SaveChanges();
                nbSaved.Visible = true;
            }
        }
Esempio n. 3
0
        /// <summary>
        /// Executes the specified context.
        /// </summary>
        /// <param name="context">The context.</param>
        public void Execute(IJobExecutionContext context)
        {
            JobDataMap dataMap             = context.JobDetail.JobDataMap;
            Guid?      groupGuid           = dataMap.GetString("EligibleFollowers").AsGuidOrNull();
            Guid?      systemEmailGuid     = dataMap.GetString("EmailTemplate").AsGuidOrNull();
            int        followingEventsSent = 0;

            if (groupGuid.HasValue && systemEmailGuid.HasValue)
            {
                var exceptionMsgs = new List <string>();

                using (var rockContext = new RockContext())
                {
                    var followingService                  = new FollowingService(rockContext);
                    var followingEventTypeService         = new FollowingEventTypeService(rockContext);
                    var followingEventNotificationService = new FollowingEventNotificationService(rockContext);

                    // Get all the active event types
                    var eventTypes = followingEventTypeService
                                     .Queryable().AsNoTracking()
                                     .Where(e =>
                                            e.EntityTypeId.HasValue &&
                                            e.IsActive)
                                     .OrderBy(e => e.Order)
                                     .ToList();

                    // Get the required event types
                    var requiredEventTypes = eventTypes
                                             .Where(e => e.IsNoticeRequired)
                                             .ToList();

                    // The people who are eligible to get following event notices based on the group type setting for this job
                    var eligiblePersonIds = new GroupMemberService(rockContext)
                                            .Queryable().AsNoTracking()
                                            .Where(m =>
                                                   m.Group != null &&
                                                   m.Group.Guid.Equals(groupGuid.Value) &&
                                                   m.GroupMemberStatus == GroupMemberStatus.Active &&
                                                   m.Person != null &&
                                                   m.Person.Email != null &&
                                                   m.Person.Email != string.Empty &&
                                                   m.Person.EmailPreference != EmailPreference.DoNotEmail &&
                                                   m.Person.IsEmailActive
                                                   )
                                            .Select(m => m.PersonId)
                                            .Distinct()
                                            .ToList();

                    // Get all the subscriptions for the eligible people
                    var eventSubscriptions = new FollowingEventSubscriptionService(rockContext)
                                             .Queryable("PersonAlias").AsNoTracking()
                                             .Where(f => eligiblePersonIds.Contains(f.PersonAlias.PersonId))
                                             .ToList();

                    // Dictionaries used to store information that will be used to create notification
                    var personSubscriptions = new Dictionary <int, List <int> >();                  // Key: personId, Value: list of event type ids that person subscribes to
                    var personFollowings    = new Dictionary <int, List <int> >();                  // Key: personId, Value: list of following ids that person follows
                    var eventsThatHappened  = new Dictionary <int, Dictionary <int, string> >();    // Key: event type id Value: Dictionary of entity id and formatted event notice for the entity

                    //Get the subscriptions for each person
                    foreach (int personId in eligiblePersonIds)
                    {
                        var personEventTypes = eventSubscriptions
                                               .Where(s => s.PersonAlias.PersonId == personId)
                                               .Select(s => s.EventType)
                                               .ToList();
                        personEventTypes.AddRange(requiredEventTypes);
                        if (personEventTypes.Any())
                        {
                            personSubscriptions.AddOrIgnore(personId, personEventTypes
                                                            .OrderBy(e => e.Order)
                                                            .ThenBy(e => e.Name)
                                                            .Select(e => e.Id)
                                                            .Distinct()
                                                            .ToList());
                        }
                    }

                    // Get a distinct list of each entitytype/entity that is being followed by anyone that subscribes to events
                    var followings = followingService
                                     .Queryable("PersonAlias").AsNoTracking()
                                     .Where(f => personSubscriptions.Keys.Contains(f.PersonAlias.PersonId))
                                     .ToList();

                    // group the followings by their type
                    var followedEntityIds = new Dictionary <int, List <int> >();
                    foreach (var followedEntity in followings
                             .Select(f => new
                    {
                        f.EntityTypeId,
                        f.EntityId
                    })
                             .Distinct())
                    {
                        followedEntityIds.AddOrIgnore(followedEntity.EntityTypeId, new List <int>());
                        followedEntityIds[followedEntity.EntityTypeId].Add(followedEntity.EntityId);
                    }

                    // group the followings by the follower
                    foreach (int personId in personSubscriptions.Select(s => s.Key))
                    {
                        var personFollowing = followings
                                              .Where(f => f.PersonAlias.PersonId == personId)
                                              .Select(f => f.Id)
                                              .ToList();

                        personFollowings.Add(personId, personFollowing);
                    }

                    var timestamp = RockDateTime.Now;

                    // foreach followed entitytype
                    foreach (var keyVal in followedEntityIds)
                    {
                        // Get the entitytype
                        EntityTypeCache itemEntityType = EntityTypeCache.Get(keyVal.Key);
                        if (itemEntityType != null && itemEntityType.AssemblyName != null)
                        {
                            // get the actual type of what is being followed
                            Type entityType = itemEntityType.GetEntityType();
                            if (entityType != null)
                            {
                                var dbContext = Reflection.GetDbContextForEntityType(entityType);
                                if (dbContext != null)
                                {
                                    var serviceInstance = Reflection.GetServiceForEntityType(entityType, dbContext);
                                    if (serviceInstance != null)
                                    {
                                        MethodInfo qryMethod = serviceInstance.GetType().GetMethod("Queryable", new Type[] { });
                                        var        entityQry = qryMethod.Invoke(serviceInstance, new object[] { }) as IQueryable <IEntity>;

                                        // If looking at person alias following, make sure to exclude deceased people
                                        if (entityType == typeof(Rock.Model.PersonAlias))
                                        {
                                            var personAliasQry = entityQry as IQueryable <PersonAlias>;
                                            if (personAliasQry != null)
                                            {
                                                entityQry = personAliasQry.Where(p => !p.Person.IsDeceased);
                                            }
                                        }

                                        var entityList = entityQry.Where(q => keyVal.Value.Contains(q.Id)).ToList();

                                        // If there are any followed entities of this type
                                        if (entityList.Any())
                                        {
                                            // Get the active event types for this entity type
                                            foreach (var eventType in eventTypes.Where(e => e.FollowedEntityTypeId == keyVal.Key))
                                            {
                                                try
                                                {
                                                    // Get the component
                                                    var eventComponent = eventType.GetEventComponent();
                                                    if (eventComponent != null)
                                                    {
                                                        // Get the previous notifications for this event type
                                                        var previousNotifications = followingEventNotificationService
                                                                                    .Queryable()
                                                                                    .Where(n => n.FollowingEventTypeId == eventType.Id)
                                                                                    .ToList();

                                                        // check each entity that is followed (by anyone)
                                                        foreach (IEntity entity in entityList)
                                                        {
                                                            var previousNotification = previousNotifications
                                                                                       .Where(n => n.EntityId == entity.Id)
                                                                                       .FirstOrDefault();
                                                            DateTime?lastNotification = previousNotification != null ? previousNotification.LastNotified : (DateTime?)null;

                                                            // If ok to send on this day
                                                            var today = RockDateTime.Today;
                                                            if (eventType.SendOnWeekends || (today.DayOfWeek != DayOfWeek.Saturday && today.DayOfWeek != DayOfWeek.Sunday))
                                                            {
                                                                // if the event happened
                                                                if (eventComponent.HasEventHappened(eventType, entity, lastNotification))
                                                                {
                                                                    // Store the event type id and the entity for later processing of notifications
                                                                    eventsThatHappened.AddOrIgnore(eventType.Id, new Dictionary <int, string>());
                                                                    eventsThatHappened[eventType.Id].Add(entity.Id, eventComponent.FormatEntityNotification(eventType, entity));

                                                                    if (previousNotification == null)
                                                                    {
                                                                        previousNotification = new FollowingEventNotification();
                                                                        previousNotification.FollowingEventTypeId = eventType.Id;
                                                                        previousNotification.EntityId             = entity.Id;
                                                                        followingEventNotificationService.Add(previousNotification);
                                                                    }
                                                                    previousNotification.LastNotified = timestamp;
                                                                }
                                                            }
                                                        }

                                                        rockContext.SaveChanges();
                                                    }

                                                    eventType.LastCheckDateTime = RockDateTime.Now;
                                                }

                                                catch (Exception ex)
                                                {
                                                    exceptionMsgs.Add(string.Format("An exception occurred calculating events for the '{0}' suggestion type:{1}    {2}", eventType.Name, Environment.NewLine, ex.Messages().AsDelimited(Environment.NewLine + "   ")));
                                                    ExceptionLogService.LogException(ex, System.Web.HttpContext.Current);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }

                    // send notifications
                    var possibleRecipients = new PersonService(rockContext)
                                             .Queryable().AsNoTracking()
                                             .Where(p => personSubscriptions.Keys.Contains(p.Id))
                                             .ToList();

                    // Loop through the possible recipients that actually subscribe to events
                    foreach (var personSubscription in personSubscriptions)
                    {
                        // Get the recipient person
                        int personId = personSubscription.Key;
                        var person   = possibleRecipients.Where(p => p.Id == personId).FirstOrDefault();
                        if (person != null)
                        {
                            try
                            {
                                // Make sure person is actually following anything
                                if (personFollowings.ContainsKey(personId))
                                {
                                    // Dictionary to store the entities that had an event for each event type
                                    var personEventTypeNotices = new List <FollowingEventTypeNotices>();

                                    // Get the event types that person subscribes to
                                    foreach (var eventType in eventsThatHappened.Where(e => personSubscription.Value.Contains(e.Key)))
                                    {
                                        // Get the EntityTypeId for this event type
                                        int entityTypeId = eventTypes
                                                           .Where(e => e.Id == eventType.Key)
                                                           .Select(e => e.FollowedEntityTypeId.Value)
                                                           .FirstOrDefault();

                                        // Find all the entities with this event type that the person follows
                                        var personFollowedEntityIds = followings
                                                                      .Where(f =>
                                                                             personFollowings[personId].Contains(f.Id) &&
                                                                             f.EntityTypeId == entityTypeId)
                                                                      .Select(f => f.EntityId)
                                                                      .ToList();

                                        // Get any of those entities that had an event happen
                                        var personFollowedEntities = eventType.Value
                                                                     .Where(e => personFollowedEntityIds.Contains(e.Key))
                                                                     .ToList();

                                        // If any were found
                                        if (personFollowedEntities.Any())
                                        {
                                            // Add the entry
                                            var eventTypeObj = eventTypes.Where(e => e.Id == eventType.Key).FirstOrDefault();
                                            if (eventTypeObj != null)
                                            {
                                                personEventTypeNotices.Add(new FollowingEventTypeNotices(eventTypeObj, personFollowedEntities.Select(e => e.Value).ToList()));
                                            }
                                        }
                                    }

                                    // If there are any events for any of the entities that this person follows, send a notification
                                    if (personEventTypeNotices.Any())
                                    {
                                        // Send the notice
                                        var mergeFields = new Dictionary <string, object>();
                                        mergeFields.Add("Person", person);
                                        mergeFields.Add("EventTypes", personEventTypeNotices.OrderBy(e => e.EventType.Order).ToList());

                                        var emailMessage = new RockEmailMessage(systemEmailGuid.Value);
                                        emailMessage.AddRecipient(new RockEmailMessageRecipient(person, mergeFields));
                                        var errors = new List <string>();
                                        emailMessage.Send(out errors);
                                        exceptionMsgs.AddRange(errors);
                                        if (!errors.Any())
                                        {
                                            followingEventsSent++;
                                        }
                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                                exceptionMsgs.Add(string.Format("An exception occurred sending event notice to '{0}':{1}    {2}", person.FullName, Environment.NewLine, ex.Messages().AsDelimited(Environment.NewLine + "   ")));
                                ExceptionLogService.LogException(ex, System.Web.HttpContext.Current);
                            }
                        }
                    }
                }

                context.Result = string.Format("{0} following events emails sent", followingEventsSent);

                if (exceptionMsgs.Any())
                {
                    throw new Exception("One or more exceptions occurred calculating following events..." + Environment.NewLine + exceptionMsgs.AsDelimited(Environment.NewLine));
                }
            }
        }
        /// <summary>
        /// Executes the specified context.
        /// </summary>
        /// <param name="context">The context.</param>
        public void Execute(IJobExecutionContext context)
        {
            var exceptionMsgs = new List <string>();

            JobDataMap dataMap         = context.JobDetail.JobDataMap;
            Guid?      groupGuid       = dataMap.GetString("EligibleFollowers").AsGuidOrNull();
            Guid?      systemEmailGuid = dataMap.GetString("EmailTemplate").AsGuidOrNull();
            int        followingSuggestionsEmailsSent       = 0;
            int        followingSuggestionsSuggestionsTotal = 0;

            if (groupGuid.HasValue && systemEmailGuid.HasValue)
            {
                using (var rockContext = new RockContext())
                {
                    var followingService = new FollowingService(rockContext);

                    // The people who are eligible to get following suggestions based on the group type setting for this job
                    var eligiblePersonIds = new GroupMemberService(rockContext)
                                            .Queryable().AsNoTracking()
                                            .Where(m =>
                                                   m.Group != null &&
                                                   m.Group.Guid.Equals(groupGuid.Value) &&
                                                   m.GroupMemberStatus == GroupMemberStatus.Active &&
                                                   m.Person != null &&
                                                   m.Person.Email != null &&
                                                   m.Person.Email != "")
                                            .Select(m => m.PersonId)
                                            .Distinct();

                    // check to see if there are any event types that require notification
                    var followerPersonIds = new List <int>();
                    if (new FollowingEventTypeService(rockContext)
                        .Queryable().AsNoTracking()
                        .Any(e => e.IsNoticeRequired))
                    {
                        // if so, include all eligible people
                        followerPersonIds = eligiblePersonIds.ToList();
                    }
                    else
                    {
                        // if not, filter the list of eligible people down to only those that actually have subscribed to one or more following events
                        followerPersonIds = new FollowingEventSubscriptionService(rockContext)
                                            .Queryable().AsNoTracking()
                                            .Where(f => eligiblePersonIds.Contains(f.PersonAlias.PersonId))
                                            .Select(f => f.PersonAlias.PersonId)
                                            .Distinct()
                                            .ToList();
                    }

                    if (followerPersonIds.Any())
                    {
                        // Get the primary person alias id for each of the followers
                        var primaryAliasIds = new Dictionary <int, int>();
                        new PersonAliasService(rockContext)
                        .Queryable().AsNoTracking()
                        .Where(a =>
                               followerPersonIds.Contains(a.PersonId) &&
                               a.PersonId == a.AliasPersonId)
                        .ToList()
                        .ForEach(a => primaryAliasIds.AddOrIgnore(a.PersonId, a.Id));

                        // Get current date/time.
                        var timestamp = RockDateTime.Now;

                        var suggestionTypes = new FollowingSuggestionTypeService(rockContext)
                                              .Queryable().AsNoTracking()
                                              .Where(s => s.IsActive)
                                              .OrderBy(s => s.Name)
                                              .ToList();

                        var components        = new Dictionary <int, SuggestionComponent>();
                        var suggestedEntities = new Dictionary <int, Dictionary <int, IEntity> >();

                        foreach (var suggestionType in suggestionTypes)
                        {
                            try
                            {
                                // Get the suggestion type component
                                var suggestionComponent = suggestionType.GetSuggestionComponent();
                                if (suggestionComponent != null)
                                {
                                    components.Add(suggestionType.Id, suggestionComponent);

                                    // Get the entitytype for this suggestion type
                                    var suggestionEntityType = EntityTypeCache.Read(suggestionComponent.FollowedType);
                                    if (suggestionEntityType != null)
                                    {
                                        var entityIds = new List <int>();

                                        // Call the components method to return all of it's suggestions
                                        var personEntitySuggestions = suggestionComponent.GetSuggestions(suggestionType, followerPersonIds);

                                        // If any suggestions were returned by the component
                                        if (personEntitySuggestions.Any())
                                        {
                                            int    entityTypeId = suggestionEntityType.Id;
                                            string reasonNote   = suggestionType.ReasonNote;

                                            // Get the existing followings for any of the followers
                                            var existingFollowings = new Dictionary <int, List <int> >();
                                            foreach (var following in followingService.Queryable("PersonAlias").AsNoTracking()
                                                     .Where(f =>
                                                            f.EntityTypeId == entityTypeId &&
                                                            followerPersonIds.Contains(f.PersonAlias.PersonId)))
                                            {
                                                existingFollowings.AddOrIgnore(following.PersonAlias.PersonId, new List <int>());
                                                existingFollowings[following.PersonAlias.PersonId].Add(following.EntityId);
                                            }

                                            // Loop through each follower
                                            foreach (var followerPersonId in personEntitySuggestions
                                                     .Select(s => s.PersonId)
                                                     .Distinct())
                                            {
                                                using (var suggestionContext = new RockContext())
                                                {
                                                    var followingSuggestedService = new FollowingSuggestedService(suggestionContext);

                                                    // Read all the existing suggestions for this type and the returned followers
                                                    var existingSuggestions = followingSuggestedService
                                                                              .Queryable("PersonAlias")
                                                                              .Where(s =>
                                                                                     s.SuggestionTypeId == suggestionType.Id &&
                                                                                     s.PersonAlias.PersonId == followerPersonId)
                                                                              .ToList();

                                                    // Look  through the returned suggestions
                                                    foreach (var followedEntityId in personEntitySuggestions
                                                             .Where(s => s.PersonId == followerPersonId)
                                                             .Select(s => s.EntityId))
                                                    {
                                                        // Make sure person isn't already following this entity
                                                        if (!existingFollowings.ContainsKey(followerPersonId) ||
                                                            !existingFollowings[followerPersonId].Contains(followedEntityId))
                                                        {
                                                            // If this person had a primary alias id
                                                            if (primaryAliasIds.ContainsKey(followerPersonId))
                                                            {
                                                                entityIds.Add(followedEntityId);

                                                                // Look for existing suggestion for this person and entity
                                                                var suggestion = existingSuggestions
                                                                                 .Where(s => s.EntityId == followedEntityId)
                                                                                 .OrderByDescending(s => s.StatusChangedDateTime)
                                                                                 .FirstOrDefault();

                                                                // If not found, add one
                                                                if (suggestion == null)
                                                                {
                                                                    suggestion = new FollowingSuggested();
                                                                    suggestion.EntityTypeId          = entityTypeId;
                                                                    suggestion.EntityId              = followedEntityId;
                                                                    suggestion.PersonAliasId         = primaryAliasIds[followerPersonId];
                                                                    suggestion.SuggestionTypeId      = suggestionType.Id;
                                                                    suggestion.Status                = FollowingSuggestedStatus.PendingNotification;
                                                                    suggestion.StatusChangedDateTime = timestamp;
                                                                    followingSuggestedService.Add(suggestion);
                                                                }
                                                                else
                                                                {
                                                                    // If found, and it has not been ignored, and it's time to promote again, update the promote date
                                                                    if (suggestion.Status != FollowingSuggestedStatus.Ignored &&
                                                                        (
                                                                            !suggestionType.ReminderDays.HasValue ||
                                                                            !suggestion.LastPromotedDateTime.HasValue ||
                                                                            suggestion.LastPromotedDateTime.Value.AddDays(suggestionType.ReminderDays.Value) <= timestamp
                                                                        ))
                                                                    {
                                                                        if (suggestion.Status != FollowingSuggestedStatus.PendingNotification)
                                                                        {
                                                                            suggestion.StatusChangedDateTime = timestamp;
                                                                            suggestion.Status = FollowingSuggestedStatus.PendingNotification;
                                                                        }
                                                                    }
                                                                }
                                                            }
                                                        }
                                                    }

                                                    // Save the suggestions for this type
                                                    suggestionContext.SaveChanges();
                                                }
                                            }
                                        }

                                        // If any entities are being suggested for this type, query database for them and save to dictionary
                                        if (entityIds.Any())
                                        {
                                            if (suggestionEntityType.AssemblyName != null)
                                            {
                                                // get the actual type of what is being followed
                                                Type entityType = suggestionEntityType.GetEntityType();
                                                if (entityType != null)
                                                {
                                                    // Get generic queryable method and query all the entities that are being followed
                                                    Type[]             modelType          = { entityType };
                                                    Type               genericServiceType = typeof(Rock.Data.Service <>);
                                                    Type               modelServiceType   = genericServiceType.MakeGenericType(modelType);
                                                    Rock.Data.IService serviceInstance    = Activator.CreateInstance(modelServiceType, new object[] { rockContext }) as IService;
                                                    MethodInfo         qryMethod          = serviceInstance.GetType().GetMethod("Queryable", new Type[] { });
                                                    var entityQry  = qryMethod.Invoke(serviceInstance, new object[] { }) as IQueryable <IEntity>;
                                                    var entityList = entityQry.AsNoTracking().Where(q => entityIds.Contains(q.Id)).ToList();
                                                    if (entityList != null && entityList.Any())
                                                    {
                                                        var entities = new Dictionary <int, IEntity>();
                                                        entityList.ForEach(e => entities.Add(e.Id, e));
                                                        suggestedEntities.Add(suggestionType.Id, entities);
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                                exceptionMsgs.Add(string.Format("An exception occurred calculating suggestions for the '{0}' suggestion type:{1}    {2}", suggestionType.Name, Environment.NewLine, ex.Messages().AsDelimited(Environment.NewLine + "   ")));
                                ExceptionLogService.LogException(ex, System.Web.HttpContext.Current);
                            }
                        }

                        var allSuggestions = new FollowingSuggestedService(rockContext)
                                             .Queryable("PersonAlias")
                                             .Where(s => s.Status == FollowingSuggestedStatus.PendingNotification)
                                             .ToList();

                        var suggestionPersonIds = allSuggestions
                                                  .Where(s => followerPersonIds.Contains(s.PersonAlias.PersonId))
                                                  .Select(s => s.PersonAlias.PersonId)
                                                  .Distinct()
                                                  .ToList();

                        var appRoot = Rock.Web.Cache.GlobalAttributesCache.Read(rockContext).GetValue("ExternalApplicationRoot");

                        foreach (var person in new PersonService(rockContext)
                                 .Queryable().AsNoTracking()
                                 .Where(p => suggestionPersonIds.Contains(p.Id))
                                 .ToList())
                        {
                            try
                            {
                                var personSuggestionNotices = new List <FollowingSuggestionNotices>();

                                foreach (var suggestionType in suggestionTypes)
                                {
                                    var component = components.ContainsKey(suggestionType.Id) ? components[suggestionType.Id] : null;
                                    if (component != null && suggestedEntities.ContainsKey(suggestionType.Id))
                                    {
                                        var entities = new List <IEntity>();
                                        foreach (var suggestion in allSuggestions
                                                 .Where(s =>
                                                        s.PersonAlias.PersonId == person.Id &&
                                                        s.SuggestionTypeId == suggestionType.Id)
                                                 .ToList())
                                        {
                                            if (suggestedEntities[suggestionType.Id].ContainsKey(suggestion.EntityId))
                                            {
                                                entities.Add(suggestedEntities[suggestionType.Id][suggestion.EntityId]);
                                                suggestion.LastPromotedDateTime = timestamp;
                                                suggestion.Status = FollowingSuggestedStatus.Suggested;
                                            }
                                        }

                                        var notices = new List <string>();
                                        foreach (var entity in component.SortEntities(entities))
                                        {
                                            notices.Add(component.FormatEntityNotification(suggestionType, entity));
                                        }

                                        if (notices.Any())
                                        {
                                            personSuggestionNotices.Add(new FollowingSuggestionNotices(suggestionType, notices));
                                        }
                                    }
                                }

                                if (personSuggestionNotices.Any())
                                {
                                    // Send the notice
                                    var recipients  = new List <RecipientData>();
                                    var mergeFields = new Dictionary <string, object>();
                                    mergeFields.Add("Person", person);
                                    mergeFields.Add("Suggestions", personSuggestionNotices.OrderBy(s => s.SuggestionType.Order).ToList());
                                    recipients.Add(new RecipientData(person.Email, mergeFields));
                                    Email.Send(systemEmailGuid.Value, recipients, appRoot);
                                    followingSuggestionsEmailsSent       += recipients.Count();
                                    followingSuggestionsSuggestionsTotal += personSuggestionNotices.Count();
                                }

                                rockContext.SaveChanges();
                            }
                            catch (Exception ex)
                            {
                                exceptionMsgs.Add(string.Format("An exception occurred sending suggestions to '{0}':{1}    {2}", person.FullName, Environment.NewLine, ex.Messages().AsDelimited(Environment.NewLine + "   ")));
                                ExceptionLogService.LogException(ex, System.Web.HttpContext.Current);
                            }
                        }
                    }
                }
            }

            context.Result = string.Format("A total of {0} following suggestions sent to {1} people", followingSuggestionsSuggestionsTotal, followingSuggestionsEmailsSent);

            if (exceptionMsgs.Any())
            {
                throw new Exception("One or more exceptions occurred calculating suggestions..." + Environment.NewLine + exceptionMsgs.AsDelimited(Environment.NewLine));
            }
        }
        /// <summary>
        /// Executes the specified context.
        /// </summary>
        /// <param name="context">The context.</param>
        public void Execute( IJobExecutionContext context )
        {
            JobDataMap dataMap = context.JobDetail.JobDataMap;
            Guid? groupGuid = dataMap.GetString( "EligibleFollowers" ).AsGuidOrNull();
            Guid? systemEmailGuid = dataMap.GetString( "EmailTemplate" ).AsGuidOrNull();
            int followingEventsSent = 0;

            if ( groupGuid.HasValue && systemEmailGuid.HasValue )
            {
                var exceptionMsgs = new List<string>();

                using ( var rockContext = new RockContext() )
                {
                    var followingService = new FollowingService( rockContext );
                    var followingEventTypeService = new FollowingEventTypeService( rockContext );
                    var followingEventNotificationService = new FollowingEventNotificationService( rockContext );

                    // Get all the active event types
                    var eventTypes = followingEventTypeService
                        .Queryable().AsNoTracking()
                        .Where( e =>
                            e.EntityTypeId.HasValue &&
                            e.IsActive )
                        .OrderBy( e => e.Order )
                        .ToList();

                    // Get the required event types
                    var requiredEventTypes = eventTypes
                        .Where( e => e.IsNoticeRequired )
                        .ToList();

                    // The people who are eligible to get following event notices based on the group type setting for this job
                    var eligiblePersonIds = new GroupMemberService( rockContext )
                        .Queryable().AsNoTracking()
                        .Where( m =>
                            m.Group != null &&
                            m.Group.Guid.Equals( groupGuid.Value ) &&
                            m.GroupMemberStatus == GroupMemberStatus.Active &&
                            m.Person != null &&
                            m.Person.Email != null &&
                            m.Person.Email != "" )
                        .Select( m => m.PersonId )
                        .Distinct()
                        .ToList();

                    // Get all the subscriptions for the eligible people
                    var eventSubscriptions = new FollowingEventSubscriptionService( rockContext )
                        .Queryable( "PersonAlias" ).AsNoTracking()
                        .Where( f => eligiblePersonIds.Contains( f.PersonAlias.PersonId ) )
                        .ToList();

                    // Dictionaries used to store information that will be used to create notification
                    var personSubscriptions = new Dictionary<int, List<int>>();                     // Key: personId, Value: list of event type ids that person subscribes to
                    var personFollowings = new Dictionary<int, List<int>>();                        // Key: personId, Value: list of following ids that person follows
                    var eventsThatHappened = new Dictionary<int, Dictionary<int, string>>();        // Key: event type id Value: Dictionary of entity id and formatted event notice for the entity

                    //Get the subscriptions for each person
                    foreach ( int personId in eligiblePersonIds )
                    {
                        var personEventTypes = eventSubscriptions
                            .Where( s => s.PersonAlias.PersonId == personId )
                            .Select( s => s.EventType )
                            .ToList();
                        personEventTypes.AddRange( requiredEventTypes );
                        if ( personEventTypes.Any() )
                        {
                            personSubscriptions.AddOrIgnore( personId, personEventTypes
                                .OrderBy( e => e.Order )
                                .ThenBy( e => e.Name )
                                .Select( e => e.Id )
                                .Distinct()
                                .ToList() );
                        }
                    }

                    // Get a distinct list of each entitytype/entity that is being followed by anyone that subscribes to events
                    var followings = followingService
                        .Queryable( "PersonAlias" ).AsNoTracking()
                        .Where( f => personSubscriptions.Keys.Contains( f.PersonAlias.PersonId ) )
                        .ToList();

                    // group the followings by their type
                    var followedEntityIds = new Dictionary<int, List<int>>();
                    foreach ( var followedEntity in followings
                        .Select( f => new
                        {
                            f.EntityTypeId,
                            f.EntityId
                        } )
                        .Distinct() )
                    {
                        followedEntityIds.AddOrIgnore( followedEntity.EntityTypeId, new List<int>() );
                        followedEntityIds[followedEntity.EntityTypeId].Add( followedEntity.EntityId );
                    }

                    // group the followings by the follower
                    foreach ( int personId in personSubscriptions.Select( s => s.Key ) )
                    {
                        var personFollowing = followings
                            .Where( f => f.PersonAlias.PersonId == personId )
                            .Select( f => f.Id )
                            .ToList();

                        personFollowings.Add( personId, personFollowing );
                    }

                    var timestamp = RockDateTime.Now;

                    // foreach followed entitytype
                    foreach ( var keyVal in followedEntityIds )
                    {
                        // Get the entitytype
                        EntityTypeCache itemEntityType = EntityTypeCache.Read( keyVal.Key );
                        if ( itemEntityType.AssemblyName != null )
                        {
                            // get the actual type of what is being followed
                            Type entityType = itemEntityType.GetEntityType();
                            if ( entityType != null )
                            {
                                var dbContext = Reflection.GetDbContextForEntityType( entityType );
                                if ( dbContext != null )
                                {
                                    var serviceInstance = Reflection.GetServiceForEntityType( entityType, dbContext );
                                    if ( serviceInstance != null )
                                    {
                                        MethodInfo qryMethod = serviceInstance.GetType().GetMethod( "Queryable", new Type[] { } );
                                        var entityQry = qryMethod.Invoke( serviceInstance, new object[] { } ) as IQueryable<IEntity>;

                                        // If looking at person alias following, make sure to exclude deceased people
                                        if ( entityType == typeof( Rock.Model.PersonAlias ) )
                                        {
                                            var personAliasQry = entityQry as IQueryable<PersonAlias>;
                                            if ( personAliasQry != null )
                                            {
                                                entityQry = personAliasQry.Where( p => !p.Person.IsDeceased );
                                            }
                                        }

                                        var entityList = entityQry.Where( q => keyVal.Value.Contains( q.Id ) ).ToList();

                                        // If there are any followed entities of this type
                                        if ( entityList.Any() )
                                        {
                                            // Get the active event types for this entity type
                                            foreach ( var eventType in eventTypes.Where( e => e.FollowedEntityTypeId == keyVal.Key ) )
                                            {
                                                try
                                                {
                                                    // Get the component
                                                    var eventComponent = eventType.GetEventComponent();
                                                    if ( eventComponent != null )
                                                    {
                                                        // Get the previous notificatoins for this event type
                                                        var previousNotifications = followingEventNotificationService
                                                            .Queryable()
                                                            .Where( n => n.FollowingEventTypeId == eventType.Id )
                                                            .ToList();

                                                        // check each entity that is followed (by anyone)
                                                        foreach ( IEntity entity in entityList )
                                                        {
                                                            var previousNotification = previousNotifications
                                                                .Where( n => n.EntityId == entity.Id )
                                                                .FirstOrDefault();
                                                            DateTime? lastNotification = previousNotification != null ? previousNotification.LastNotified : (DateTime?)null;

                                                            // if the event happened
                                                            if ( eventComponent.HasEventHappened( eventType, entity, lastNotification ) )
                                                            {
                                                                // Store the event type id and the entity for later processing of notifications
                                                                eventsThatHappened.AddOrIgnore( eventType.Id, new Dictionary<int, string>() );
                                                                eventsThatHappened[eventType.Id].Add( entity.Id, eventComponent.FormatEntityNotification( eventType, entity ) );

                                                                if ( previousNotification == null )
                                                                {
                                                                    previousNotification = new FollowingEventNotification();
                                                                    previousNotification.FollowingEventTypeId = eventType.Id;
                                                                    previousNotification.EntityId = entity.Id;
                                                                    followingEventNotificationService.Add( previousNotification );
                                                                }
                                                                previousNotification.LastNotified = timestamp;
                                                            }
                                                        }

                                                        rockContext.SaveChanges();
                                                    }

                                                    eventType.LastCheckDateTime = RockDateTime.Now;
                                                }

                                                catch ( Exception ex )
                                                {
                                                    exceptionMsgs.Add( string.Format( "An exception occurred calculating events for the '{0}' suggestion type:{1}    {2}", eventType.Name, Environment.NewLine, ex.Messages().AsDelimited( Environment.NewLine + "   " ) ) );
                                                    ExceptionLogService.LogException( ex, System.Web.HttpContext.Current );
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }

                    // send notificatons
                    var appRoot = Rock.Web.Cache.GlobalAttributesCache.Read( rockContext ).GetValue( "ExternalApplicationRoot" );

                    var possibleRecipients = new PersonService( rockContext )
                        .Queryable().AsNoTracking()
                        .Where( p => personSubscriptions.Keys.Contains( p.Id ) )
                        .ToList();

                    // Loop through the possible recipients that actually subscribe to events
                    foreach ( var personSubscription in personSubscriptions )
                    {
                        // Get the recipient person
                        int personId = personSubscription.Key;
                        var person = possibleRecipients.Where( p => p.Id == personId ).FirstOrDefault();
                        if ( person != null )
                        {
                            try
                            {

                                // Make sure person is actually following anything
                                if ( personFollowings.ContainsKey( personId ) )
                                {
                                    // Dictionary to store the entities that had an event for each event type
                                    var personEventTypeNotices = new List<FollowingEventTypeNotices>();

                                    // Get the event types that person subscribes to
                                    foreach ( var eventType in eventsThatHappened.Where( e => personSubscription.Value.Contains( e.Key ) ) )
                                    {
                                        // Get the EntityTypeId for this event type
                                        int entityTypeId = eventTypes
                                            .Where( e => e.Id == eventType.Key )
                                            .Select( e => e.FollowedEntityTypeId.Value )
                                            .FirstOrDefault();

                                        // Find all the entities with this event type that the person follows
                                        var personFollowedEntityIds = followings
                                            .Where( f =>
                                                personFollowings[personId].Contains( f.Id ) &&
                                                f.EntityTypeId == entityTypeId )
                                            .Select( f => f.EntityId )
                                            .ToList();

                                        // Get any of those entities that had an event happen
                                        var personFollowedEntities = eventType.Value
                                            .Where( e => personFollowedEntityIds.Contains( e.Key ) )
                                            .ToList();

                                        // If any were found
                                        if ( personFollowedEntities.Any() )
                                        {
                                            // Add the entry
                                            var eventTypeObj = eventTypes.Where( e => e.Id == eventType.Key ).FirstOrDefault();
                                            if ( eventTypeObj != null )
                                            {
                                                personEventTypeNotices.Add( new FollowingEventTypeNotices( eventTypeObj, personFollowedEntities.Select( e => e.Value ).ToList() ) );
                                            }
                                        }
                                    }

                                    // If there are any events for any of the entities that this person follows, send a notification
                                    if ( personEventTypeNotices.Any() )
                                    {
                                        // Send the notice
                                        var recipients = new List<RecipientData>();
                                        var mergeFields = new Dictionary<string, object>();
                                        mergeFields.Add( "Person", person );
                                        mergeFields.Add( "EventTypes", personEventTypeNotices.OrderBy( e => e.EventType.Order ).ToList() );
                                        recipients.Add( new RecipientData( person.Email, mergeFields ) );
                                        Email.Send( systemEmailGuid.Value, recipients, appRoot );
                                        followingEventsSent++;
                                    }
                                }
                            }
                            catch ( Exception ex )
                            {
                                exceptionMsgs.Add( string.Format( "An exception occurred sending event notice to '{0}':{1}    {2}", person.FullName, Environment.NewLine, ex.Messages().AsDelimited( Environment.NewLine + "   " ) ) );
                                ExceptionLogService.LogException( ex, System.Web.HttpContext.Current );
                            }
                        }
                    }
                }

                context.Result = string.Format( "{0} following events emails sent", followingEventsSent );

                if ( exceptionMsgs.Any() )
                {
                    throw new Exception( "One or more exceptions occurred calculating following events..." + Environment.NewLine + exceptionMsgs.AsDelimited( Environment.NewLine ) );
                }
            }
        }
Esempio n. 6
0
        /// <summary>
        /// Executes the specified context.
        /// </summary>
        /// <param name="context">The context.</param>
        public void Execute( IJobExecutionContext context )
        {
            var exceptionMsgs = new List<string>();

            JobDataMap dataMap = context.JobDetail.JobDataMap;
            Guid? groupGuid = dataMap.GetString( "EligibleFollowers" ).AsGuidOrNull();
            Guid? systemEmailGuid = dataMap.GetString( "EmailTemplate" ).AsGuidOrNull();
            int followingSuggestionsEmailsSent = 0;
            int followingSuggestionsSuggestionsTotal = 0;

            if ( groupGuid.HasValue && systemEmailGuid.HasValue )
            {
                using ( var rockContext = new RockContext() )
                {
                    var followingService = new FollowingService( rockContext );

                    // The people who are eligible to get following suggestions based on the group type setting for this job
                    var eligiblePersonIds = new GroupMemberService( rockContext )
                        .Queryable().AsNoTracking()
                        .Where( m =>
                            m.Group != null &&
                            m.Group.Guid.Equals( groupGuid.Value ) &&
                            m.GroupMemberStatus == GroupMemberStatus.Active &&
                            m.Person != null &&
                            m.Person.Email != null &&
                            m.Person.Email != "" )
                        .Select( m => m.PersonId )
                        .Distinct();

                    // check to see if there are any event types that require notification
                    var followerPersonIds = new List<int>();
                    if ( new FollowingEventTypeService( rockContext )
                        .Queryable().AsNoTracking()
                        .Any( e => e.IsNoticeRequired ) )
                    {
                        // if so, include all eligible people
                        followerPersonIds = eligiblePersonIds.ToList();
                    }
                    else
                    {
                        // if not, filter the list of eligible people down to only those that actually have subscribed to one or more following events
                        followerPersonIds = new FollowingEventSubscriptionService( rockContext )
                            .Queryable().AsNoTracking()
                            .Where( f => eligiblePersonIds.Contains( f.PersonAlias.PersonId ) )
                            .Select( f => f.PersonAlias.PersonId )
                            .Distinct()
                            .ToList();
                    }

                    if ( followerPersonIds.Any() )
                    {
                        // Get the primary person alias id for each of the followers
                        var primaryAliasIds = new Dictionary<int, int>();
                        new PersonAliasService( rockContext )
                            .Queryable().AsNoTracking()
                            .Where( a =>
                                followerPersonIds.Contains( a.PersonId ) &&
                                a.PersonId == a.AliasPersonId )
                            .ToList()
                            .ForEach( a => primaryAliasIds.AddOrIgnore( a.PersonId, a.Id ) );

                        // Get current date/time.
                        var timestamp = RockDateTime.Now;

                        var suggestionTypes = new FollowingSuggestionTypeService( rockContext )
                            .Queryable().AsNoTracking()
                            .Where( s => s.IsActive )
                            .OrderBy( s => s.Name )
                            .ToList();

                        var components = new Dictionary<int, SuggestionComponent>();
                        var suggestedEntities = new Dictionary<int, Dictionary<int, IEntity>>();

                        foreach ( var suggestionType in suggestionTypes )
                        {
                            try
                            {
                                // Get the suggestion type component
                                var suggestionComponent = suggestionType.GetSuggestionComponent();
                                if ( suggestionComponent != null )
                                {
                                    components.Add( suggestionType.Id, suggestionComponent );

                                    // Get the entitytype for this suggestion type
                                    var suggestionEntityType = EntityTypeCache.Read( suggestionComponent.FollowedType );
                                    if ( suggestionEntityType != null )
                                    {
                                        var entityIds = new List<int>();

                                        // Call the components method to return all of it's suggestions
                                        var personEntitySuggestions = suggestionComponent.GetSuggestions( suggestionType, followerPersonIds );

                                        // If any suggestions were returned by the component
                                        if ( personEntitySuggestions.Any() )
                                        {
                                            int entityTypeId = suggestionEntityType.Id;
                                            string reasonNote = suggestionType.ReasonNote;

                                            // Get the existing followings for any of the followers
                                            var existingFollowings = new Dictionary<int, List<int>>();
                                            foreach( var following in followingService.Queryable( "PersonAlias" ).AsNoTracking()
                                                .Where( f =>
                                                    f.EntityTypeId == entityTypeId &&
                                                    followerPersonIds.Contains( f.PersonAlias.PersonId ) ) )
                                            {
                                                existingFollowings.AddOrIgnore( following.PersonAlias.PersonId, new List<int>() );
                                                existingFollowings[ following.PersonAlias.PersonId].Add( following.EntityId );
                                            }

                                            // Loop through each follower
                                            foreach ( var followerPersonId in personEntitySuggestions
                                                .Select( s => s.PersonId )
                                                .Distinct() )
                                            {

                                                using ( var suggestionContext = new RockContext() )
                                                {
                                                    var followingSuggestedService = new FollowingSuggestedService( suggestionContext );

                                                    // Read all the existing suggestions for this type and the returned followers
                                                    var existingSuggestions = followingSuggestedService
                                                        .Queryable( "PersonAlias" )
                                                        .Where( s =>
                                                            s.SuggestionTypeId == suggestionType.Id &&
                                                            s.PersonAlias.PersonId == followerPersonId )
                                                        .ToList();

                                                    // Look  through the returned suggestions
                                                    foreach ( var followedEntityId in personEntitySuggestions
                                                        .Where( s => s.PersonId == followerPersonId )
                                                        .Select( s => s.EntityId ) )
                                                    {
                                                        // Make sure person isn't already following this entity
                                                        if ( !existingFollowings.ContainsKey( followerPersonId )
                                                            || !existingFollowings[followerPersonId].Contains( followedEntityId ) )
                                                        {
                                                            // If this person had a primary alias id
                                                            if ( primaryAliasIds.ContainsKey( followerPersonId ) )
                                                            {
                                                                entityIds.Add( followedEntityId );

                                                                // Look for existing suggestion for this person and entity
                                                                var suggestion = existingSuggestions
                                                                    .Where( s => s.EntityId == followedEntityId )
                                                                    .OrderByDescending( s => s.StatusChangedDateTime )
                                                                    .FirstOrDefault();

                                                                // If not found, add one
                                                                if ( suggestion == null )
                                                                {
                                                                    suggestion = new FollowingSuggested();
                                                                    suggestion.EntityTypeId = entityTypeId;
                                                                    suggestion.EntityId = followedEntityId;
                                                                    suggestion.PersonAliasId = primaryAliasIds[followerPersonId];
                                                                    suggestion.SuggestionTypeId = suggestionType.Id;
                                                                    suggestion.Status = FollowingSuggestedStatus.PendingNotification;
                                                                    suggestion.StatusChangedDateTime = timestamp;
                                                                    followingSuggestedService.Add( suggestion );
                                                                }
                                                                else
                                                                {
                                                                    // If found, and it has not been ignored, and it's time to promote again, update the promote date
                                                                    if ( suggestion.Status != FollowingSuggestedStatus.Ignored &&
                                                                        (
                                                                            !suggestionType.ReminderDays.HasValue ||
                                                                            !suggestion.LastPromotedDateTime.HasValue ||
                                                                            suggestion.LastPromotedDateTime.Value.AddDays( suggestionType.ReminderDays.Value ) <= timestamp
                                                                        ) )
                                                                    {
                                                                        if ( suggestion.Status != FollowingSuggestedStatus.PendingNotification )
                                                                        {
                                                                            suggestion.StatusChangedDateTime = timestamp;
                                                                            suggestion.Status = FollowingSuggestedStatus.PendingNotification;
                                                                        }
                                                                    }
                                                                }
                                                            }
                                                        }
                                                    }

                                                    // Save the suggestions for this type
                                                    suggestionContext.SaveChanges();
                                                }
                                            }
                                        }

                                        // If any entities are being suggested for this type, query database for them and save to dictionary
                                        if ( entityIds.Any() )
                                        {
                                            if ( suggestionEntityType.AssemblyName != null )
                                            {
                                                // get the actual type of what is being followed
                                                Type entityType = suggestionEntityType.GetEntityType();
                                                if ( entityType != null )
                                                {
                                                    // Get generic queryable method and query all the entities that are being followed
                                                    Type[] modelType = { entityType };
                                                    Type genericServiceType = typeof( Rock.Data.Service<> );
                                                    Type modelServiceType = genericServiceType.MakeGenericType( modelType );
                                                    Rock.Data.IService serviceInstance = Activator.CreateInstance( modelServiceType, new object[] { rockContext } ) as IService;
                                                    MethodInfo qryMethod = serviceInstance.GetType().GetMethod( "Queryable", new Type[] { } );
                                                    var entityQry = qryMethod.Invoke( serviceInstance, new object[] { } ) as IQueryable<IEntity>;
                                                    var entityList = entityQry.AsNoTracking().Where( q => entityIds.Contains( q.Id ) ).ToList();
                                                    if ( entityList != null && entityList.Any() )
                                                    {
                                                        var entities = new Dictionary<int, IEntity>();
                                                        entityList.ForEach( e => entities.Add( e.Id, e ) );
                                                        suggestedEntities.Add( suggestionType.Id, entities );
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                            catch ( Exception ex )
                            {
                                exceptionMsgs.Add( string.Format( "An exception occurred calculating suggestions for the '{0}' suggestion type:{1}    {2}", suggestionType.Name, Environment.NewLine, ex.Messages().AsDelimited( Environment.NewLine + "   " ) ) );
                                ExceptionLogService.LogException( ex, System.Web.HttpContext.Current );
                            }
                        }

                        var allSuggestions = new FollowingSuggestedService( rockContext )
                            .Queryable( "PersonAlias" )
                            .Where( s => s.Status == FollowingSuggestedStatus.PendingNotification )
                            .ToList();

                        var suggestionPersonIds = allSuggestions
                            .Where( s => followerPersonIds.Contains( s.PersonAlias.PersonId ) )
                            .Select( s => s.PersonAlias.PersonId )
                            .Distinct()
                            .ToList();

                        var appRoot = Rock.Web.Cache.GlobalAttributesCache.Read( rockContext ).GetValue( "PublicApplicationRoot" );

                        foreach ( var person in new PersonService( rockContext )
                            .Queryable().AsNoTracking()
                            .Where( p => suggestionPersonIds.Contains( p.Id ) )
                            .ToList() )
                        {
                            try
                            {
                                var personSuggestionNotices = new List<FollowingSuggestionNotices>();

                                foreach ( var suggestionType in suggestionTypes )
                                {
                                    var component = components.ContainsKey( suggestionType.Id ) ? components[suggestionType.Id] : null;
                                    if ( component != null && suggestedEntities.ContainsKey( suggestionType.Id ) )
                                    {

                                        var entities = new List<IEntity>();
                                        foreach ( var suggestion in allSuggestions
                                            .Where( s =>
                                                s.PersonAlias.PersonId == person.Id &&
                                                s.SuggestionTypeId == suggestionType.Id )
                                            .ToList() )
                                        {
                                            if ( suggestedEntities[suggestionType.Id].ContainsKey( suggestion.EntityId ) )
                                            {
                                                entities.Add( suggestedEntities[suggestionType.Id][suggestion.EntityId] );
                                                suggestion.LastPromotedDateTime = timestamp;
                                                suggestion.Status = FollowingSuggestedStatus.Suggested;
                                            }
                                        }

                                        var notices = new List<string>();
                                        foreach ( var entity in component.SortEntities( entities ) )
                                        {
                                            notices.Add( component.FormatEntityNotification( suggestionType, entity ) );
                                        }

                                        if ( notices.Any() )
                                        {
                                            personSuggestionNotices.Add( new FollowingSuggestionNotices( suggestionType, notices ) );
                                        }
                                    }
                                }

                                if ( personSuggestionNotices.Any() )
                                {
                                    // Send the notice
                                    var recipients = new List<RecipientData>();
                                    var mergeFields = new Dictionary<string, object>();
                                    mergeFields.Add( "Person", person );
                                    mergeFields.Add( "Suggestions", personSuggestionNotices.OrderBy( s => s.SuggestionType.Order ).ToList() );
                                    recipients.Add( new RecipientData( person.Email, mergeFields ) );
                                    Email.Send( systemEmailGuid.Value, recipients, appRoot );
                                    followingSuggestionsEmailsSent += recipients.Count();
                                    followingSuggestionsSuggestionsTotal += personSuggestionNotices.Count();
                                }

                                rockContext.SaveChanges();
                            }
                            catch ( Exception ex )
                            {
                                exceptionMsgs.Add( string.Format( "An exception occurred sending suggestions to '{0}':{1}    {2}", person.FullName, Environment.NewLine, ex.Messages().AsDelimited( Environment.NewLine + "   " ) ) );
                                ExceptionLogService.LogException( ex, System.Web.HttpContext.Current );
                            }
                        }

                    }
                }
            }

            context.Result = string.Format( "A total of {0} following suggestions sent to {1} people", followingSuggestionsSuggestionsTotal, followingSuggestionsEmailsSent );

            if ( exceptionMsgs.Any() )
            {
                throw new Exception( "One or more exceptions occurred calculating suggestions..." + Environment.NewLine + exceptionMsgs.AsDelimited( Environment.NewLine ) );
            }
        }