/// <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; } }
/// <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; } }
/// <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 ) ); } } }
/// <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 ) ); } }