/// <summary> /// Handles the SelectedIndexChanged event of the ddlGroup control to populate the ddlGroupRole control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> protected void ddlGroup_SelectedIndexChanged(object sender, EventArgs e) { ddlGroupRole.Items.Clear(); int?groupId = ddlGroup.SelectedValue.AsIntegerOrNull(); if (groupId == null) { return; } ddlGroupRole.Required = true; var rockContext = new RockContext(); var groupSyncService = new GroupSyncService(rockContext); var syncList = groupSyncService .Queryable() .AsNoTracking() .Where(s => s.GroupId == groupId) .Select(s => s.GroupTypeRoleId) .ToList(); nbModalDetailSyncMessage.Visible = syncList.Count > 0 ? true : false; var groupService = new GroupService(rockContext); var selectedGroup = groupService.GetNoTracking(groupId.Value); var groupTypeRoleService = new GroupTypeRoleService(rockContext); var qry = groupTypeRoleService .Queryable() .AsNoTracking() .Where(r => r.GroupTypeId == selectedGroup.GroupTypeId) .Where(r => !syncList.Contains(r.Id)) .ToList(); ddlGroupRole.DataSource = qry; ddlGroupRole.DataBind(); }
/// <summary> /// Shows the readonly details. /// </summary> /// <param name="dataView">The data view.</param> private void ShowReadonlyDetails(DataView dataView) { SetEditMode(false); hfDataViewId.SetValue(dataView.Id); lReadOnlyTitle.Text = dataView.Name.FormatAsHtmlTitle(); hlblDataViewId.Text = "Id: " + dataView.Id.ToString(); if (dataView.Id == default(int) || string.IsNullOrWhiteSpace(GetAttributeValue("ReportDetailPage"))) { lbViewCreateReport.Visible = false; } lDescription.Text = dataView.Description.ConvertMarkdownToHtml(); DescriptionList descriptionListMain = new DescriptionList(); if (dataView.EntityType != null) { descriptionListMain.Add("Applies To", dataView.EntityType.FriendlyName); } if (dataView.Category != null) { descriptionListMain.Add("Category", dataView.Category.Name); } if (dataView.TransformEntityType != null) { descriptionListMain.Add("Post-filter Transformation", dataView.TransformEntityType.FriendlyName); } lblMainDetails.Text = descriptionListMain.Html; DescriptionList descriptionListFilters = new DescriptionList(); if (dataView.DataViewFilter != null && dataView.EntityTypeId.HasValue) { var entityTypeCache = EntityTypeCache.Get(dataView.EntityTypeId.Value); if (entityTypeCache != null) { var entityTypeType = entityTypeCache.GetEntityType(); if (entityTypeType != null) { descriptionListFilters.Add("Filter", dataView.DataViewFilter.ToString(entityTypeType)); } } } lFilters.Text = descriptionListFilters.Html; DescriptionList descriptionListPersisted = new DescriptionList(); hlblPersisted.Visible = dataView.PersistedScheduleIntervalMinutes.HasValue && dataView.PersistedLastRefreshDateTime.HasValue; if (hlblPersisted.Visible) { hlblPersisted.Text = string.Format("Persisted {0}", dataView.PersistedLastRefreshDateTime.ToElapsedString()); } lPersisted.Text = descriptionListPersisted.Html; DescriptionList descriptionListDataviews = new DescriptionList(); var dataViewFilterEntityId = EntityTypeCache.Get(typeof(Rock.Reporting.DataFilter.OtherDataViewFilter)).Id; var rockContext = new RockContext(); DataViewService dataViewService = new DataViewService(rockContext); var dataViews = dataViewService.Queryable().AsNoTracking() .Where(d => d.DataViewFilter.ChildFilters .Any(f => f.Selection == dataView.Id.ToString() && f.EntityTypeId == dataViewFilterEntityId)) .OrderBy(d => d.Name); StringBuilder sbDataViews = new StringBuilder(); var dataViewDetailPage = GetAttributeValue("DataViewDetailPage"); foreach (var dataview in dataViews) { if (!string.IsNullOrWhiteSpace(dataViewDetailPage)) { sbDataViews.Append("<a href=\"" + LinkedPageUrl("DataViewDetailPage", new Dictionary <string, string>() { { "DataViewId", dataview.Id.ToString() } }) + "\">" + dataview.Name + "</a><br/>"); } else { sbDataViews.Append(dataview.Name + "<br/>"); } } descriptionListDataviews.Add("Data Views", sbDataViews); lDataViews.Text = descriptionListDataviews.Html; DescriptionList descriptionListReports = new DescriptionList(); StringBuilder sbReports = new StringBuilder(); ReportService reportService = new ReportService(rockContext); var reports = reportService.Queryable().AsNoTracking().Where(r => r.DataViewId == dataView.Id).OrderBy(r => r.Name); var reportDetailPage = GetAttributeValue("ReportDetailPage"); foreach (var report in reports) { if (!string.IsNullOrWhiteSpace(reportDetailPage)) { sbReports.Append("<a href=\"" + LinkedPageUrl("ReportDetailPage", new Dictionary <string, string>() { { "ReportId", report.Id.ToString() } }) + "\">" + report.Name + "</a><br/>"); } else { sbReports.Append(report.Name + "<br/>"); } } descriptionListReports.Add("Reports", sbReports); lReports.Text = descriptionListReports.Html; // Group-Roles using DataView in Group Sync DescriptionList descriptionListGroupSync = new DescriptionList(); StringBuilder sbGroups = new StringBuilder(); GroupSyncService groupSyncService = new GroupSyncService(rockContext); var groupSyncs = groupSyncService .Queryable() .Where(a => a.SyncDataViewId == dataView.Id) .ToList(); var groupDetailPage = GetAttributeValue("GroupDetailPage"); if (groupSyncs.Count() > 0) { foreach (var groupSync in groupSyncs) { string groupAndRole = string.Format("{0} - {1}", groupSync.Group.Name, groupSync.GroupTypeRole.Name); if (!string.IsNullOrWhiteSpace(groupDetailPage)) { sbGroups.Append("<a href=\"" + LinkedPageUrl("GroupDetailPage", new Dictionary <string, string>() { { "GroupId", groupSync.Group.Id.ToString() } }) + "\">" + groupAndRole + "</a><br/>"); } else { sbGroups.Append(string.Format("{0}<br/>", groupAndRole)); } } descriptionListGroupSync.Add("Groups", sbGroups); lGroups.Text = descriptionListGroupSync.Html; } ShowReport(dataView); }
/// <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"); // 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()) { activeSyncIds = new GroupSyncService(rockContext) .Queryable().AsNoTracking() .Select(x => x.Id) .ToList(); } foreach (var syncId in activeSyncIds) { bool hasSyncChanged = false; // Use a fresh rockContext per syc so that ChangeTracker doesn't get bogged down using (var rockContext = new RockContext()) { // increase the timeout just in case the dataview source is slow rockContext.Database.CommandTimeout = 180; 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 dataview 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 dataview (source) var personService = new PersonService(rockContext); var parameterExpression = personService.ParameterExpression; var whereExpression = sync.SyncDataView.GetExpression(personService, parameterExpression, out syncErrors); var sourcePersonIds = new PersonService(rockContext) .Get(parameterExpression, whereExpression) .Select(q => q.Id) .ToList(); // If any error occurred, just skip this sync 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) var targetPersonIds = new GroupMemberService(rockContext) .Queryable().AsNoTracking() .Where(gm => gm.GroupId == sync.GroupId) .Select(gm => gm.PersonId) .ToList(); // Delete people from the group/role that are no longer in the dataview foreach (var personId in targetPersonIds.Where(t => !sourcePersonIds.Contains(t))) { // Use a new context to limit the amount of change-tracking required using (var groupMemberContext = new RockContext()) { // Delete any group members for the role with the person id var groupMemberService = new GroupMemberService(groupMemberContext); foreach (var groupMember in groupMemberService .Queryable() .Where(m => m.GroupId == sync.GroupId && m.GroupRoleId == sync.GroupTypeRoleId && m.PersonId == 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(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); } } } hasSyncChanged = true; } foreach (var personId in sourcePersonIds.Where(s => !targetPersonIds.Contains(s))) { // Use a new context to limit the amount of change-tracking required using (var groupMemberContext = new RockContext()) { // Add new person to the group with the role specified in the sync var groupMemberService = new GroupMemberService(groupMemberContext); var newGroupMember = new GroupMember { Id = 0 }; newGroupMember.PersonId = personId; newGroupMember.GroupId = sync.GroupId; newGroupMember.GroupMemberStatus = GroupMemberStatus.Active; newGroupMember.GroupRoleId = sync.GroupTypeRoleId; groupMemberService.Add(newGroupMember); groupMemberContext.SaveChanges(); // 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 username. 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); } } } 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 sync'ed"; } else { resultMessage = string.Format("{0} groups were sync'ed", 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> /// 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> /// 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> /// Binds the repeater. /// </summary> protected void BindRepeater() { if (this.CurrentPersonId == null) { return; } int communicationListGroupTypeId = GroupTypeCache.Get(Rock.SystemGuid.GroupType.GROUPTYPE_COMMUNICATIONLIST.AsGuid()).Id; int?communicationListGroupTypeDefaultRoleId = GroupTypeCache.Get(communicationListGroupTypeId).DefaultGroupRoleId; var rockContext = new RockContext(); var memberOfList = new GroupMemberService(rockContext).GetByPersonId(CurrentPersonId.Value).AsNoTracking().Select(a => a.GroupId).ToList(); // Get a list of syncs for the communication list groups where the default role is sync'd AND the current person is NOT a member of // This is used to filter out the list of communication lists. var commGroupSyncsForDefaultRole = new GroupSyncService(rockContext) .Queryable() .Where(a => a.Group.GroupTypeId == communicationListGroupTypeId) .Where(a => a.GroupTypeRoleId == communicationListGroupTypeDefaultRoleId) .Where(a => !memberOfList.Contains(a.GroupId)) .Select(a => a.GroupId) .ToList(); var communicationLists = new GroupService(rockContext) .Queryable() .Where(a => a.GroupTypeId == communicationListGroupTypeId && !commGroupSyncsForDefaultRole.Contains(a.Id)) .ToList(); var categoryGuids = this.GetAttributeValue("CommunicationListCategories").SplitDelimitedValues().AsGuidList(); var viewableCommunicationLists = new List <Group>(); foreach (var communicationList in communicationLists) { communicationList.LoadAttributes(rockContext); if (!categoryGuids.Any()) { // if no categories where specified, only show lists that the person has VIEW auth if (communicationList.IsAuthorized(Rock.Security.Authorization.VIEW, this.CurrentPerson)) { viewableCommunicationLists.Add(communicationList); } } else { Guid?categoryGuid = communicationList.GetAttributeValue("Category").AsGuidOrNull(); if (categoryGuid.HasValue && categoryGuids.Contains(categoryGuid.Value)) { viewableCommunicationLists.Add(communicationList); } } } viewableCommunicationLists = viewableCommunicationLists.OrderBy(a => { var name = a.GetAttributeValue("PublicName"); if (name.IsNullOrWhiteSpace()) { name = a.Name; } return(name); }).ToList(); var groupIds = viewableCommunicationLists.Select(a => a.Id).ToList(); var personId = this.CurrentPersonId.Value; showMediumPreference = this.GetAttributeValue("ShowMediumPreference").AsBoolean(); personCommunicationListsMember = new GroupMemberService(rockContext) .Queryable() .AsNoTracking() .Where(a => groupIds.Contains(a.GroupId) && a.PersonId == personId) .GroupBy(a => a.GroupId) .ToList() .ToDictionary(k => k.Key, v => v.FirstOrDefault()); rptCommunicationLists.DataSource = viewableCommunicationLists; rptCommunicationLists.DataBind(); nbNoCommunicationLists.Visible = !viewableCommunicationLists.Any(); pnlCommunicationPreferences.Visible = viewableCommunicationLists.Any(); }
/// <summary> /// Gets the subscription options for the current person. /// </summary> /// <returns>A collection of <see cref="Subscription"/> objects.</returns> protected virtual IEnumerable <Subscription> GetSubscriptions() { int communicationListGroupTypeId = GroupTypeCache.Get(Rock.SystemGuid.GroupType.GROUPTYPE_COMMUNICATIONLIST.AsGuid()).Id; int?communicationListGroupTypeDefaultRoleId = GroupTypeCache.Get(communicationListGroupTypeId).DefaultGroupRoleId; if (RequestContext.CurrentPerson == null) { return(new List <Subscription>()); } var rockContext = new RockContext(); // // Get all the lists this person is already a member of. // var memberOfList = new GroupMemberService(rockContext) .GetByPersonId(RequestContext.CurrentPerson.Id) .AsNoTracking() .Where(a => a.Group.GroupTypeId == communicationListGroupTypeId) .Select(a => a.GroupId) .ToList(); // // Get a list of syncs for the communication list groups // where the default role is sync'd AND the current person // is NOT a member of. // This is used to filter out the list of communication lists. // var commGroupSyncsForDefaultRole = new GroupSyncService(rockContext) .Queryable() .Where(a => a.Group.GroupTypeId == communicationListGroupTypeId) .Where(a => a.GroupTypeRoleId == communicationListGroupTypeDefaultRoleId) .Where(a => !memberOfList.Contains(a.GroupId)) .Select(a => a.GroupId) .ToList(); // // Get all the communication lists, excluding those from the above // query about synced groups. // var communicationLists = new GroupService(rockContext) .Queryable() .Where(a => a.GroupTypeId == communicationListGroupTypeId) .Where(a => !commGroupSyncsForDefaultRole.Contains(a.Id)) .ToList(); var categoryGuids = CommunicationListCategories; var viewableCommunicationLists = communicationLists .Where(a => { a.LoadAttributes(rockContext); if (!categoryGuids.Any()) { // // If no categories where specified, only show // lists that the person has VIEW auth to. // if (a.IsAuthorized(Rock.Security.Authorization.VIEW, RequestContext.CurrentPerson)) { return(true); } } else { // // If categories were specified, ensure that this // communication list has a category and is one of // the specified categories. // Guid?categoryGuid = a.GetAttributeValue("Category").AsGuidOrNull(); if (categoryGuid.HasValue && categoryGuids.Contains(categoryGuid.Value)) { return(true); } } return(false); }) .ToList(); var groupIds = viewableCommunicationLists.Select(a => a.Id).ToList(); var personId = RequestContext.CurrentPerson.Id; var communicationListsMember = new GroupMemberService(rockContext) .Queryable() .AsNoTracking() .Where(a => groupIds.Contains(a.GroupId) && a.PersonId == personId) .GroupBy(a => a.GroupId) .ToList() .ToDictionary(k => k.Key, v => v.FirstOrDefault()); return(viewableCommunicationLists .Select(a => { var publicName = a.GetAttributeValue("PublicName"); return new Subscription { DisplayName = publicName.IsNotNullOrWhiteSpace() ? publicName : a.Name, CommunicationList = a, Member = communicationListsMember.GetValueOrDefault(a.Id, null) }; }) .OrderBy(a => a.DisplayName) .ToList()); }