/// <summary> /// Processes the follower PersonId for the SuggestionType /// </summary> /// <param name="followerPersonId">The follower person identifier.</param> /// <param name="suggestionTypeComponent">The suggestion type component.</param> /// <param name="primaryAliasIds">The primary alias ids.</param> /// <param name="entityIds">The entity ids.</param> /// <param name="entityTypeId">The entity type identifier.</param> /// <param name="timestamp">The timestamp.</param> private void ProcessFollowerPersonId(int followerPersonId, SuggestionTypeComponent suggestionTypeComponent, Dictionary <int, int> primaryAliasIds, List <int> entityIds, int entityTypeId, DateTime timestamp) { 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 == suggestionTypeComponent.FollowingSuggestionType.Id && s.PersonAlias.PersonId == followerPersonId) .ToList(); // Look through the returned suggestions foreach (var followedEntityId in suggestionTypeComponent.PersonEntitySuggestions.Where(s => s.PersonId == followerPersonId).Select(s => s.EntityId)) { // Make sure person isn't already following this entity if (!suggestionTypeComponent.ExistingFollowings.ContainsKey(followerPersonId) || !suggestionTypeComponent.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 it if needed if (suggestion == null) { bool addSuggestion; ProcessFollowingSuggestionAndPersonAliasEntity(followerPersonId, suggestionTypeComponent, entityIds, entityTypeId, suggestionContext, followedEntityId, out addSuggestion); if (addSuggestion) { // This is a new entity ID so insert it suggestion = new FollowingSuggested { EntityTypeId = entityTypeId, EntityId = followedEntityId, PersonAliasId = primaryAliasIds[followerPersonId], SuggestionTypeId = suggestionTypeComponent.FollowingSuggestionType.Id, Status = FollowingSuggestedStatus.PendingNotification, StatusChangedDateTime = timestamp }; followingSuggestedService.Add(suggestion); } } else { ProcessFollowingSuggestionAndPersonAliasEntity(followerPersonId, suggestionTypeComponent, entityIds, entityTypeId, suggestionContext, followedEntityId); // If found, and it has not been ignored, and it's time to promote again, update the promote date if (suggestion.Status != FollowingSuggestedStatus.Ignored && suggestionTypeComponent.FollowingSuggestionType.ReminderDays.HasValue && ( !suggestion.LastPromotedDateTime.HasValue || suggestion.LastPromotedDateTime.Value.AddDays(suggestionTypeComponent.FollowingSuggestionType.ReminderDays.Value) <= timestamp )) { if (suggestion.Status != FollowingSuggestedStatus.PendingNotification) { suggestion.StatusChangedDateTime = timestamp; suggestion.Status = FollowingSuggestedStatus.PendingNotification; } } } } } } // Save the suggestions for this type suggestionContext.SaveChanges(); } }
/// <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 ) { 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 ) ); } }