/// <summary> /// Gets the entity query /// For example: If the EntityTypeId is GroupMember, this will return a GroupMember query of group members that the person is following /// </summary> /// <param name="entityTypeId">The entity type identifier.</param> /// <param name="personId">The person identifier.</param> /// <returns></returns> public IQueryable <IEntity> GetFollowedItems(int entityTypeId, int personId) { EntityTypeCache itemEntityType = EntityTypeCache.Get(entityTypeId); var rockContext = this.Context as RockContext; var followedItemsQry = this.Queryable().Where(a => a.PersonAlias.PersonId == personId && a.EntityTypeId == entityTypeId); if (itemEntityType.AssemblyName != null) { Type entityType = itemEntityType.GetEntityType(); if (entityType != null) { 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>; entityQry = followedItemsQry.Join( entityQry, f => f.EntityId, e => e.Id, (f, e) => e); return(entityQry); } } return(null); }
/// <summary> /// Gets the entity query for the specified EntityTypeId /// </summary> /// <param name="entityTypeId">The entity type identifier.</param> /// <returns></returns> public IQueryable <IEntity> GetEntityQuery(int entityTypeId) { EntityTypeCache entityTypeCache = EntityTypeCache.Get(entityTypeId); var rockContext = this.Context as RockContext; if (entityTypeCache.AssemblyName != null) { Type entityType = entityTypeCache.GetEntityType(); if (entityType != null) { 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>; return(entityQry); } } return(null); }
/// <summary> /// Returns a queryable collection of <see cref="Rock.Data.IEntity" /> target entities (related to the given source entity) for the given entity type and (optionally) also have a matching purpose key. /// </summary> /// <param name="sourceEntityId">A <see cref="System.Int32" /> representing the source entity identifier.</param> /// <param name="sourceEntityTypeId">A <see cref="System.Int32" /> representing the source entity type identifier.</param> /// <param name="relatedEntityTypeId">A <see cref="System.Int32" /> representing the related target entity type identifier.</param> /// <param name="purposeKey">The purpose key.</param> /// <returns> /// Returns a queryable collection of <see cref="Rock.Data.IEntity" /> entities. /// </returns> public IQueryable <IEntity> GetRelatedToSource(int sourceEntityId, int sourceEntityTypeId, int relatedEntityTypeId, string purposeKey) { EntityTypeCache relatedEntityTypeCache = EntityTypeCache.Get(relatedEntityTypeId); if (relatedEntityTypeCache.AssemblyName != null) { IQueryable <RelatedEntity> query = GetRelatedEntityRecordsToSource(sourceEntityId, sourceEntityTypeId, relatedEntityTypeId, purposeKey); var rockContext = this.Context as RockContext; Type relatedEntityType = relatedEntityTypeCache.GetEntityType(); if (relatedEntityType != null) { Rock.Data.IService serviceInstance = Reflection.GetServiceForEntityType(relatedEntityType, rockContext); MethodInfo qryMethod = serviceInstance.GetType().GetMethod("Queryable", new Type[] { }); var entityQry = qryMethod.Invoke(serviceInstance, new object[] { }) as IQueryable <IEntity>; entityQry = query.Join( entityQry, f => f.TargetEntityId, e => e.Id, (f, e) => e); return(entityQry); } } return(null); }
/// <summary> /// Gets the type of the i entity for entity. /// </summary> /// <param name="entityType">Type of the entity.</param> /// <param name="guid">The unique identifier.</param> /// <returns></returns> public static Rock.Data.IEntity GetIEntityForEntityType(Type entityType, Guid guid) { var dbContext = Reflection.GetDbContextForEntityType(entityType); Rock.Data.IService serviceInstance = Reflection.GetServiceForEntityType(entityType, dbContext); if (serviceInstance != null) { System.Reflection.MethodInfo getMethod = serviceInstance.GetType().GetMethod("Get", new Type[] { typeof(Guid) }); return(getMethod.Invoke(serviceInstance, new object[] { guid }) as Rock.Data.IEntity); } return(null); }
/// <summary> /// Gets the entity query /// For example: If the EntityTypeId is GroupMember, this will return a GroupMember query of group members that the person is following /// </summary> /// <param name="entityTypeId">The entity type identifier.</param> /// <param name="personId">The person identifier.</param> /// <param name="purposeKey">A purpose that defines how this following will be used.</param> /// <returns></returns> public IQueryable <IEntity> GetFollowedItems(int entityTypeId, int personId, string purposeKey) { EntityTypeCache itemEntityType = EntityTypeCache.Get(entityTypeId); var rockContext = this.Context as RockContext; purposeKey = purposeKey ?? string.Empty; var followedItemsQry = this.Queryable() .Where(a => a.PersonAlias.PersonId == personId && a.EntityTypeId == entityTypeId) .Where(f => (f.PurposeKey == null && purposeKey == "") || f.PurposeKey == purposeKey); if (itemEntityType.AssemblyName != null) { Type entityType = itemEntityType.GetEntityType(); if (entityType != null) { Rock.Data.IService serviceInstance = Reflection.GetServiceForEntityType(entityType, rockContext); MethodInfo qryMethod = serviceInstance.GetType().GetMethod("Queryable", new Type[] { }); var entityQry = qryMethod.Invoke(serviceInstance, new object[] { }) as IQueryable <IEntity>; entityQry = followedItemsQry.Join( entityQry, f => f.EntityId, e => e.Id, (f, e) => e); int personEntityTypeId = EntityTypeCache.Get <Rock.Model.Person>().Id; int personAliasEntityTypeId = EntityTypeCache.Get <Rock.Model.PersonAlias>().Id; // if requesting persons that the person is following, it is probably recorded as the PersonAlias records that the person is following, so get Person from that if (entityTypeId == personEntityTypeId) { var followedItemsPersonAliasQry = this.Queryable().Where(a => a.PersonAlias.PersonId == personId && a.EntityTypeId == personAliasEntityTypeId); var entityPersonAliasQry = new PersonAliasService(rockContext).Queryable(); var entityFollowedPersons = followedItemsPersonAliasQry.Join( entityPersonAliasQry, f => f.EntityId, e => e.Id, (f, e) => e).Select(a => a.Person); entityQry = entityQry.Union(entityFollowedPersons as IQueryable <IEntity>); } return(entityQry); } } return(null); }
/// <summary> /// Gets the appropriate Rock.Data.IService based on the entity type /// </summary> /// <param name="entityType">Type of the Entity.</param> /// <param name="dbContext">The database context.</param> /// <returns></returns> public static Rock.Data.IService GetServiceForEntityType(Type entityType, System.Data.Entity.DbContext dbContext) { Type serviceType = typeof(Rock.Data.Service <>); if (entityType.Assembly != serviceType.Assembly) { var serviceTypeLookup = Reflection.SearchAssembly(entityType.Assembly, serviceType); if (serviceTypeLookup.Any()) { serviceType = serviceTypeLookup.First().Value; } } Type service = serviceType.MakeGenericType(new Type[] { entityType }); Rock.Data.IService serviceInstance = Activator.CreateInstance(service, dbContext) as Rock.Data.IService; return(serviceInstance); }
/// <summary> /// Gets the entity query, ordered by the EntitySetItem.Order /// For example: If the EntitySet.EntityType is Person, this will return a Person Query of the items in this set /// </summary> /// <param name="entitySetId">The entity set identifier.</param> /// <returns></returns> public IQueryable <IEntity> GetEntityQuery(int entitySetId) { var entitySet = this.Get(entitySetId); if (entitySet?.EntityTypeId == null) { // the EntitySet Items are not IEntity items return(null); } EntityTypeCache itemEntityType = EntityTypeCache.Get(entitySet.EntityTypeId.Value); var rockContext = this.Context as RockContext; var entitySetItemsService = new EntitySetItemService(rockContext); var entityItemQry = entitySetItemsService.Queryable().Where(a => a.EntitySetId == entitySetId).OrderBy(a => a.Order); bool isPersonEntitySet = itemEntityType.Guid == Rock.SystemGuid.EntityType.PERSON.AsGuid(); if (itemEntityType.AssemblyName != null) { Type entityType = itemEntityType.GetEntityType(); if (entityType != null) { 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 joinQry = entityItemQry.Join(entityQry, k => k.EntityId, i => i.Id, (setItem, item) => new { Item = item, ItemOrder = setItem.Order } ).OrderBy(a => a.ItemOrder).ThenBy(a => a.Item.Id); return(joinQry.Select(a => a.Item)); } } return(null); }
/// <summary> /// Returns a queryable collection of <see cref="Rock.Data.IEntity"/> source entities (related to the given target entity) for the given entity type and (optionally) also have a matching purpose key. /// </summary> /// <param name="targetEntityId">A <see cref="System.Int32" /> representing the target entity identifier.</param> /// <param name="targetEntityTypeId">A <see cref="System.Int32" /> representing the <see cref="RelatedEntity.TargetEntityTypeId"/></param> /// <param name="relatedEntityTypeId">A <see cref="System.Int32" /> representing the related <see cref="RelatedEntity.SourceEntityTypeId"/></param> /// <param name="purposeKey">The purpose key.</param> /// <returns> /// Returns a queryable collection of <see cref="Rock.Data.IEntity" /> entities. /// </returns> public IQueryable <IEntity> GetRelatedToTarget(int targetEntityId, int targetEntityTypeId, int relatedEntityTypeId, string purposeKey) { EntityTypeCache relatedEntityTypeCache = EntityTypeCache.Get(relatedEntityTypeId); if (relatedEntityTypeCache.AssemblyName != null) { var query = Queryable() .Where(a => a.TargetEntityTypeId == targetEntityTypeId && a.TargetEntityId == targetEntityId && a.SourceEntityTypeId == relatedEntityTypeId); if (purposeKey.IsNullOrWhiteSpace()) { query = query.Where(a => string.IsNullOrEmpty(a.PurposeKey)); } else { query = query.Where(a => a.PurposeKey == purposeKey); } var rockContext = this.Context as RockContext; Type relatedEntityType = relatedEntityTypeCache.GetEntityType(); if (relatedEntityType != null) { Rock.Data.IService serviceInstance = Reflection.GetServiceForEntityType(relatedEntityType, rockContext); MethodInfo qryMethod = serviceInstance.GetType().GetMethod("Queryable", new Type[] { }); var entityQry = qryMethod.Invoke(serviceInstance, new object[] { }) as IQueryable <IEntity>; entityQry = query.Join( entityQry, f => f.SourceEntityId, e => e.Id, (f, e) => e); return(entityQry); } } return(null); }
/// <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(); 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) { // 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.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); } } } 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); } } } } if (exceptionMsgs.Any()) { throw new Exception("One or more exceptions occurred calculating following events..." + Environment.NewLine + exceptionMsgs.AsDelimited(Environment.NewLine)); } } }
/// <summary> /// Renders the specified context. /// </summary> /// <param name="context">The context.</param> /// <param name="result">The result.</param> /// <exception cref="System.Exception">Your Lava command must contain at least one valid filter. If you configured a filter it's possible that the property or attribute you provided does not exist.</exception> public override void Render(Context context, TextWriter result) { // first ensure that entity commands are allowed in the context if (!this.IsAuthorized(context)) { result.Write(string.Format(RockLavaBlockBase.NotAuthorizedMessage, this.Name)); base.Render(context, result); return; } bool hasFilter = false; // get a service for the entity based off it's friendly name var entityTypes = EntityTypeCache.All(); var model = string.Empty; if (_entityName == "business") { model = "Rock.Model.Person"; } else { model = "Rock.Model." + _entityName; } // Check first to see if this is a core model var entityTypeCache = entityTypes.Where(e => String.Equals(e.Name, model, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); // If not, look for first plugin model that has same friendly name if (entityTypeCache == null) { entityTypeCache = entityTypes .Where(e => e.IsEntity && !e.Name.StartsWith("Rock.Model") && e.FriendlyName != null && e.FriendlyName.RemoveSpaces().ToLower() == _entityName) .OrderBy(e => e.Id) .FirstOrDefault(); } // If still null check to see if this was a duplicate class and full class name was used as entity name if (entityTypeCache == null) { model = _entityName.Replace('_', '.'); entityTypeCache = entityTypes.Where(e => String.Equals(e.Name, model, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); } if (entityTypeCache != null) { Type entityType = entityTypeCache.GetEntityType(); if (entityType != null) { // Get the database context Type contextType = null; Rock.Data.DbContext dbContext = null; var contexts = Rock.Reflection.SearchAssembly(entityType.Assembly, typeof(Rock.Data.DbContext)); if (contexts.Any()) { contextType = contexts.First().Value; dbContext = Activator.CreateInstance(contextType) as Rock.Data.DbContext; } if (dbContext == null) { dbContext = _rockContext; } // create an instance of the entity's service Type[] modelType = { entityType }; Type genericServiceType = typeof(Rock.Data.Service <>); Type modelServiceType = genericServiceType.MakeGenericType(modelType); Rock.Data.IService serviceInstance = Activator.CreateInstance(modelServiceType, new object[] { dbContext }) as IService; ParameterExpression paramExpression = Expression.Parameter(entityType, "x"); Expression queryExpression = null; // the base expression we'll use to build our query from // parse markup var parms = ParseMarkup(_markup, context); if (parms.Any(p => p.Key == "id")) { string propertyName = "Id"; List <string> selectionParms = new List <string>(); selectionParms.Add(PropertyComparisonConverstion("==").ToString()); selectionParms.Add(parms["id"].ToString()); selectionParms.Add(propertyName); var entityProperty = entityType.GetProperty(propertyName); queryExpression = ExpressionHelper.PropertyFilterExpression(selectionParms, paramExpression, propertyName, entityProperty.PropertyType); hasFilter = true; } else { // where clause expression if (parms.Any(p => p.Key == "where")) { queryExpression = ParseWhere(parms["where"], entityType, serviceInstance, paramExpression, entityType, entityTypeCache); if (queryExpression != null) { hasFilter = true; } } // dataview expression if (parms.Any(p => p.Key == "dataview")) { var dataViewId = parms["dataview"].AsIntegerOrNull(); if (dataViewId.HasValue) { var dataViewExpression = GetDataViewExpression(dataViewId.Value, serviceInstance, paramExpression, entityTypeCache); if (queryExpression == null) { queryExpression = dataViewExpression; hasFilter = true; } else { queryExpression = Expression.AndAlso(queryExpression, dataViewExpression); } } } // process dynamic filter expressions (from the query string) if (parms.Any(p => p.Key == "dynamicparameters")) { var dynamicFilters = parms["dynamicparameters"].Split(',') .Select(x => x.Trim()) .Where(x => !string.IsNullOrWhiteSpace(x)) .ToList(); foreach (var dynamicFilter in dynamicFilters) { var dynamicFilterValue = HttpContext.Current.Request[dynamicFilter]; var dynamicFilterExpression = GetDynamicFilterExpression(dynamicFilter, dynamicFilterValue, entityType, serviceInstance, paramExpression); if (dynamicFilterExpression != null) { if (queryExpression == null) { queryExpression = dynamicFilterExpression; hasFilter = true; } else { queryExpression = Expression.AndAlso(queryExpression, dynamicFilterExpression); } } } } } // make the query from the expression MethodInfo getMethod = serviceInstance.GetType().GetMethod("Get", new Type[] { typeof(ParameterExpression), typeof(Expression), typeof(Rock.Web.UI.Controls.SortProperty), typeof(int?) }); if (getMethod != null) { var queryResult = getMethod.Invoke(serviceInstance, new object[] { paramExpression, queryExpression, null, null }) as IQueryable <IEntity>; // process entity specific filters switch (_entityName) { case "person": { queryResult = PersonFilters((IQueryable <Person>)queryResult, parms); break; } case "business": { queryResult = BusinessFilters((IQueryable <Person>)queryResult, parms); break; } } // if there was a dynamic expression add it now if (parms.Any(p => p.Key == "expression")) { queryResult = queryResult.Where(parms["expression"]); hasFilter = true; } // get a listing of ids if (parms.Any(p => p.Key == "ids")) { var value = parms["ids"].ToString().Split(',').Select(int.Parse).ToList(); queryResult = queryResult.Where(x => value.Contains(x.Id)); hasFilter = true; } var queryResultExpression = queryResult.Expression; // add sort expressions if (parms.Any(p => p.Key == "sort")) { string orderByMethod = "OrderBy"; foreach (var column in parms["sort"].Split(',').Select(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).ToList()) { string propertyName; var direction = SortDirection.Ascending; if (column.EndsWith(" desc", StringComparison.OrdinalIgnoreCase)) { direction = SortDirection.Descending; propertyName = column.Left(column.Length - 5); } else { propertyName = column; } string methodName = direction == SortDirection.Descending ? orderByMethod + "Descending" : orderByMethod; if (entityType.GetProperty(propertyName) != null) { // sorting a entity property var memberExpression = Expression.Property(paramExpression, propertyName); LambdaExpression sortSelector = Expression.Lambda(memberExpression, paramExpression); queryResultExpression = Expression.Call(typeof(Queryable), methodName, new Type[] { queryResult.ElementType, sortSelector.ReturnType }, queryResultExpression, sortSelector); } else { // sorting on an attribute // get attribute id int?attributeId = null; foreach (var id in AttributeCache.GetByEntity(entityTypeCache.Id).SelectMany(a => a.AttributeIds)) { var attribute = AttributeCache.Get(id); if (attribute.Key == propertyName) { attributeId = id; } } if (attributeId.HasValue) { // get AttributeValue queryable and parameter var attributeValues = _rockContext.Set <AttributeValue>(); ParameterExpression attributeValueParameter = Expression.Parameter(typeof(AttributeValue), "v"); MemberExpression idExpression = Expression.Property(paramExpression, "Id"); var attributeExpression = Attribute.Helper.GetAttributeValueExpression(attributeValues, attributeValueParameter, idExpression, attributeId.Value); LambdaExpression sortSelector = Expression.Lambda(attributeExpression, paramExpression); queryResultExpression = Expression.Call(typeof(Queryable), methodName, new Type[] { queryResult.ElementType, sortSelector.ReturnType }, queryResultExpression, sortSelector); } } orderByMethod = "ThenBy"; } } // reassemble the queryable with the sort expressions queryResult = queryResult.Provider.CreateQuery(queryResultExpression) as IQueryable <IEntity>; if (parms.GetValueOrNull("count").AsBoolean()) { int countResult = queryResult.Count(); context.Scopes.Last()["count"] = countResult; } else { // run security check on each result var items = queryResult.ToList(); var itemsSecured = new List <IEntity>(); Person person = GetCurrentPerson(context); foreach (IEntity item in items) { ISecured itemSecured = item as ISecured; if (itemSecured == null || itemSecured.IsAuthorized(Authorization.VIEW, person)) { itemsSecured.Add(item); } } queryResult = itemsSecured.AsQueryable(); // offset if (parms.Any(p => p.Key == "offset")) { queryResult = queryResult.Skip(parms["offset"].AsInteger()); } // limit, default to 1000 if (parms.Any(p => p.Key == "limit")) { queryResult = queryResult.Take(parms["limit"].AsInteger()); } else { queryResult = queryResult.Take(1000); } // check to ensure we had some form of filter (otherwise we'll return all results in the table) if (!hasFilter) { throw new Exception("Your Lava command must contain at least one valid filter. If you configured a filter it's possible that the property or attribute you provided does not exist."); } var resultList = queryResult.ToList(); // if there is only one item to return set an alternative non-array based variable if (resultList.Count == 1) { context.Scopes.Last()[_entityName] = resultList.FirstOrDefault(); } context.Scopes.Last()[parms["iterator"]] = resultList; } } } } else { result.Write(string.Format("Could not find a model for {0}.", _entityName)); base.Render(context, result); } base.Render(context, result); }