예제 #1
0
        /// <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);
            }
        }
예제 #2
0
        /// <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);
            }
        }
예제 #4
0
            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);
            }
예제 #5
0
        /// <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();
                            }
                        }
                    }
                }
            }
        }
예제 #6
0
        /// <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);
        }
예제 #7
0
        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");
        }
예제 #8
0
        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);
                    }
                }
            }
        }
예제 #9
0
        /// <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");
        }
예제 #10
0
        /// <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);
        }
예제 #11
0
        /// <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);
                    }
                }
            }
        }
예제 #12
0
        /// <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();
                        }
                    }
                }
            }
        }
예제 #13
0
        /// <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;
            }
        }
예제 #15
0
파일: GroupSync.cs 프로젝트: waldo2590/Rock
        /// <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;
            }
        }
예제 #16
0
        /// <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;
            }
        }
예제 #17
0
        /// <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;
            }
        }
예제 #18
0
        /// <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);
        }
예제 #19
0
        /// <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");
            }
        }