/// <summary> /// Notifies the admins. /// </summary> /// <param name="subject">The subject.</param> /// <param name="message">The message.</param> /// <param name="appRoot">The application root.</param> /// <param name="themeRoot">The theme root.</param> /// <exception cref="System.Exception"> /// Error sending System Email: Could not read Email Medium Entity Type /// </exception> public static void NotifyAdmins(string subject, string message, string appRoot = "", string themeRoot = "") { try { List <string> recipients = null; Guid adminGroup = Rock.SystemGuid.Group.GROUP_ADMINISTRATORS.AsGuid(); using (var rockContext = new RockContext()) { recipients = new GroupMemberService(rockContext).Queryable() .Where(m => m.Group.Guid.Equals(adminGroup) && m.GroupMemberStatus == GroupMemberStatus.Active && m.Person.Email != null && m.Person.Email != "") .Select(m => m.Person.Email) .ToList(); } if (recipients != null && recipients.Any()) { var mediumEntity = EntityTypeCache.Read(Rock.SystemGuid.EntityType.COMMUNICATION_MEDIUM_EMAIL.AsGuid()); if (mediumEntity != null) { var medium = MediumContainer.GetComponent(mediumEntity.Name); if (medium != null && medium.IsActive) { var transport = medium.Transport; if (transport != null && transport.IsActive) { try { transport.Send(recipients, null, subject, message, appRoot, themeRoot); } catch (Exception ex1) { throw new Exception(string.Format("Error sending System Email ({0}).", subject), ex1); } } else { throw new Exception(string.Format("Error sending System Email: The '{0}' medium does not have a valid transport, or the transport is not active.", mediumEntity.FriendlyName)); } } else { throw new Exception(string.Format("Error sending System Email: The '{0}' medium does not exist, or is not active (type: {1}).", mediumEntity.FriendlyName, mediumEntity.Name)); } } else { throw new Exception("Error sending System Email: Could not read Email Medium Entity Type"); } } } catch (Exception ex) { ExceptionLogService.LogException(ex, HttpContext.Current); } }
/// <summary> /// Handles the SaveClick event of the modalAddPerson control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> void modalAddPerson_SaveClick(object sender, EventArgs e) { if (hfActiveTab.Value == "Existing") { int?personId = ppExistingPerson.PersonId; if (personId.HasValue) { using (new UnitOfWorkScope()) { var person = new PersonService().Get(personId.Value); if (person != null) { var familyMember = new FamilyMember(); familyMember.SetValuesFromPerson(person); var familyRoleIds = familyRoles.Select(r => r.Id).ToList(); var existingFamilyRoles = new GroupMemberService().Queryable("GroupRole") .Where(m => m.PersonId == person.Id && familyRoleIds.Contains(m.GroupRoleId)) .OrderBy(m => m.GroupRole.SortOrder) .ToList(); var existingRole = existingFamilyRoles.Select(m => m.GroupRole).FirstOrDefault(); if (existingRole != null) { familyMember.RoleGuid = existingRole.Guid; familyMember.RoleName = existingRole.Name; } familyMember.ExistingFamilyMember = existingFamilyRoles.Any(r => r.GroupId == _family.Id); familyMember.RemoveFromOtherFamilies = cbRemoveOtherFamilies.Checked; FamilyMembers.Add(familyMember); } } } } else { var familyMember = new FamilyMember(); familyMember.FirstName = tbNewPersonFirstName.Text; familyMember.LastName = tbNewPersonLastName.Text; familyMember.Gender = ddlNewPersonGender.SelectedValueAsEnum <Gender>(); familyMember.BirthDate = dpNewPersonBirthDate.SelectedDate; var role = familyRoles.Where(r => r.Id == (rblNewPersonRole.SelectedValueAsInt() ?? 0)).FirstOrDefault(); if (role != null) { familyMember.RoleGuid = role.Guid; familyMember.RoleName = role.Name; } FamilyMembers.Add(familyMember); } tbNewPersonFirstName.Required = false; tbNewPersonLastName.Required = false; confirmExit.Enabled = true; BindMembers(); }
public void Execute(IJobExecutionContext context) { var rockContext = new RockContext(); var familyGroupType = GroupTypeCache.Read(Rock.SystemGuid.GroupType.GROUPTYPE_FAMILY); var familyMembers = new GroupMemberService(rockContext) .Queryable("Group,Person") .Where( g => g.Group.GroupType.Id == familyGroupType.Id && g.Person.GivingGroupId == 0 || g.Person.GivingGroupId == null); while (familyMembers.Any()) { //Chunk to prevent foreach saving thread errors foreach (var chunk in familyMembers.OrderBy(f => f.Id).QueryChunksOfSize(100)) { foreach (var familyMember in chunk) { familyMember.Person.GivingGroupId = familyMember.GroupId; } rockContext.SaveChanges(); } rockContext = new RockContext(); familyMembers = new GroupMemberService(rockContext) .Queryable("Group,Person") .Where( g => g.Group.GroupType.Id == familyGroupType.Id && g.Person.GivingGroupId == 0 || g.Person.GivingGroupId == null); } }
public void GetGroupMembersExcludeArchived() { List <GroupMember> deceasedList = new List <GroupMember>(); using (var rockContext = new RockContext()) { deceasedList = new GroupMemberService(rockContext).Queryable(true).ToList(); } var areAnyArchived = deceasedList.Any(x => x.IsArchived); Assert.That.IsFalse(areAnyArchived); }
/// <summary> /// Executes this instance. /// </summary> /// <param name="message"></param> public override void Execute(Message message) { using (var rockContext = new RockContext()) { var communication = new CommunicationService(rockContext).Get(message.CommunicationId); if (communication != null && communication.Status == CommunicationStatus.PendingApproval) { // get notification group var groupGuid = SystemGuid.Group.GROUP_COMMUNICATION_APPROVERS.AsGuid(); var approvers = new GroupMemberService(rockContext).Queryable() .Where(m => m.Group.Guid == groupGuid && m.GroupMemberStatus == GroupMemberStatus.Active) .ToList(); if (approvers.Any()) { var communicationSettingApprovalGuid = Rock.Web.SystemSettings.GetValue(SystemSetting.COMMUNICATION_SETTING_APPROVAL_TEMPLATE).AsGuidOrNull(); if (communicationSettingApprovalGuid.HasValue) { var approvalPageUrl = message.ApprovalPageUrl; // create approval link if one was not provided if (string.IsNullOrEmpty(approvalPageUrl)) { var internalApplicationRoot = GlobalAttributesCache.Value("InternalApplicationRoot").EnsureTrailingForwardslash(); approvalPageUrl = $"{internalApplicationRoot}Communication/{communication.Id}"; } foreach (var approver in approvers) { var recipients = new List <RockEmailMessageRecipient>(); var emailMessage = new RockEmailMessage(communicationSettingApprovalGuid.Value); // Build Lava merge fields. var mergeFields = Rock.Lava.LavaHelper.GetCommonMergeFields(null); mergeFields.Add("Approver", approver.Person); mergeFields.Add("Communication", communication); mergeFields.Add("RecipientsCount", communication.GetRecipientsQry(rockContext).Count()); mergeFields.Add("ApprovalPageUrl", approvalPageUrl); recipients.Add(new RockEmailMessageRecipient(approver.Person, mergeFields)); emailMessage.SetRecipients(recipients); emailMessage.Send(); } } } } } }
/// <summary> /// Creates a Linq Expression that can be applied to an IQueryable to filter the result set. /// </summary> /// <param name="entityType">The type of entity in the result set.</param> /// <param name="serviceInstance">A service instance that can be queried to obtain the result set.</param> /// <param name="parameterExpression">The input parameter that will be injected into the filter expression.</param> /// <param name="selection">A formatted string representing the filter settings.</param> /// <returns> /// A Linq Expression that can be used to filter an IQueryable. /// </returns> /// <exception cref="System.Exception">Filter issue(s): + errorMessages.AsDelimited( ; )</exception> public override Expression GetExpression(Type entityType, IService serviceInstance, ParameterExpression parameterExpression, string selection) { var settings = new FilterSettings(selection); var context = (RockContext)serviceInstance.Context; // Get the Location Data View that defines the set of candidates from which proximate Locations can be selected. var dataView = DataComponentSettingsHelper.GetDataViewForFilterComponent(settings.DataViewGuid, context); // Evaluate the Data View that defines the candidate Locations. var locationService = new LocationService(context); var locationQuery = locationService.Queryable(); if (dataView != null) { locationQuery = DataComponentSettingsHelper.FilterByDataView(locationQuery, dataView, locationService); } // Get all the Family Groups that have a Location matching one of the candidate Locations. int familyGroupTypeId = GroupTypeCache.Get(SystemGuid.GroupType.GROUPTYPE_FAMILY.AsGuid()).Id; var groupLocationsQuery = new GroupLocationService(context).Queryable() .Where(gl => gl.Group.GroupTypeId == familyGroupTypeId && locationQuery.Any(l => l.Id == gl.LocationId)); // If a Location Type is specified, apply the filter condition. if (settings.LocationTypeGuid.HasValue) { int groupLocationTypeId = DefinedValueCache.Get(settings.LocationTypeGuid.Value).Id; groupLocationsQuery = groupLocationsQuery.Where(x => x.GroupLocationTypeValue.Id == groupLocationTypeId); } // Get all of the Group Members of the qualifying Families. var groupMemberServiceQry = new GroupMemberService(context).Queryable() .Where(gm => groupLocationsQuery.Any(gl => gl.GroupId == gm.GroupId)); // Get all of the People corresponding to the qualifying Group Members. var qry = new PersonService(context).Queryable() .Where(p => groupMemberServiceQry.Any(gm => gm.PersonId == p.Id)); // Retrieve the Filter Expression. var extractedFilterExpression = FilterExpressionExtractor.Extract <Model.Person>(qry, parameterExpression, "p"); return(extractedFilterExpression); }
public void Execute(IJobExecutionContext context) { var dataMap = context.JobDetail.JobDataMap; RockContext rockContext = null; IQueryable <GroupMember> familyMembers = null; var dateRange = SlidingDateRangePicker.CalculateDateRangeFromDelimitedValues(dataMap.GetString("DateRange") ?? "-1||"); int peopleUpdated = 0; var familyGroupType = GroupTypeCache.Get(Rock.SystemGuid.GroupType.GROUPTYPE_FAMILY); do { if (familyMembers != null) { foreach (var familyMember in familyMembers.OrderBy(f => f.Id).Take(100).ToList()) { familyMember.Person.GivingGroupId = familyMember.GroupId; peopleUpdated += 1; } rockContext.SaveChanges(); } rockContext = new RockContext(); familyMembers = new GroupMemberService(rockContext) .Queryable("Group,Person") .Where(g => g.Group.GroupType.Id == familyGroupType.Id && (g.Person.GivingGroupId == 0 || g.Person.GivingGroupId == null)); if (dateRange.Start.HasValue) { familyMembers = familyMembers.Where(g => g.Person.CreatedDateTime >= dateRange.Start); } if (dateRange.End.HasValue) { familyMembers = familyMembers.Where(g => g.Person.CreatedDateTime < dateRange.End); } } while (familyMembers.Any()); context.Result = string.Format("Combined giving on {0} {1}", peopleUpdated, peopleUpdated == 1 ? "person" : "people"); }
private void LoadGroups(int?currentGroupId) { ddlGroup.Items.Clear(); int? personId = ppPerson.PersonId; Guid?groupTypeGuid = GetAttributeValue("SelectGroupType").AsGuidOrNull(); if (personId.HasValue && groupTypeGuid.HasValue) { using (var rockContext = new RockContext()) { var groups = new GroupMemberService(rockContext) .Queryable().AsNoTracking() .Where(m => m.Group.GroupType.Guid == groupTypeGuid.Value && m.PersonId == personId.Value && m.GroupMemberStatus == GroupMemberStatus.Active && m.Group.IsActive && !m.Group.IsArchived) .Select(m => new { m.GroupId, Name = m.Group.Name }) .ToList() .Distinct() .OrderBy(g => g.Name) .ToList(); if (groups.Any()) { ddlGroup.DataSource = groups; ddlGroup.DataBind(); ddlGroup.Items.Insert(0, new ListItem()); ddlGroup.SetValue(currentGroupId); } } } }
/// <summary> /// Shows the view. /// </summary> private void ShowForm() { lReceipt.Visible = false; pnlAddPledge.Visible = true; if (CurrentPerson != null) { lName.Text = CurrentPerson.FullName; lName.Visible = true; tbFirstName.Visible = false; tbLastName.Visible = false; tbEmail.Visible = false; using (var rockContext = new RockContext()) { Guid?groupTypeGuid = GetAttributeValue("SelectGroupType").AsGuidOrNull(); if (groupTypeGuid.HasValue) { var groups = new GroupMemberService(rockContext) .Queryable().AsNoTracking() .Where(m => m.Group.GroupType.Guid == groupTypeGuid.Value && m.PersonId == CurrentPerson.Id && m.GroupMemberStatus == GroupMemberStatus.Active && m.Group.IsActive && !m.Group.IsArchived) .Select(m => new { m.GroupId, Name = m.Group.Name, GroupTypeName = m.Group.GroupType.Name }) .ToList() .Distinct() .OrderBy(g => g.Name) .ToList(); if (groups.Any()) { ddlGroup.Label = "For " + groups.First().GroupTypeName; ddlGroup.DataSource = groups; ddlGroup.DataBind(); ddlGroup.Visible = true; } else { ddlGroup.Visible = false; } } else { ddlGroup.Visible = false; } } } else { lName.Visible = false; ddlGroup.Visible = false; tbFirstName.Visible = true; tbLastName.Visible = true; tbEmail.Visible = true; tbFirstName.Text = string.Empty; tbLastName.Text = string.Empty; tbEmail.Text = string.Empty; } // Warn if Financial Account is not specified (must be set up by administrator) var financialAccount = new FinancialAccountService(new RockContext()).Get(GetAttributeValue("Account").AsGuid()); if (financialAccount == null) { nbWarningMessage.Text = "Warning: No Account is specified for this pledge. Please contact the administrator."; nbWarningMessage.Visible = true; } else { nbWarningMessage.Visible = false; } drpDateRange.DelimitedValues = GetAttributeValue("PledgeDateRange"); // only show the date range picker if the block setting for date range isn't fully specified drpDateRange.Visible = drpDateRange.LowerValue == null || drpDateRange.UpperValue == null; ddlFrequency.Items.Clear(); var frequencies = DefinedTypeCache.Get(Rock.SystemGuid.DefinedType.FINANCIAL_FREQUENCY.AsGuid()) .DefinedValues.Where(dv => dv.IsActive).OrderBy(a => a.Order).ThenBy(a => a.Value); foreach (var frequency in frequencies) { ddlFrequency.Items.Add(new ListItem(frequency.Value, frequency.Id.ToString())); } ddlFrequency.Visible = GetAttributeValue("ShowPledgeFrequency").AsBooleanOrNull() ?? false; ddlFrequency.SelectedValue = null; // if Frequency is Visible, require it if RequirePledgeFrequency ddlFrequency.Required = ddlFrequency.Visible && (GetAttributeValue("RequirePledgeFrequency").AsBooleanOrNull() ?? false); string saveButtonText = GetAttributeValue("SaveButtonText"); if (!string.IsNullOrWhiteSpace(saveButtonText)) { btnSave.Text = saveButtonText; } else { btnSave.Text = "Save"; } lNote.Text = GetAttributeValue("NoteMessage"); }
/// <summary> /// Gets the expression. /// </summary> /// <param name="context">The context.</param> /// <param name="entityIdProperty">The entity identifier property.</param> /// <param name="selection">The selection.</param> /// <returns></returns> public override Expression GetExpression(RockContext context, MemberExpression entityIdProperty, string selection) { var settings = new GroupParticipationSelectSettings(selection); // // Define Candidate Groups. // // Get the Group Data View that defines the set of candidates from which matching Groups can be selected. var dataView = DataComponentSettingsHelper.GetDataViewForFilterComponent(settings.DataViewGuid, context); // Evaluate the Data View that defines the candidate Groups. var groupService = new GroupService(context); var groupQuery = groupService.Queryable(); if (dataView != null) { groupQuery = DataComponentSettingsHelper.FilterByDataView(groupQuery, dataView, groupService); } else { // Apply a default Group filter to only show Groups that would be visible in a Group List. groupQuery = groupQuery.Where(x => x.GroupType.ShowInGroupList); } var groupKeys = groupQuery.Select(x => x.Id); // // Construct the Query to return the list of Group Members matching the filter conditions. // var groupMemberQuery = new GroupMemberService(context).Queryable(); // Filter By Group. groupMemberQuery = groupMemberQuery.Where(x => groupKeys.Contains(x.GroupId)); // Filter By Group Role Type. switch (settings.RoleType) { case RoleTypeSpecifier.Member: groupMemberQuery = groupMemberQuery.Where(x => !x.GroupRole.IsLeader); break; case RoleTypeSpecifier.Leader: groupMemberQuery = groupMemberQuery.Where(x => x.GroupRole.IsLeader); break; } // Filter by Group Member Status. if (settings.MemberStatus.HasValue) { groupMemberQuery = groupMemberQuery.Where(x => x.GroupMemberStatus == settings.MemberStatus.Value); } // // Create a Select Expression to return the requested values. // // Set the Output Format of the field. Expression selectExpression; if (settings.ListFormat == ListFormatSpecifier.YesNo) { // Define a Query to return True/False text indicating if the Person participates in any of the filtered Groups. // Note that the text must be returned as an Enumerable to satisfy the expected output of this field. var personGroupsQuery = new PersonService(context).Queryable() .Select(p => new List <string> { groupMemberQuery.Any(s => s.PersonId == p.Id) ? "Yes" : "No" }); selectExpression = SelectExpressionExtractor.Extract(personGroupsQuery, entityIdProperty, "p"); } else { // Define a Query to return the collection of filtered Groups for each Person. Expression <Func <Rock.Model.GroupMember, string> > outputExpression; if (settings.ListFormat == ListFormatSpecifier.GroupOnly) { outputExpression = ((m => m.Group.Name)); } else { outputExpression = ((m => m.Group.Name + " [" + m.GroupRole.Name + "]")); } // Define a Query to return the collection of filtered Groups for each Person. var personGroupsQuery = new PersonService(context).Queryable() .Select(p => groupMemberQuery.Where(s => s.PersonId == p.Id) .OrderBy(x => x.Group.Name) .ThenBy(x => x.GroupRole.Name) .Select(outputExpression).AsEnumerable()); selectExpression = SelectExpressionExtractor.Extract(personGroupsQuery, entityIdProperty, "p"); } return(selectExpression); }
/// <summary> /// Updates the note watch notification digest. /// </summary> /// <param name="personIdNotificationDigestList">The person identifier notification digest list.</param> /// <param name="rockContext">The rock context.</param> /// <param name="noteId">The note identifier.</param> private void UpdateNoteWatchNotificationDigest(Dictionary <int, NoteWatchPersonToNotifyList> personIdNotificationDigestList, RockContext rockContext, int noteId) { var noteService = new Rock.Model.NoteService(rockContext); var note = noteService.Queryable().Include(a => a.EditedByPersonAlias).FirstOrDefault(a => a.Id == noteId); if (note == null || !note.EntityId.HasValue) { // shouldn't' happen return; } var noteType = NoteTypeCache.Get(note.NoteTypeId); // make sure the note's notetype has an EntityTypeId (is should, but just in case it doesn't) int?noteEntityTypeId = noteType?.EntityTypeId; if (!noteEntityTypeId.HasValue) { return; } var noteWatchService = new Rock.Model.NoteWatchService(rockContext); // narrow it down to NoteWatches for the same EntityType as the Note var noteWatchesQuery = noteWatchService.Queryable() .Where(a => (a.EntityTypeId.HasValue && a.EntityTypeId.Value == noteEntityTypeId.Value) || (a.NoteTypeId.HasValue && a.NoteType.EntityTypeId == noteEntityTypeId)); // narrow it down to either note watches on.. // 1) specific Entity // 2) specific Note // 3) any note of the NoteType // 4) any note on the EntityType // specific Entity noteWatchesQuery = noteWatchesQuery.Where(a => (a.EntityId == null) || (note.EntityId.HasValue && a.EntityId.Value == note.EntityId.Value)); // or specifically for this Note's ParentNote (a reply to the Note) noteWatchesQuery = noteWatchesQuery.Where(a => (a.NoteId == null) || (note.ParentNoteId.HasValue && a.NoteId.Value == note.ParentNoteId)); // or specifically for this note's note type noteWatchesQuery = noteWatchesQuery.Where(a => (a.NoteTypeId == null) || (a.NoteTypeId.Value == note.NoteTypeId)); // if there are any NoteWatches that relate to this note, process them if (noteWatchesQuery.Any()) { var noteWatchesForNote = noteWatchesQuery.Include(a => a.WatcherPersonAlias.Person).AsNoTracking().ToList(); List <NoteWatchPersonToNotify> noteWatchPersonToNotifyListAll = new List <NoteWatchPersonToNotify>(); // loop thru Watches to get a list of people to possibly notify/override foreach (var noteWatch in noteWatchesForNote) { // if a specific person is the watcher, add them var watcherPerson = noteWatch.WatcherPersonAlias?.Person; if (watcherPerson != null) { // Since this is iterated do not add the person to the list if they are already there. var exists = noteWatchPersonToNotifyListAll.Where(p => p.Person.Email.Contains(watcherPerson.Email)).Any(); if (!exists) { noteWatchPersonToNotifyListAll.Add(new NoteWatchPersonToNotify(watcherPerson, note, noteWatch)); } } if (noteWatch.WatcherGroupId.HasValue) { var watcherPersonsFromGroup = new GroupMemberService(rockContext).Queryable() .Where(a => a.GroupMemberStatus == GroupMemberStatus.Active && a.GroupId == noteWatch.WatcherGroupId.Value) .Select(a => a.Person).ToList(); if (watcherPersonsFromGroup.Any()) { // Do not add people from the group that are already added. var distinctWatchers = watcherPersonsFromGroup.Where(wg => !noteWatchPersonToNotifyListAll.Where(w => w.Person.Email.Contains(wg.Email)).Any()); noteWatchPersonToNotifyListAll.AddRange(distinctWatchers.Select(a => new NoteWatchPersonToNotify(a, note, noteWatch))); } } } var noteWatchPersonToNotifyList = noteWatchPersonToNotifyListAll.Where(a => a.NoteWatch.IsWatching).ToList(); var noteWatchPersonToNotifyListWatchDisabled = noteWatchPersonToNotifyListAll.Where(a => !a.NoteWatch.IsWatching).ToList(); var noteWatchPersonToNotifyListNoOverride = noteWatchPersonToNotifyList.Where(a => a.NoteWatch.AllowOverride == false).ToList(); foreach (var noteWatchPersonToNotify in noteWatchPersonToNotifyList) { // check if somebody wanted to specifically NOT watch this (and there isn't another watch that prevents overrides) if (noteWatchPersonToNotifyListWatchDisabled.Any(a => a.Person.Id == noteWatchPersonToNotify.Person.Id)) { // Person Requested to NOT watch, now make sure that they aren't force to watch based on Override = False if (!noteWatchPersonToNotifyListNoOverride.Any(a => a.Person.Id == noteWatchPersonToNotify.Person.Id)) { // person requested to NOT watch, and there aren't any overrides to prevent that, so jump out to the next one continue; } } NoteWatchPersonToNotifyList personToNotifyList; if (personIdNotificationDigestList.ContainsKey(noteWatchPersonToNotify.Person.Id)) { // This just create a place holder for the Note referring to item in the digest by key of person personToNotifyList = personIdNotificationDigestList[noteWatchPersonToNotify.Person.Id] ?? new NoteWatchPersonToNotifyList(noteWatchPersonToNotify.Person); } else { // This just creates a new place holder for the Note in the digest personToNotifyList = new NoteWatchPersonToNotifyList(noteWatchPersonToNotify.Person); personIdNotificationDigestList.Add(noteWatchPersonToNotify.Person.Id, personToNotifyList); } // Only include the note if the watcher person is authorized to view the note if (noteWatchPersonToNotify.Note.IsAuthorized(Rock.Security.Authorization.VIEW, noteWatchPersonToNotify.Person)) { // This is where the get added to the item of the digest personToNotifyList.Add(noteWatchPersonToNotify); } } } }
/// <summary> /// Executes this instance. /// </summary> public void Execute() { using (var rockContext = new RockContext()) { var communication = new CommunicationService(rockContext).Get(CommunicationId); if (communication != null && communication.Status == CommunicationStatus.PendingApproval) { // get notification group var groupGuid = SystemGuid.Group.GROUP_COMMUNICATION_APPROVERS.AsGuid(); var approvers = new GroupMemberService(rockContext).Queryable() .Where(m => m.Group.Guid == groupGuid && m.GroupMemberStatus == GroupMemberStatus.Active) .ToList(); if (approvers.Any()) { string fromName = Rock.Web.Cache.GlobalAttributesCache.Value("OrganizationName"); string fromEmail = Rock.Web.Cache.GlobalAttributesCache.Value("OrganizationEmail"); string subject = "Pending Communication Requires Approval"; var appRoot = Rock.Web.Cache.GlobalAttributesCache.Read(rockContext).GetValue("PublicApplicationRoot"); string communicationDetails = string.Empty; string typeName = communication.CommunicationType.ConvertToString(); // get custom details by type switch (communication.CommunicationType) { case CommunicationType.Email: communicationDetails = $@" <strong>From Name:</strong> {communication.FromName}<br/> <strong>From Address:</strong> {communication.FromEmail}<br/> <strong>Subject:</strong> {communication.Subject}<br/>"; break; case CommunicationType.SMS: if (communication.SMSFromDefinedValue != null) { communicationDetails = $"<strong>SMS Number:</strong> {communication.SMSFromDefinedValue.Description} ({communication.SMSFromDefinedValue.Value})<br/>"; } break; case CommunicationType.PushNotification: communicationDetails = $"<strong>Title:</strong> {communication.PushTitle}<br/>"; break; } // create approval link if one was not provided if (string.IsNullOrEmpty(ApprovalPageUrl)) { var internalApplicationRoot = Rock.Web.Cache.GlobalAttributesCache.Read(rockContext).GetValue("InternalApplicationRoot").EnsureTrailingForwardslash(); ApprovalPageUrl = $"{internalApplicationRoot}Communication/{communication.Id}"; } foreach (var approver in approvers) { string message = string.Format(@" {{{{ 'Global' | Attribute:'EmailHeader' }}}} <p>{0}:</p> <p>A new communication requires approval. Information about this communication can be found below.</p> <p> <strong>From:</strong> {1}<br /> <strong>Type:</strong> {2}<br /> {3} <strong>Recipient Count:</strong> {4}<br /> </p> <p> <a href='{5}'>View Communication</a> </p> {{{{ 'Global' | Attribute:'EmailFooter' }}}}", approver.Person.NickName, communication.SenderPersonAlias.Person.FullName, typeName, communicationDetails, communication.GetRecipientsQry(rockContext).Count(), ApprovalPageUrl); var emailMessage = new RockEmailMessage(); emailMessage.AddRecipient(approver.Person.Email); emailMessage.FromEmail = fromEmail; emailMessage.FromName = fromName; emailMessage.Subject = subject; emailMessage.Message = message; emailMessage.AppRoot = appRoot; emailMessage.Send(); } } } } }
/// <summary> /// Binds the grid. /// </summary> private void BindGrid() { // Find all the Group Types var groupTypeIds = GetAvailableGroupTypes(); if (GetAttributeValue("DisplayFilter").AsBooleanOrNull() ?? false) { int?groupTypeFilter = gfSettings.GetUserPreference("Group Type").AsIntegerOrNull(); if (groupTypeFilter.HasValue) { groupTypeIds = groupTypeIds.Where(g => g == groupTypeFilter.Value).ToList(); } } // filter to a specific group type if provided in the query string if (!string.IsNullOrWhiteSpace(RockPage.PageParameter("GroupTypeId"))) { int?groupTypeId = RockPage.PageParameter("GroupTypeId").AsIntegerOrNull(); if (groupTypeId.HasValue) { groupTypeIds.Clear(); groupTypeIds.Add(groupTypeId.Value); } } var rockContext = new RockContext(); var groupService = new GroupService(rockContext); SortProperty sortProperty = gGroups.SortProperty; if (sortProperty == null) { sortProperty = new SortProperty(new GridViewSortEventArgs("Name", SortDirection.Ascending)); } bool onlySecurityGroups = GetAttributeValue("LimittoSecurityRoleGroups").AsBoolean(); var qryGroups = groupService.Queryable() .Where(g => groupTypeIds.Contains(g.GroupTypeId) && (!onlySecurityGroups || g.IsSecurityRole)); string limitToActiveStatus = GetAttributeValue("LimittoActiveStatus"); bool showActive = true; bool showInactive = true; if (limitToActiveStatus == "all" && gfSettings.Visible) { // Filter by active/inactive unless the block settings restrict it if (ddlActiveFilter.SelectedIndex > -1) { switch (ddlActiveFilter.SelectedValue) { case "active": showInactive = false; break; case "inactive": showActive = false; break; } } } else if (limitToActiveStatus != "all") { // filter by the block setting for Active Status if (limitToActiveStatus == "active") { showInactive = false; } } var groupTypePurposeValue = gfSettings.GetUserPreference("Group Type Purpose").AsIntegerOrNull(); var groupList = new List <GroupListRowInfo>(); if (GroupListGridMode == GridListGridMode.GroupsPersonMemberOf) { // Grid is in 'Groups that Person is member of' mode var personContext = ContextEntity <Person>(); if (personContext != null) { // limit to Groups that the person is a member of var qry = new GroupMemberService(rockContext).Queryable(true) .Where(m => m.PersonId == personContext.Id) .Join(qryGroups, gm => gm.GroupId, g => g.Id, (gm, g) => new { Group = g, GroupMember = gm }); // Filter by Active Status of Group and Group Membership. if (showActive && !showInactive) { // Show only active Groups and active Memberships. qry = qry.Where(gmg => gmg.Group.IsActive && gmg.GroupMember.GroupMemberStatus == GroupMemberStatus.Active); } else if (!showActive) { // Show only inactive Groups or inactive Memberships. qry = qry.Where(gmg => !gmg.Group.IsActive || gmg.GroupMember.GroupMemberStatus == GroupMemberStatus.Inactive); } if (groupTypePurposeValue.HasValue && gfSettings.Visible) { qry = qry.Where(t => t.Group.GroupType.GroupTypePurposeValueId == groupTypePurposeValue); } // load with Groups where the current person has GroupMemberHistory for _groupsWithGroupHistory = new HashSet <int>(new GroupMemberHistoricalService(rockContext).Queryable().Where(a => qry.Any(x => x.GroupMember.Id == a.GroupMemberId)).Select(a => a.GroupId).ToList()); groupList = qry .AsEnumerable() .Where(gm => gm.Group.IsAuthorized(Rock.Security.Authorization.VIEW, CurrentPerson)) .Select(m => new GroupListRowInfo { Id = m.Group.Id, GroupMemberId = m.GroupMember.Id, Path = string.Empty, Name = m.Group.Name, GroupType = GroupTypeCache.Get(m.Group.GroupTypeId), GroupOrder = m.Group.Order, Description = m.Group.Description, IsSystem = m.Group.IsSystem, GroupRole = m.GroupMember.GroupRole.Name, DateAdded = m.GroupMember.DateTimeAdded ?? m.GroupMember.CreatedDateTime, IsActive = m.Group.IsActive && (m.GroupMember.GroupMemberStatus == GroupMemberStatus.Active), IsArchived = m.Group.IsArchived || m.GroupMember.IsArchived, IsActiveOrder = m.Group.IsActive && m.GroupMember.GroupMemberStatus == GroupMemberStatus.Active ? 1 : 2, IsSynced = m.Group.GroupSyncs.Where(s => s.GroupTypeRoleId == m.GroupMember.GroupRoleId).Any(), MemberCount = 0 }) .AsQueryable() .Sort(sortProperty) .ToList(); } } else { // Grid is in normal 'Group List' mode var roleGroupType = GroupTypeCache.Get(Rock.SystemGuid.GroupType.GROUPTYPE_SECURITY_ROLE.AsGuid()); int roleGroupTypeId = roleGroupType != null ? roleGroupType.Id : 0; bool useRolePrefix = onlySecurityGroups || groupTypeIds.Contains(roleGroupTypeId); if (!showInactive) { qryGroups = qryGroups.Where(x => x.IsActive); } else if (!showActive) { qryGroups = qryGroups.Where(x => !x.IsActive); } if (groupTypePurposeValue.HasValue && gfSettings.Visible) { qryGroups = qryGroups.Where(t => t.GroupType.GroupTypePurposeValueId == groupTypePurposeValue); } // load with groups that have Group History _groupsWithGroupHistory = new HashSet <int>(new GroupHistoricalService(rockContext).Queryable().Where(a => qryGroups.Any(g => g.Id == a.GroupId)).Select(a => a.GroupId).ToList()); groupList = qryGroups .AsEnumerable() .Where(g => g.IsAuthorized(Rock.Security.Authorization.VIEW, CurrentPerson)) .Select(g => new GroupListRowInfo { Id = g.Id, Path = string.Empty, Name = ((useRolePrefix && g.GroupType.Id != roleGroupTypeId) ? "GROUP - " : string.Empty) + g.Name, GroupType = GroupTypeCache.Get(g.GroupTypeId), GroupOrder = g.Order, Description = g.Description, IsSystem = g.IsSystem, IsActive = g.IsActive, IsArchived = g.IsArchived, IsActiveOrder = g.IsActive ? 1 : 2, GroupRole = string.Empty, DateAdded = DateTime.MinValue, IsSynced = g.GroupSyncs.Any(), MemberCount = g.Members.Count() }) .AsQueryable() .Sort(sortProperty) .ToList(); } if (_showGroupPath) { foreach (var groupRow in groupList) { groupRow.Path = groupService.GroupAncestorPathName(groupRow.Id); } } gGroups.DataSource = groupList; gGroups.EntityTypeId = EntityTypeCache.Get <Group>().Id; gGroups.DataBind(); // hide the group type column if there's only one type; must come after DataBind() if (_groupTypesCount == 1) { var groupTypeColumn = this.gGroups.ColumnsOfType <RockBoundField>().FirstOrDefault(a => a.DataField == "GroupTypeName"); groupTypeColumn.Visible = false; } }
/// <summary> /// Job that will send scheduled group SMS messages. /// /// Called by the <see cref="IScheduler" /> when a /// <see cref="ITrigger" /> fires that is associated with /// the <see cref="IJob" />. /// </summary> public virtual void Execute(IJobExecutionContext context) { var dataMap = context.JobDetail.JobDataMap; int?commandTimeout = dataMap.GetString("CommandTimeout").AsIntegerOrNull(); int?lastRunBuffer = dataMap.GetString("LastRunBuffer").AsIntegerOrNull(); var enabledLavaCommands = dataMap.GetString("EnabledLavaCommands"); var JobStartDateTime = RockDateTime.Now; var dateAttributes = new List <AttributeValue>(); var dAttributeMatrixItemAndGroupIds = new Dictionary <int, int>(); // Key: AttributeMatrixItemId Value: GroupId int communicationsSent = 0; var smsMediumType = EntityTypeCache.Get("Rock.Communication.Medium.Sms"); var dateAttributeId = Rock.Web.Cache.AttributeCache.Get(KFSConst.Attribute.MATRIX_ATTRIBUTE_SMS_SEND_DATE.AsGuid()).Id; var recurrenceAttributeId = Rock.Web.Cache.AttributeCache.Get(KFSConst.Attribute.MATRIX_ATTRIBUTE_SMS_SEND_RECURRENCE.AsGuid()).Id; var fromNumberAttributeId = Rock.Web.Cache.AttributeCache.Get(KFSConst.Attribute.MATRIX_ATTRIBUTE_SMS_FROM_NUMBER.AsGuid()).Id; var messageAttributeId = Rock.Web.Cache.AttributeCache.Get(KFSConst.Attribute.MATRIX_ATTRIBUTE_SMS_MESSAGE.AsGuid()).Id; try { using (var rockContext = new RockContext()) { // get the last run date or yesterday DateTime?lastStartDateTime = null; // get job type id int jobId = context.JobDetail.Description.AsInteger(); // load job var job = new ServiceJobService(rockContext) .GetNoTracking(jobId); if (job != null && job.Guid != Rock.SystemGuid.ServiceJob.JOB_PULSE.AsGuid()) { lastStartDateTime = job.LastRunDateTime?.AddSeconds(0.0d - ( double )(job.LastRunDurationSeconds + lastRunBuffer)); } var beginDateTime = lastStartDateTime ?? JobStartDateTime.AddDays(-1); // get the date attributes dateAttributes = new AttributeValueService(rockContext) .Queryable().AsNoTracking() .Where(d => d.AttributeId == dateAttributeId && d.EntityId.HasValue && d.ValueAsDateTime >= beginDateTime && d.ValueAsDateTime <= JobStartDateTime) .ToList(); } foreach (var d in dateAttributes) { // Use a new context to limit the amount of change-tracking required using (var rockContext = new RockContext()) { var attributeMatrixId = new AttributeMatrixItemService(rockContext) .GetNoTracking(d.EntityId.Value) .AttributeMatrixId; var attributeMatrixGuid = new AttributeMatrixService(rockContext) .GetNoTracking(attributeMatrixId) .Guid .ToString(); var attributeValue = new AttributeValueService(rockContext) .Queryable().AsNoTracking() .FirstOrDefault(a => a.Value.Equals(attributeMatrixGuid, StringComparison.CurrentCultureIgnoreCase)); if (attributeValue != null && attributeValue.EntityId.HasValue) { dAttributeMatrixItemAndGroupIds.Add(d.EntityId.Value, attributeValue.EntityId.Value); } } } foreach (var attributeMatrixItemAndGroupId in dAttributeMatrixItemAndGroupIds) { // Use a new context to limit the amount of change-tracking required using (var rockContext = new RockContext()) { rockContext.Database.CommandTimeout = commandTimeout; var fromNumberGuid = new AttributeValueService(rockContext) .GetByAttributeIdAndEntityId(fromNumberAttributeId, attributeMatrixItemAndGroupId.Key) .Value; var fromNumber = DefinedValueCache.Get(fromNumberGuid.AsGuid()); var message = new AttributeValueService(rockContext) .GetByAttributeIdAndEntityId(messageAttributeId, attributeMatrixItemAndGroupId.Key) .Value; var attachments = new List <BinaryFile>(); var group = new GroupService(rockContext) .GetNoTracking(attributeMatrixItemAndGroupId.Value); if (!message.IsNullOrWhiteSpace() && smsMediumType != null) { var recipients = new GroupMemberService(rockContext) .GetByGroupId(attributeMatrixItemAndGroupId.Value).AsNoTracking() .Where(m => m.GroupMemberStatus == GroupMemberStatus.Active) .ToList(); if (recipients.Any()) { var communicationService = new CommunicationService(rockContext); var communication = new Rock.Model.Communication(); communication.Status = CommunicationStatus.Transient; communication.ReviewedDateTime = JobStartDateTime; communication.ReviewerPersonAliasId = group.ModifiedByPersonAliasId; communication.SenderPersonAliasId = group.ModifiedByPersonAliasId; communication.CreatedByPersonAliasId = group.ModifiedByPersonAliasId; communicationService.Add(communication); communication.EnabledLavaCommands = enabledLavaCommands; var personIdHash = new HashSet <int>(); foreach (var groupMember in recipients) { // Use a new context to limit the amount of change-tracking required using (var groupMemberContext = new RockContext()) { if (!personIdHash.Contains(groupMember.PersonId)) { var person = new PersonService(groupMemberContext) .GetNoTracking(groupMember.PersonId); if (person != null && person.PrimaryAliasId.HasValue) { personIdHash.Add(groupMember.PersonId); var communicationRecipient = new CommunicationRecipient(); communicationRecipient.PersonAliasId = person.PrimaryAliasId; communicationRecipient.AdditionalMergeValues = new Dictionary <string, object>(); communicationRecipient.AdditionalMergeValues.Add("GroupMember", groupMember); //communicationRecipient.AdditionalMergeValues.Add( "Group", group ); communication.Recipients.Add(communicationRecipient); } } } } communication.IsBulkCommunication = false; communication.CommunicationType = CommunicationType.SMS; communication.CommunicationTemplateId = null; foreach (var recipient in communication.Recipients) { recipient.MediumEntityTypeId = smsMediumType.Id; } communication.SMSMessage = message; communication.SMSFromDefinedValueId = fromNumber.Id; communication.Subject = string.Empty; communication.Status = CommunicationStatus.Approved; rockContext.SaveChanges(); Rock.Model.Communication.Send(communication); communicationsSent = communicationsSent + personIdHash.Count; var recurrence = new AttributeValueService(rockContext) .GetByAttributeIdAndEntityId(recurrenceAttributeId, attributeMatrixItemAndGroupId.Key); if (recurrence != null && !string.IsNullOrWhiteSpace(recurrence.Value)) { var sendDate = new AttributeValueService(rockContext) .GetByAttributeIdAndEntityId(dateAttributeId, attributeMatrixItemAndGroupId.Key); switch (recurrence.Value) { case "1": sendDate.Value = sendDate.ValueAsDateTime.Value.AddDays(7).ToString(); break; case "2": sendDate.Value = sendDate.ValueAsDateTime.Value.AddDays(14).ToString(); break; case "3": sendDate.Value = sendDate.ValueAsDateTime.Value.AddMonths(1).ToString(); break; case "4": sendDate.Value = sendDate.ValueAsDateTime.Value.AddDays(1).ToString(); break; default: break; } rockContext.SaveChanges(); } } } } } if (communicationsSent > 0) { context.Result = string.Format("Sent {0} {1}", communicationsSent, "communication".PluralizeIf(communicationsSent > 1)); } else { context.Result = "No communications to send"; } } catch (System.Exception ex) { HttpContext context2 = HttpContext.Current; ExceptionLogService.LogException(ex, context2); throw; } }
/// <summary> /// Job that will sync groups. /// /// Called by the <see cref="IScheduler" /> when a /// <see cref="ITrigger" /> fires that is associated with /// the <see cref="IJob" />. /// </summary> public virtual void Execute(IJobExecutionContext context) { // Get the job setting(s) JobDataMap dataMap = context.JobDetail.JobDataMap; bool requirePasswordReset = dataMap.GetBoolean("RequirePasswordReset"); var commandTimeout = dataMap.GetString("CommandTimeout").AsIntegerOrNull() ?? 180; // Counters for displaying results int groupsSynced = 0; int groupsChanged = 0; string groupName = string.Empty; string dataViewName = string.Empty; var errors = new List <string>(); try { // get groups set to sync var activeSyncList = new List <GroupSyncInfo>(); using (var rockContext = new RockContext()) { // Get groups that are not archived and are still active. activeSyncList = new GroupSyncService(rockContext) .Queryable() .AsNoTracking() .AreNotArchived() .AreActive() .NeedToBeSynced() .Select(x => new GroupSyncInfo { SyncId = x.Id, GroupName = x.Group.Name }) .ToList(); } foreach (var syncInfo in activeSyncList) { int syncId = syncInfo.SyncId; bool hasSyncChanged = false; context.UpdateLastStatusMessage($"Syncing group {syncInfo.GroupName}"); // Use a fresh rockContext per sync so that ChangeTracker doesn't get bogged down using (var rockContext = new RockContext()) { // increase the timeout just in case the data view source is slow rockContext.Database.CommandTimeout = commandTimeout; rockContext.SourceOfChange = "Group Sync"; // Get the Sync var sync = new GroupSyncService(rockContext) .Queryable() .Include(a => a.Group) .Include(a => a.SyncDataView) .AsNoTracking() .FirstOrDefault(s => s.Id == syncId); if (sync == null || sync.SyncDataView.EntityTypeId != EntityTypeCache.Get(typeof(Person)).Id) { // invalid sync or invalid SyncDataView continue; } List <string> syncErrors = new List <string>(); dataViewName = sync.SyncDataView.Name; groupName = sync.Group.Name; Stopwatch stopwatch = Stopwatch.StartNew(); // Get the person id's from the data view (source) var dataViewQry = sync.SyncDataView.GetQuery(null, rockContext, commandTimeout, out syncErrors); var sourcePersonIds = dataViewQry.Select(q => q.Id).ToList(); stopwatch.Stop(); DataViewService.AddRunDataViewTransaction(sync.SyncDataView.Id, Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds)); // If any error occurred trying get the 'where expression' from the sync-data-view, // just skip trying to sync that particular group's Sync Data View for now. if (syncErrors.Count > 0) { errors.AddRange(syncErrors); ExceptionLogService.LogException(new Exception(string.Format("An error occurred while trying to GroupSync group '{0}' and data view '{1}' so the sync was skipped. Error: {2}", groupName, dataViewName, String.Join(",", syncErrors)))); continue; } // Get the person id's in the group (target) for the role being synced. // Note: targetPersonIds must include archived group members // so we don't try to delete anyone who's already archived, and // it must include deceased members so we can remove them if they // are no longer in the data view. var existingGroupMemberPersonList = new GroupMemberService(rockContext) .Queryable(true, true).AsNoTracking() .Where(gm => gm.GroupId == sync.GroupId) .Where(gm => gm.GroupRoleId == sync.GroupTypeRoleId) .Select(gm => new { PersonId = gm.PersonId, IsArchived = gm.IsArchived }) .ToList(); var targetPersonIdsToDelete = existingGroupMemberPersonList.Where(t => !sourcePersonIds.Contains(t.PersonId) && t.IsArchived != true).ToList(); if (targetPersonIdsToDelete.Any()) { context.UpdateLastStatusMessage($"Deleting {targetPersonIdsToDelete.Count()} group records in {syncInfo.GroupName} that are no longer in the sync data view"); } int deletedCount = 0; // Delete people from the group/role that are no longer in the data view -- // but not the ones that are already archived. foreach (var targetPerson in targetPersonIdsToDelete) { deletedCount++; if (deletedCount % 100 == 0) { context.UpdateLastStatusMessage($"Deleted {deletedCount} of {targetPersonIdsToDelete.Count()} group member records for group {syncInfo.GroupName}"); } try { // Use a new context to limit the amount of change-tracking required using (var groupMemberContext = new RockContext()) { // Delete the records for that person's group and role. // NOTE: just in case there are duplicate records, delete all group member records for that person and role var groupMemberService = new GroupMemberService(groupMemberContext); foreach (var groupMember in groupMemberService .Queryable(true, true) .Where(m => m.GroupId == sync.GroupId && m.GroupRoleId == sync.GroupTypeRoleId && m.PersonId == targetPerson.PersonId) .ToList()) { groupMemberService.Delete(groupMember); } groupMemberContext.SaveChanges(); // If the Group has an exit email, and person has an email address, send them the exit email if (sync.ExitSystemCommunication != null) { var person = new PersonService(groupMemberContext).Get(targetPerson.PersonId); if (person.CanReceiveEmail(false)) { // Send the exit email var mergeFields = new Dictionary <string, object>(); mergeFields.Add("Group", sync.Group); mergeFields.Add("Person", person); var emailMessage = new RockEmailMessage(sync.ExitSystemCommunication); emailMessage.AddRecipient(new RockEmailMessageRecipient(person, mergeFields)); var emailErrors = new List <string>(); emailMessage.Send(out emailErrors); errors.AddRange(emailErrors); } } } } catch (Exception ex) { ExceptionLogService.LogException(ex); continue; } hasSyncChanged = true; } // Now find all the people in the source list who are NOT already in the target list (as Unarchived) var targetPersonIdsToAdd = sourcePersonIds.Where(s => !existingGroupMemberPersonList.Any(t => t.PersonId == s && t.IsArchived == false)).ToList(); // Make a list of PersonIds that have an Archived group member record // if this person isn't already a member of the list as an Unarchived member, we can Restore the group member for that PersonId instead var archivedTargetPersonIds = existingGroupMemberPersonList.Where(t => t.IsArchived == true).Select(a => a.PersonId).ToList(); context.UpdateLastStatusMessage($"Adding {targetPersonIdsToAdd.Count()} group member records for group {syncInfo.GroupName}"); int addedCount = 0; int notAddedCount = 0; foreach (var personId in targetPersonIdsToAdd) { if ((addedCount + notAddedCount) % 100 == 0) { string notAddedMessage = string.Empty; if (notAddedCount > 0) { notAddedMessage = $"{Environment.NewLine} There are {notAddedCount} members that could not be added due to group requirements."; } context.UpdateLastStatusMessage($"Added {addedCount} of {targetPersonIdsToAdd.Count()} group member records for group {syncInfo.GroupName}. {notAddedMessage}"); } try { // Use a new context to limit the amount of change-tracking required using (var groupMemberContext = new RockContext()) { var groupMemberService = new GroupMemberService(groupMemberContext); var groupService = new GroupService(groupMemberContext); // If this person is currently archived... if (archivedTargetPersonIds.Contains(personId)) { // ...then we'll just restore them; GroupMember archivedGroupMember = groupService.GetArchivedGroupMember(sync.Group, personId, sync.GroupTypeRoleId); if (archivedGroupMember == null) { // shouldn't happen, but just in case continue; } archivedGroupMember.GroupMemberStatus = GroupMemberStatus.Active; if (archivedGroupMember.IsValidGroupMember(groupMemberContext)) { addedCount++; groupMemberService.Restore(archivedGroupMember); groupMemberContext.SaveChanges(); } else { notAddedCount++; // Validation errors will get added to the ValidationResults collection. Add those results to the log and then move on to the next person. var ex = new GroupMemberValidationException(string.Join(",", archivedGroupMember.ValidationResults.Select(r => r.ErrorMessage).ToArray())); ExceptionLogService.LogException(ex); continue; } } else { // ...otherwise we will add a new person to the group with the role specified in the sync. var newGroupMember = new GroupMember { Id = 0 }; newGroupMember.PersonId = personId; newGroupMember.GroupId = sync.GroupId; newGroupMember.GroupMemberStatus = GroupMemberStatus.Active; newGroupMember.GroupRoleId = sync.GroupTypeRoleId; if (newGroupMember.IsValidGroupMember(groupMemberContext)) { addedCount++; groupMemberService.Add(newGroupMember); groupMemberContext.SaveChanges(); } else { notAddedCount++; // Validation errors will get added to the ValidationResults collection. Add those results to the log and then move on to the next person. var ex = new GroupMemberValidationException(string.Join(",", newGroupMember.ValidationResults.Select(r => r.ErrorMessage).ToArray())); ExceptionLogService.LogException(ex); continue; } } // If the Group has a welcome email, and person has an email address, send them the welcome email and possibly create a login if (sync.WelcomeSystemCommunication != null) { var person = new PersonService(groupMemberContext).Get(personId); if (person.CanReceiveEmail(false)) { // If the group is configured to add a user account for anyone added to the group, and person does not yet have an // account, add one for them. string newPassword = string.Empty; bool createLogin = sync.AddUserAccountsDuringSync; // Only create a login if requested, no logins exist and we have enough information to generate a user name. if (createLogin && !person.Users.Any() && !string.IsNullOrWhiteSpace(person.NickName) && !string.IsNullOrWhiteSpace(person.LastName)) { newPassword = System.Web.Security.Membership.GeneratePassword(9, 1); string username = Rock.Security.Authentication.Database.GenerateUsername(person.NickName, person.LastName); UserLogin login = UserLoginService.Create( groupMemberContext, person, AuthenticationServiceType.Internal, EntityTypeCache.Get(Rock.SystemGuid.EntityType.AUTHENTICATION_DATABASE.AsGuid()).Id, username, newPassword, true, requirePasswordReset); } // Send the welcome email var mergeFields = new Dictionary <string, object>(); mergeFields.Add("Group", sync.Group); mergeFields.Add("Person", person); mergeFields.Add("NewPassword", newPassword); mergeFields.Add("CreateLogin", createLogin); var emailMessage = new RockEmailMessage(sync.WelcomeSystemCommunication); emailMessage.AddRecipient(new RockEmailMessageRecipient(person, mergeFields)); var emailErrors = new List <string>(); emailMessage.Send(out emailErrors); errors.AddRange(emailErrors); } } } } catch (Exception ex) { ExceptionLogService.LogException(ex); continue; } hasSyncChanged = true; } // Increment Groups Changed Counter (if people were deleted or added to the group) if (hasSyncChanged) { groupsChanged++; } // Increment the Groups Synced Counter groupsSynced++; } // Update last refresh datetime in different context to avoid side-effects. using (var rockContext = new RockContext()) { var sync = new GroupSyncService(rockContext) .Queryable() .FirstOrDefault(s => s.Id == syncId); sync.LastRefreshDateTime = RockDateTime.Now; rockContext.SaveChanges(); } } // Format the result message var resultMessage = string.Empty; if (groupsSynced == 0) { resultMessage = "No groups to sync"; } else if (groupsSynced == 1) { resultMessage = "1 group was synced"; } else { resultMessage = string.Format("{0} groups were synced", groupsSynced); } resultMessage += string.Format(" and {0} groups were changed", groupsChanged); if (errors.Any()) { StringBuilder sb = new StringBuilder(); sb.AppendLine(); sb.Append("Errors: "); errors.ForEach(e => { sb.AppendLine(); sb.Append(e); }); string errorMessage = sb.ToString(); resultMessage += errorMessage; throw new Exception(errorMessage); } context.Result = resultMessage; } catch (System.Exception ex) { HttpContext context2 = HttpContext.Current; ExceptionLogService.LogException(ex, context2); throw; } }
/// <summary> /// Binds the group members grid. /// </summary> protected void BindGroupMembersGrid(bool selectAll = false) { if (_group != null) { pnlGroupMembers.Visible = true; lHeading.Text = string.Format("{0} {1}", _group.GroupType.GroupTerm, _group.GroupType.GroupMemberTerm.Pluralize()); if (_group.GroupType.Roles.Any()) { nbRoleWarning.Visible = false; rFilter.Visible = true; gGroupMembers.Visible = true; var rockContext = new RockContext(); GroupMemberService groupMemberService = new GroupMemberService(rockContext); var qry = groupMemberService.Queryable("Person,GroupRole", true).AsNoTracking() .Where(m => m.GroupId == _group.Id); // Filter by First Name string firstName = tbFirstName.Text; if (!string.IsNullOrWhiteSpace(firstName)) { qry = qry.Where(m => m.Person.FirstName.StartsWith(firstName) || m.Person.NickName.StartsWith(firstName)); } // Filter by Last Name string lastName = tbLastName.Text; if (!string.IsNullOrWhiteSpace(lastName)) { qry = qry.Where(m => m.Person.LastName.StartsWith(lastName)); } // Filter by role var validGroupTypeRoles = _group.GroupType.Roles.Select(r => r.Id).ToList(); var roles = new List <int>(); foreach (var roleId in cblRole.SelectedValues.AsIntegerList()) { if (validGroupTypeRoles.Contains(roleId)) { roles.Add(roleId); } } if (roles.Any()) { qry = qry.Where(m => roles.Contains(m.GroupRoleId)); } // Filter by Group Member Status var statuses = new List <GroupMemberStatus>(); foreach (string status in cblGroupMemberStatus.SelectedValues) { if (!string.IsNullOrWhiteSpace(status)) { statuses.Add(status.ConvertToEnum <GroupMemberStatus>()); } } if (statuses.Any()) { qry = qry.Where(m => statuses.Contains(m.GroupMemberStatus)); } // Filter by Campus if (cpCampusFilter.SelectedCampusId.HasValue) { Guid familyGuid = new Guid(Rock.SystemGuid.GroupType.GROUPTYPE_FAMILY); int campusId = cpCampusFilter.SelectedCampusId.Value; var qryFamilyMembersForCampus = new GroupMemberService(rockContext).Queryable().Where(a => a.Group.GroupType.Guid == familyGuid && a.Group.CampusId == campusId); qry = qry.Where(a => qryFamilyMembersForCampus.Any(f => f.PersonId == a.PersonId)); } // Filter query by any configured attribute filters if (AvailableAttributes != null && AvailableAttributes.Any()) { var attributeValueService = new AttributeValueService(rockContext); var parameterExpression = attributeValueService.ParameterExpression; foreach (var attribute in AvailableAttributes) { var filterControl = phAttributeFilters.FindControl("filter_" + attribute.Id.ToString()); if (filterControl != null) { var filterValues = attribute.FieldType.Field.GetFilterValues(filterControl, attribute.QualifierValues, Rock.Reporting.FilterMode.SimpleFilter); var expression = attribute.FieldType.Field.AttributeFilterExpression(attribute.QualifierValues, filterValues, parameterExpression); if (expression != null) { var attributeValues = attributeValueService .Queryable() .Where(v => v.Attribute.Id == attribute.Id); attributeValues = attributeValues.Where(parameterExpression, expression, null); qry = qry.Where(w => attributeValues.Select(v => v.EntityId).Contains(w.Id)); } } } } _inactiveStatus = DefinedValueCache.Read(Rock.SystemGuid.DefinedValue.PERSON_RECORD_STATUS_INACTIVE); SortProperty sortProperty = gGroupMembers.SortProperty; bool hasGroupRequirements = new GroupRequirementService(rockContext).Queryable().Where(a => a.GroupId == _group.Id).Any(); // If there are group requirements that that member doesn't meet, show an icon in the grid bool includeWarnings = false; var groupMemberIdsThatLackGroupRequirements = new GroupService(rockContext).GroupMembersNotMeetingRequirements(_group.Id, includeWarnings).Select(a => a.Key.Id); List <GroupMember> groupMembersList = null; if (sortProperty != null) { groupMembersList = qry.Sort(sortProperty).ToList(); } else { groupMembersList = qry.OrderBy(a => a.GroupRole.Order).ThenBy(a => a.Person.LastName).ThenBy(a => a.Person.FirstName).ToList(); } // Since we're not binding to actual group member list, but are using AttributeField columns, // we need to save the workflows into the grid's object list gGroupMembers.ObjectList = new Dictionary <string, object>(); groupMembersList.ForEach(m => gGroupMembers.ObjectList.Add(m.Id.ToString(), m)); gGroupMembers.EntityTypeId = EntityTypeCache.Read(Rock.SystemGuid.EntityType.GROUP_MEMBER.AsGuid()).Id; var homePhoneType = DefinedValueCache.Read(Rock.SystemGuid.DefinedValue.PERSON_PHONE_TYPE_HOME); var cellPhoneType = DefinedValueCache.Read(Rock.SystemGuid.DefinedValue.PERSON_PHONE_TYPE_MOBILE); // If exporting to Excel, the selectAll option will be true, and home location should be calculated var homeLocations = new Dictionary <int, Location>(); if (selectAll) { foreach (var m in groupMembersList) { homeLocations.Add(m.Id, m.Person.GetHomeLocation(rockContext)); } } var groupMemberIds = groupMembersList.Select(m => m.Id).ToList(); // Get all the group members with any associated registrations _groupMembersWithRegistrations = new RegistrationRegistrantService(rockContext) .Queryable().AsNoTracking() .Where(r => r.Registration != null && r.Registration.RegistrationInstance != null && r.GroupMemberId.HasValue && groupMemberIds.Contains(r.GroupMemberId.Value)) .ToList() .GroupBy(r => r.GroupMemberId.Value) .Select(g => new { GroupMemberId = g.Key, Registrations = g.ToList() .Select(r => new { Id = r.Registration.Id, Name = r.Registration.RegistrationInstance.Name }) .ToDictionary(r => r.Id, r => r.Name) }) .ToDictionary(r => r.GroupMemberId, r => r.Registrations); var registrationField = gGroupMembers.ColumnsOfType <RockTemplateFieldUnselected>().FirstOrDefault(); if (registrationField != null) { registrationField.Visible = _groupMembersWithRegistrations.Any(); } var connectionStatusField = gGroupMembers.ColumnsOfType <DefinedValueField>().FirstOrDefault(a => a.DataField == "ConnectionStatusValueId"); if (connectionStatusField != null) { connectionStatusField.Visible = _group.GroupType.ShowConnectionStatus; } string photoFormat = "<div class=\"photo-icon photo-round photo-round-xs pull-left margin-r-sm js-person-popover\" personid=\"{0}\" data-original=\"{1}&w=50\" style=\"background-image: url( '{2}' ); background-size: cover; background-repeat: no-repeat;\"></div>"; gGroupMembers.DataSource = groupMembersList.Select(m => new { m.Id, m.Guid, m.PersonId, m.Person.NickName, m.Person.LastName, Name = (selectAll ? m.Person.LastName + ", " + m.Person.NickName : string.Format(photoFormat, m.PersonId, m.Person.PhotoUrl, ResolveUrl("~/Assets/Images/person-no-photo-male.svg")) + m.Person.NickName + " " + m.Person.LastName + (hasGroupRequirements && groupMemberIdsThatLackGroupRequirements.Contains(m.Id) ? " <i class='fa fa-exclamation-triangle text-warning'></i>" : string.Empty) + (!string.IsNullOrEmpty(m.Note) ? " <i class='fa fa-file-text-o text-info'></i>" : string.Empty)), m.Person.ConnectionStatusValueId, Email = m.Person.Email, HomePhone = selectAll && homePhoneType != null ? m.Person.PhoneNumbers .Where(p => p.NumberTypeValueId.HasValue && p.NumberTypeValueId.Value == homePhoneType.Id) .Select(p => p.NumberFormatted) .FirstOrDefault() : string.Empty, CellPhone = selectAll && cellPhoneType != null ? m.Person.PhoneNumbers .Where(p => p.NumberTypeValueId.HasValue && p.NumberTypeValueId.Value == cellPhoneType.Id) .Select(p => p.NumberFormatted) .FirstOrDefault() : string.Empty, HomeAddress = homeLocations.ContainsKey(m.Id) && homeLocations[m.Id] != null ? homeLocations[m.Id].FormattedAddress : string.Empty, Latitude = homeLocations.ContainsKey(m.Id) && homeLocations[m.Id] != null ? homeLocations[m.Id].Latitude : (double?)null, Longitude = homeLocations.ContainsKey(m.Id) && homeLocations[m.Id] != null ? homeLocations[m.Id].Longitude : (double?)null, GroupRole = m.GroupRole.Name, m.GroupMemberStatus, RecordStatusValueId = m.Person.RecordStatusValueId, IsDeceased = m.Person.IsDeceased }).ToList(); gGroupMembers.DataBind(); } else { nbRoleWarning.Text = string.Format( "{0} cannot be added to this {1} because the '{2}' group type does not have any roles defined.", _group.GroupType.GroupMemberTerm.Pluralize(), _group.GroupType.GroupTerm, _group.GroupType.Name); nbRoleWarning.Visible = true; rFilter.Visible = false; gGroupMembers.Visible = false; } } else { pnlGroupMembers.Visible = false; } }
/// <summary> /// Job that will sync groups. /// /// Called by the <see cref="IScheduler" /> when a /// <see cref="ITrigger" /> fires that is associated with /// the <see cref="IJob" />. /// </summary> public virtual void Execute(IJobExecutionContext context) { // Get the job setting(s) JobDataMap dataMap = context.JobDetail.JobDataMap; bool requirePasswordReset = dataMap.GetBoolean("RequirePasswordReset"); var commandTimeout = dataMap.GetString("CommandTimeout").AsIntegerOrNull() ?? 180; // Counters for displaying results int groupsSynced = 0; int groupsChanged = 0; string groupName = string.Empty; string dataViewName = string.Empty; var errors = new List <string>(); try { // get groups set to sync var activeSyncIds = new List <int>(); using (var rockContext = new RockContext()) { // Get groups that are not archived and are still active. activeSyncIds = new GroupSyncService(rockContext) .Queryable().AsNoTracking() .Where(x => !x.Group.IsArchived && x.Group.IsActive) .Select(x => x.Id) .ToList(); } foreach (var syncId in activeSyncIds) { bool hasSyncChanged = false; // Use a fresh rockContext per sync so that ChangeTracker doesn't get bogged down using (var rockContext = new RockContext()) { // increase the timeout just in case the data view source is slow rockContext.Database.CommandTimeout = commandTimeout; rockContext.SourceOfChange = "Group Sync"; // Get the Sync var sync = new GroupSyncService(rockContext) .Queryable().AsNoTracking() .FirstOrDefault(s => s.Id == syncId); // Ensure that the group's Sync Data View is a person data view if (sync != null && sync.SyncDataView.EntityTypeId == EntityTypeCache.Get(typeof(Person)).Id) { List <string> syncErrors = new List <string>(); dataViewName = sync.SyncDataView.Name; groupName = sync.Group.Name; // Get the person id's from the data view (source) var personService = new PersonService(rockContext); var sourcePersonIds = sync.SyncDataView.GetQuery(null, rockContext, commandTimeout, out syncErrors) .Select(q => q.Id).ToList(); // If any error occurred trying get the 'where expression' from the sync-data-view, // just skip trying to sync that particular group's Sync Data View for now. if (syncErrors.Count > 0) { errors.AddRange(syncErrors); ExceptionLogService.LogException(new Exception(string.Format("An error occurred while trying to GroupSync group '{0}' and data view '{1}' so the sync was skipped. Error: {2}", groupName, dataViewName, String.Join(",", syncErrors)))); continue; } // Get the person id's in the group (target) for the role being synced. // Note: targetPersonIds must include archived group members // so we don't try to delete anyone who's already archived, and // it must include deceased members so we can remove them if they // are no longer in the data view. var targetPersonIds = new GroupMemberService(rockContext) .Queryable(true, true).AsNoTracking() .Where(gm => gm.GroupId == sync.GroupId) .Where(gm => gm.GroupRoleId == sync.GroupTypeRoleId) .Select(gm => new { PersonId = gm.PersonId, IsArchived = gm.IsArchived }) .ToList(); // Delete people from the group/role that are no longer in the data view -- // but not the ones that are already archived. foreach (var targetPerson in targetPersonIds.Where(t => !sourcePersonIds.Contains(t.PersonId) && t.IsArchived != true)) { try { // Use a new context to limit the amount of change-tracking required using (var groupMemberContext = new RockContext()) { // Delete the records for that person's group and role var groupMemberService = new GroupMemberService(groupMemberContext); foreach (var groupMember in groupMemberService .Queryable(true, true) .Where(m => m.GroupId == sync.GroupId && m.GroupRoleId == sync.GroupTypeRoleId && m.PersonId == targetPerson.PersonId) .ToList()) { groupMemberService.Delete(groupMember); } groupMemberContext.SaveChanges(); // If the Group has an exit email, and person has an email address, send them the exit email if (sync.ExitSystemEmail != null) { var person = new PersonService(groupMemberContext).Get(targetPerson.PersonId); if (person.Email.IsNotNullOrWhiteSpace()) { // Send the exit email var mergeFields = new Dictionary <string, object>(); mergeFields.Add("Group", sync.Group); mergeFields.Add("Person", person); var emailMessage = new RockEmailMessage(sync.ExitSystemEmail); emailMessage.AddRecipient(new RecipientData(person.Email, mergeFields)); var emailErrors = new List <string>(); emailMessage.Send(out emailErrors); errors.AddRange(emailErrors); } } } } catch (Exception ex) { ExceptionLogService.LogException(ex); continue; } hasSyncChanged = true; } // Now find all the people in the source list who are NOT already in the target list // or in the target list as archived (so we can restore them). foreach (var personId in sourcePersonIds.Where(s => !targetPersonIds.Any(t => t.PersonId == s) || targetPersonIds.Any(t => t.PersonId == s && t.IsArchived))) { try { // Use a new context to limit the amount of change-tracking required using (var groupMemberContext = new RockContext()) { var groupMemberService = new GroupMemberService(groupMemberContext); // If this person is currently archived... if (targetPersonIds.Any(t => t.PersonId == personId && t.IsArchived == true)) { // ...then we'll just restore them; // NOTE: AddOrRestoreGroupMember will find the exact group member record and // sets their IsArchived back to false. var newGroupMember = groupMemberService.AddOrRestoreGroupMember(sync.Group, personId, sync.GroupTypeRoleId); newGroupMember.GroupMemberStatus = GroupMemberStatus.Active; groupMemberContext.SaveChanges(); } else { // ...otherwise we will add a new person to the group with the role specified in the sync. var newGroupMember = new GroupMember { Id = 0 }; newGroupMember.PersonId = personId; newGroupMember.GroupId = sync.GroupId; newGroupMember.GroupMemberStatus = GroupMemberStatus.Active; newGroupMember.GroupRoleId = sync.GroupTypeRoleId; if (newGroupMember.IsValidGroupMember(groupMemberContext)) { groupMemberService.Add(newGroupMember); groupMemberContext.SaveChanges(); } else { // Validation errors will get added to the ValidationResults collection. Add those results to the log and then move on to the next person. var ex = new GroupMemberValidationException(string.Join(",", newGroupMember.ValidationResults.Select(r => r.ErrorMessage).ToArray())); ExceptionLogService.LogException(ex); continue; } } // If the Group has a welcome email, and person has an email address, send them the welcome email and possibly create a login if (sync.WelcomeSystemEmail != null) { var person = new PersonService(groupMemberContext).Get(personId); if (person.Email.IsNotNullOrWhiteSpace()) { // If the group is configured to add a user account for anyone added to the group, and person does not yet have an // account, add one for them. string newPassword = string.Empty; bool createLogin = sync.AddUserAccountsDuringSync; // Only create a login if requested, no logins exist and we have enough information to generate a user name. if (createLogin && !person.Users.Any() && !string.IsNullOrWhiteSpace(person.NickName) && !string.IsNullOrWhiteSpace(person.LastName)) { newPassword = System.Web.Security.Membership.GeneratePassword(9, 1); string username = Rock.Security.Authentication.Database.GenerateUsername(person.NickName, person.LastName); UserLogin login = UserLoginService.Create( groupMemberContext, person, AuthenticationServiceType.Internal, EntityTypeCache.Get(Rock.SystemGuid.EntityType.AUTHENTICATION_DATABASE.AsGuid()).Id, username, newPassword, true, requirePasswordReset); } // Send the welcome email var mergeFields = new Dictionary <string, object>(); mergeFields.Add("Group", sync.Group); mergeFields.Add("Person", person); mergeFields.Add("NewPassword", newPassword); mergeFields.Add("CreateLogin", createLogin); var emailMessage = new RockEmailMessage(sync.WelcomeSystemEmail); emailMessage.AddRecipient(new RecipientData(person.Email, mergeFields)); var emailErrors = new List <string>(); emailMessage.Send(out emailErrors); errors.AddRange(emailErrors); } } } } catch (Exception ex) { ExceptionLogService.LogException(ex); continue; } hasSyncChanged = true; } // Increment Groups Changed Counter (if people were deleted or added to the group) if (hasSyncChanged) { groupsChanged++; } // Increment the Groups Synced Counter groupsSynced++; } } } // Format the result message var resultMessage = string.Empty; if (groupsSynced == 0) { resultMessage = "No groups to sync"; } else if (groupsSynced == 1) { resultMessage = "1 group was synced"; } else { resultMessage = string.Format("{0} groups were synced", groupsSynced); } resultMessage += string.Format(" and {0} groups were changed", groupsChanged); if (errors.Any()) { StringBuilder sb = new StringBuilder(); sb.AppendLine(); sb.Append("Errors: "); errors.ForEach(e => { sb.AppendLine(); sb.Append(e); }); string errorMessage = sb.ToString(); resultMessage += errorMessage; throw new Exception(errorMessage); } context.Result = resultMessage; } catch (System.Exception ex) { HttpContext context2 = HttpContext.Current; ExceptionLogService.LogException(ex, context2); throw; } }
/// <summary> /// Creates a Linq Expression that can be applied to an IQueryable to filter the result set. /// </summary> /// <param name="entityType">The type of entity in the result set.</param> /// <param name="serviceInstance">A service instance that can be queried to obtain the result set.</param> /// <param name="parameterExpression">The input parameter that will be injected into the filter expression.</param> /// <param name="selection">A formatted string representing the filter settings.</param> /// <returns> /// A Linq Expression that can be used to filter an IQueryable. /// </returns> /// <exception cref="System.Exception">Filter issue(s): + errorMessages.AsDelimited( ; )</exception> public override Expression GetExpression(Type entityType, IService serviceInstance, ParameterExpression parameterExpression, string selection) { var settings = new FilterSettings(selection); var context = ( RockContext )serviceInstance.Context; var locationService = new LocationService(context); var locationQuery = locationService.Queryable(); if (locationQuery != null) { if (!string.IsNullOrWhiteSpace(settings.Street1)) { locationQuery = locationQuery.Where(l => l.Street1.Contains(settings.Street1)); } if (!string.IsNullOrWhiteSpace(settings.City)) { locationQuery = locationQuery.Where(l => l.City == settings.City); } if (!string.IsNullOrWhiteSpace(settings.State)) { locationQuery = locationQuery.Where(l => l.State == settings.State); } if (!string.IsNullOrWhiteSpace(settings.PostalCode)) { locationQuery = locationQuery.Where(l => l.PostalCode.StartsWith(settings.PostalCode)); } if (!string.IsNullOrWhiteSpace(settings.Country)) { locationQuery = locationQuery.Where(l => l.Country == settings.Country); } } // Get all the Family Groups that have a Location matching one of the candidate Locations. int familyGroupTypeId = GroupTypeCache.Read(Rock.SystemGuid.GroupType.GROUPTYPE_FAMILY.AsGuid()).Id; var groupLocationsQuery = new GroupLocationService(context).Queryable() .Where(gl => gl.Group.GroupTypeId == familyGroupTypeId && locationQuery.Any(l => l.Id == gl.LocationId)); // If a Location Type is specified, apply the filter condition. if (settings.LocationTypeGuid.HasValue) { int groupLocationTypeId = DefinedValueCache.Read(settings.LocationTypeGuid.Value).Id; groupLocationsQuery = groupLocationsQuery.Where(x => x.GroupLocationTypeValue.Id == groupLocationTypeId); } // Get all of the Group Members of the qualifying Families. var groupMemberServiceQry = new GroupMemberService(context).Queryable() .Where(gm => groupLocationsQuery.Any(gl => gl.GroupId == gm.GroupId)); // Get all of the People corresponding to the qualifying Group Members. var qry = new PersonService(context).Queryable() .Where(p => groupMemberServiceQry.Any(gm => gm.PersonId == p.Id)); // Retrieve the Filter Expression. var extractedFilterExpression = FilterExpressionExtractor.Extract <Rock.Model.Person>(qry, parameterExpression, "p"); return(extractedFilterExpression); }
/// <summary> /// Job that will run quick SQL queries on a schedule. /// /// Called by the <see cref="IScheduler" /> when a /// <see cref="ITrigger" /> fires that is associated with /// the <see cref="IJob" />. /// </summary> public virtual void Execute(IJobExecutionContext context) { JobDataMap dataMap = context.JobDetail.JobDataMap; Guid?entryWorkflowType = dataMap.GetString("EraEntryWorkflow").AsGuidOrNull(); Guid?exitWorkflowType = dataMap.GetString("EraExitWorkflow").AsGuidOrNull(); bool updateVisitDates = dataMap.GetBooleanValue("SetVisitDates"); var groupTypeList = dataMap.GetString("GroupTypes"); // configuration // // giving int exitGivingCount = 1; // attendance int exitAttendanceCountShort = 1; int exitAttendanceCountLong = 8; // get era dataset from stored proc var resultContext = new RockContext(); var eraAttribute = AttributeCache.Read(SystemGuid.Attribute.PERSON_ERA_CURRENTLY_AN_ERA.AsGuid()); var eraStartAttribute = AttributeCache.Read(SystemGuid.Attribute.PERSON_ERA_START_DATE.AsGuid()); var eraEndAttribute = AttributeCache.Read(SystemGuid.Attribute.PERSON_ERA_END_DATE.AsGuid()); resultContext.Database.CommandTimeout = 3600; var results = resultContext.Database.SqlQuery <EraResult>("spCrm_FamilyAnalyticsEraDataset").ToList(); int personEntityTypeId = EntityTypeCache.Read("Rock.Model.Person").Id; int attributeEntityTypeId = EntityTypeCache.Read("Rock.Model.Attribute").Id; int eraAttributeId = AttributeCache.Read(SystemGuid.Attribute.PERSON_ERA_CURRENTLY_AN_ERA.AsGuid()).Id; int personAnalyticsCategoryId = CategoryCache.Read(SystemGuid.Category.HISTORY_PERSON_ANALYTICS.AsGuid()).Id; foreach (var result in results) { // create new rock context for each family (https://weblog.west-wind.com/posts/2014/Dec/21/Gotcha-Entity-Framework-gets-slow-in-long-Iteration-Loops) RockContext updateContext = new RockContext(); var attributeValueService = new AttributeValueService(updateContext); var historyService = new HistoryService(updateContext); // if era ensure it still meets requirements if (result.IsEra) { if (result.ExitGiftCountDuration < exitGivingCount && result.ExitAttendanceCountDurationShort < exitAttendanceCountShort && result.ExitAttendanceCountDurationLong < exitAttendanceCountLong) { // exit era (delete attribute value from each person in family) var family = new GroupService(updateContext).Queryable("Members, Members.Person").AsNoTracking().Where(m => m.Id == result.FamilyId).FirstOrDefault(); if (family != null) { foreach (var person in family.Members.Select(m => m.Person)) { // remove the era flag var eraAttributeValue = attributeValueService.Queryable().Where(v => v.AttributeId == eraAttribute.Id && v.EntityId == person.Id).FirstOrDefault(); if (eraAttributeValue != null) { attributeValueService.Delete(eraAttributeValue); } // set end date var eraEndAttributeValue = attributeValueService.Queryable().Where(v => v.AttributeId == eraEndAttribute.Id && v.EntityId == person.Id).FirstOrDefault(); if (eraEndAttributeValue == null) { eraEndAttributeValue = new AttributeValue(); eraEndAttributeValue.EntityId = person.Id; eraEndAttributeValue.AttributeId = eraEndAttribute.Id; attributeValueService.Add(eraEndAttributeValue); } eraEndAttributeValue.Value = RockDateTime.Now.ToString(); // add a history record if (personAnalyticsCategoryId != 0 && personEntityTypeId != 0 && attributeEntityTypeId != 0 && eraAttributeId != 0) { History historyRecord = new History(); historyService.Add(historyRecord); historyRecord.EntityTypeId = personEntityTypeId; historyRecord.EntityId = person.Id; historyRecord.CreatedDateTime = RockDateTime.Now; historyRecord.CreatedByPersonAliasId = person.PrimaryAliasId; historyRecord.Caption = "eRA"; historyRecord.Summary = "Exited eRA Status"; historyRecord.Verb = "EXITED"; historyRecord.RelatedEntityTypeId = attributeEntityTypeId; historyRecord.RelatedEntityId = eraAttributeId; historyRecord.CategoryId = personAnalyticsCategoryId; } updateContext.SaveChanges(); } // launch exit workflow if (exitWorkflowType.HasValue) { LaunchWorkflow(exitWorkflowType.Value, family); } } } } else { // entered era var family = new GroupService(updateContext).Queryable("Members").AsNoTracking().Where(m => m.Id == result.FamilyId).FirstOrDefault(); if (family != null) { foreach (var person in family.Members.Where(m => !m.Person.IsDeceased).Select(m => m.Person)) { // set era attribute to true var eraAttributeValue = attributeValueService.Queryable().Where(v => v.AttributeId == eraAttribute.Id && v.EntityId == person.Id).FirstOrDefault(); if (eraAttributeValue == null) { eraAttributeValue = new AttributeValue(); eraAttributeValue.EntityId = person.Id; eraAttributeValue.AttributeId = eraAttribute.Id; attributeValueService.Add(eraAttributeValue); } eraAttributeValue.Value = bool.TrueString; // add start date var eraStartAttributeValue = attributeValueService.Queryable().Where(v => v.AttributeId == eraStartAttribute.Id && v.EntityId == person.Id).FirstOrDefault(); if (eraStartAttributeValue == null) { eraStartAttributeValue = new AttributeValue(); eraStartAttributeValue.EntityId = person.Id; eraStartAttributeValue.AttributeId = eraStartAttribute.Id; attributeValueService.Add(eraStartAttributeValue); } eraStartAttributeValue.Value = RockDateTime.Now.ToString(); // delete end date if it exists var eraEndAttributeValue = attributeValueService.Queryable().Where(v => v.AttributeId == eraEndAttribute.Id && v.EntityId == person.Id).FirstOrDefault(); if (eraEndAttributeValue != null) { attributeValueService.Delete(eraEndAttributeValue); } // add a history record if (personAnalyticsCategoryId != 0 && personEntityTypeId != 0 && attributeEntityTypeId != 0 && eraAttributeId != 0) { History historyRecord = new History(); historyService.Add(historyRecord); historyRecord.EntityTypeId = personEntityTypeId; historyRecord.EntityId = person.Id; historyRecord.CreatedDateTime = RockDateTime.Now; historyRecord.CreatedByPersonAliasId = person.PrimaryAliasId; historyRecord.Caption = "eRA"; historyRecord.Summary = "Entered eRA Status"; historyRecord.Verb = "ENTERED"; historyRecord.RelatedEntityTypeId = attributeEntityTypeId; historyRecord.RelatedEntityId = eraAttributeId; historyRecord.CategoryId = personAnalyticsCategoryId; } updateContext.SaveChanges(); } // launch entry workflow if (entryWorkflowType.HasValue) { LaunchWorkflow(entryWorkflowType.Value, family); } } } // update stats } // load giving attributes resultContext.Database.ExecuteSqlCommand("spCrm_FamilyAnalyticsGiving"); // load attendance attributes resultContext.Database.ExecuteSqlCommand("spCrm_FamilyAnalyticsAttendance"); // process history for group types if (!string.IsNullOrWhiteSpace(groupTypeList)) { string[] groupTypeGuids = groupTypeList.Split(','); var inactiveRecordValue = DefinedValueCache.Read(SystemGuid.DefinedValue.PERSON_RECORD_STATUS_INACTIVE); var groupTypeEntityTypeId = EntityTypeCache.Read("Rock.Model.GroupType").Id; foreach (var groupTypeGuid in groupTypeGuids) { var groupType = GroupTypeCache.Read(groupTypeGuid.AsGuid()); if (groupType != null) { // if the person is in a group of that type and the last history record for that group type isn't START write a start RockContext rockContext = new RockContext(); // get history for this group type var historyRecords = new HistoryService(rockContext).Queryable() .Where(h => h.EntityTypeId == personEntityTypeId && h.RelatedEntityTypeId == groupTypeEntityTypeId && h.RelatedEntityId == groupType.Id ) .GroupBy(h => h.EntityId) .Select(g => g.OrderByDescending(h => h.CreatedDateTime).Select(h => new { h.EntityId, h.Verb }).FirstOrDefault()) .ToList(); // get group member information var groupMemberInfo = new GroupMemberService(rockContext).Queryable() .Where(m => m.Group.GroupTypeId == groupType.Id && m.GroupMemberStatus == GroupMemberStatus.Active && m.Group.IsActive //&& m.Person.RecordStatusValueId != inactiveRecordValue.Id ) .GroupBy(m => m.PersonId) .Select(g => g.OrderBy(m => m.CreatedDateTime).Select(m => new { m.PersonId, m.CreatedDateTime, PersonAliasId = m.Person.Aliases.Select(p => p.Id).FirstOrDefault() }).FirstOrDefault()) .ToList(); var needsStartDate = groupMemberInfo.Where(m => !historyRecords.Any(h => h.EntityId == m.PersonId && h.Verb == "STARTED")); foreach (var startItem in needsStartDate) { using (RockContext updateContext = new RockContext()) { var historyService = new HistoryService(updateContext); History history = new History(); historyService.Add(history); history.EntityTypeId = personEntityTypeId; history.EntityId = startItem.PersonId; history.RelatedEntityTypeId = groupTypeEntityTypeId; history.RelatedEntityId = groupType.Id; history.Caption = groupType.Name; history.Summary = "Started Membership in Group Of Type"; history.Verb = "STARTED"; history.CreatedDateTime = startItem.CreatedDateTime; history.CreatedByPersonAliasId = startItem.PersonAliasId; history.CategoryId = personAnalyticsCategoryId; updateContext.SaveChanges(); } } var needsStoppedDate = historyRecords.Where(h => h.Verb == "STARTED" && !groupMemberInfo.Any(m => m.PersonId == h.EntityId)); foreach (var stopItem in needsStoppedDate) { using (RockContext updateContext = new RockContext()) { var person = new PersonService(updateContext).Get(stopItem.EntityId); if (person != null) { var historyService = new HistoryService(updateContext); History history = new History(); historyService.Add(history); history.EntityTypeId = personEntityTypeId; history.EntityId = person.Id; history.RelatedEntityTypeId = groupTypeEntityTypeId; history.RelatedEntityId = groupType.Id; history.Caption = groupType.Name; history.Summary = "Stopped Membership in Group Of Type"; history.Verb = "STOPPED"; history.CreatedDateTime = RockDateTime.Now; history.CreatedByPersonAliasId = person.PrimaryAliasId; history.CategoryId = personAnalyticsCategoryId; updateContext.SaveChanges(); } } } } } } // process visit dates if (updateVisitDates) { resultContext.Database.ExecuteSqlCommand("spCrm_FamilyAnalyticsUpdateVisitDates"); } }