/// <summary> /// Validates the JWT using the provided configuration and returns the parsed token if valid /// </summary> /// <param name="jwtString">The token.</param> /// <param name="jwtConfig">The configuration on how to validate the token</param> /// <returns></returns> private static JwtSecurityToken ValidateToken(JwtConfig jwtConfig, string jwtString) { if (jwtString.IsNullOrWhiteSpace()) { return(null); } if (jwtConfig == null) { return(null); } // It is standard to prefix JWTs with "Bearer ", but JwtSecurityTokenHandler.ValidateToken will // say the token is malformed if the prefix is not removed if (jwtString.StartsWith(HeaderTokens.JwtPrefix)) { jwtString = jwtString.Substring(HeaderTokens.JwtPrefix.Length); } // Retrieve the configuration manager, which is cached according to the jwtConfig.JwksJsonFileUrl var configurationManager = GetConfigurationManager(jwtConfig.OpenIdConfigurationUrl); if (configurationManager == null) { return(null); } // The configuration manager handles caching the configuration documents and keys, which are from another // server or provider like Auth0. var openIdConnectConfiguration = AsyncHelper.RunSync(() => configurationManager.GetConfigurationAsync()); if (openIdConnectConfiguration == null || openIdConnectConfiguration.SigningKeys == null || !openIdConnectConfiguration.SigningKeys.Any()) { return(null); } // Validate the items that are configured to be validated var validateAudience = !jwtConfig.Audience.IsNullOrWhiteSpace(); var validateIssuer = !jwtConfig.Issuer.IsNullOrWhiteSpace(); var validationParameters = new TokenValidationParameters { ValidateAudience = validateAudience, ValidAudience = validateAudience ? jwtConfig.Audience : null, ValidateIssuer = validateIssuer, ValidIssuer = validateIssuer ? jwtConfig.Issuer : null, RequireExpirationTime = true, RequireSignedTokens = true, ValidateIssuerSigningKey = true, IssuerSigningKeys = openIdConnectConfiguration.SigningKeys, ValidateLifetime = true, ClockSkew = TimeSpan.FromMinutes(1) // Allow a minute of play in server times since we're dealing with a third party key provider }; try { var principal = new JwtSecurityTokenHandler().ValidateToken(jwtString, validationParameters, out var validatedToken); // If the principal identity is null, we should not accept this as a validated token if (principal == null || principal.Identity == null) { return(null); } var jwtToken = validatedToken as JwtSecurityToken; return(jwtToken); } catch (Exception ex) { // The JWT was not well formed or did not validate in some other way ExceptionLogService.LogException(ex); Debug.WriteLine(ex.Message); return(null); } }
/// <summary> /// Handles the Click event of the btnDefault control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> protected void btnDefault_Click(object sender, EventArgs e) { var bioBlock = BlockCache.Get(Rock.SystemGuid.Block.BIO.AsGuid()); // Record an exception if the stock Bio block has been deleted but continue processing // the remaining settings. if (bioBlock == null) { var errorMessage = string.Format("Stock Bio block ({0}) is missing.", Rock.SystemGuid.Block.BIO); ExceptionLogService.LogException(new Exception(errorMessage)); } else { List <Guid> workflowActionGuidList = bioBlock.GetAttributeValues("WorkflowActions").AsGuidList(); if (workflowActionGuidList == null || workflowActionGuidList.Count == 0) { // Add to Bio Workflow Actions bioBlock.SetAttributeValue("WorkflowActions", Rock.SystemGuid.WorkflowType.PROTECTMYMINISTRY); ///BackgroundCheckContainer.Instance.Components } else { //var workflowActionValues = workflowActionValue.Split( new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries ).ToList(); Guid guid = Rock.SystemGuid.WorkflowType.PROTECTMYMINISTRY.AsGuid(); if (!workflowActionGuidList.Any(w => w == guid)) { // Add Checkr to Bio Workflow Actions workflowActionGuidList.Add(guid); } // Remove PMM from Bio Workflow Actions guid = CheckrSystemGuid.CHECKR_WORKFLOW_TYPE.AsGuid(); workflowActionGuidList.RemoveAll(w => w == guid); bioBlock.SetAttributeValue("WorkflowActions", workflowActionGuidList.AsDelimited(",")); } bioBlock.SaveAttributeValue("WorkflowActions"); } string pmmTypeName = (typeof(Rock.Security.BackgroundCheck.ProtectMyMinistry)).FullName; var pmmComponent = BackgroundCheckContainer.Instance.Components.Values.FirstOrDefault(c => c.Value.TypeName == pmmTypeName); pmmComponent.Value.SetAttributeValue("Active", "True"); pmmComponent.Value.SaveAttributeValue("Active"); // Set as the default provider in the system setting SystemSettings.SetValue(Rock.SystemKey.SystemSetting.DEFAULT_BACKGROUND_CHECK_PROVIDER, pmmTypeName); using (var rockContext = new RockContext()) { WorkflowTypeService workflowTypeService = new WorkflowTypeService(rockContext); // Rename PMM Workflow var pmmWorkflowAction = workflowTypeService.Get(Rock.SystemGuid.WorkflowType.PROTECTMYMINISTRY.AsGuid()); pmmWorkflowAction.Name = "Background Check"; var checkrWorkflowAction = workflowTypeService.Get(CheckrSystemGuid.CHECKR_WORKFLOW_TYPE.AsGuid()); // Rename Checkr Workflow checkrWorkflowAction.Name = CheckrConstants.CHECKR_WORKFLOW_TYPE_NAME; rockContext.SaveChanges(); // Enable PMM packages and disable Checkr packages DefinedValueService definedValueService = new DefinedValueService(rockContext); var packages = definedValueService .GetByDefinedTypeGuid(Rock.SystemGuid.DefinedType.BACKGROUND_CHECK_TYPES.AsGuid()) .ToList(); foreach (var package in packages) { package.IsActive = package.ForeignId == 1; } rockContext.SaveChanges(); } ShowDetail(); }
/// <summary> /// Provides an end method for an asynchronous process. /// </summary> /// <param name="result">An IAsyncResult that contains information about the status of the process.</param> public void EndProcessRequest(IAsyncResult result) { // restore the context from the asyncResult.AsyncState HttpContext context = (HttpContext)result.AsyncState; try { context.Response.Clear(); var rockContext = new RockContext(); bool requiresViewSecurity = false; BinaryFile binaryFile = null; try { binaryFile = new BinaryFileService(rockContext).EndGet(result, context, out requiresViewSecurity); } catch (Exception ex) { // Ignore Error (Will fall though to 404) } if (binaryFile != null) { //// if the binaryFile's BinaryFileType requires security, check security //// note: we put a RequiresViewSecurity flag on BinaryFileType because checking security for every file would be slow (~40ms+ per request) if (requiresViewSecurity) { var currentUser = new UserLoginService(rockContext).GetByUserName(UserLogin.GetCurrentUserName()); Person currentPerson = currentUser != null ? currentUser.Person : null; binaryFile.BinaryFileType = binaryFile.BinaryFileType ?? new BinaryFileTypeService(rockContext).Get(binaryFile.BinaryFileTypeId.Value); if (!binaryFile.IsAuthorized(Authorization.VIEW, currentPerson)) { SendNotAuthorized(context); return; } } SendFile(context, binaryFile.ContentStream, binaryFile.MimeType, binaryFile.FileName, binaryFile.Guid.ToString("N")); return; } context.Response.StatusCode = 404; context.Response.StatusDescription = "Unable to find the requested file."; } catch (Exception ex) { ExceptionLogService.LogException(ex, context); try { context.Response.StatusCode = 500; context.Response.StatusDescription = ex.Message; context.Response.Flush(); context.ApplicationInstance.CompleteRequest(); } catch (Exception ex2) { ExceptionLogService.LogException(ex2, context); } } }
/// <summary> /// Job that updates the JobPulse setting with the current date/time. /// This will allow us to notify an admin if the jobs stop running. /// /// 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 map JobDataMap dataMap = context.JobDetail.JobDataMap; // delete accounts that have not been confirmed in X hours int userExpireHours = Int32.Parse( dataMap.GetString( "HoursKeepUnconfirmedAccounts" ) ); DateTime userAccountExpireDate = DateTime.Now.Add( new TimeSpan( userExpireHours * -1,0,0 ) ); UserService userService = new UserService(); foreach (var user in userService.Queryable().Where(u => u.IsConfirmed == false && u.CreationDate < userAccountExpireDate)) { userService.Delete( user, null ); } userService.Save( null, null ); // purge exception log int exceptionExpireDays = Int32.Parse( dataMap.GetString( "DaysKeepExceptions" ) ); DateTime exceptionExpireDate = DateTime.Now.Add( new TimeSpan( userExpireHours * -1, 0, 0 ) ); ExceptionLogService exceptionLogService = new ExceptionLogService(); foreach ( var exception in exceptionLogService.Queryable().Where( e => e.ExceptionDate < exceptionExpireDate ) ) { exceptionLogService.Delete( exception, null ); } exceptionLogService.Save( null, null ); }
/// <summary> /// Executes the specified context. /// </summary> /// <param name="context">The context.</param> public virtual void Execute(IJobExecutionContext context) { JobDataMap dataMap = context.JobDetail.JobDataMap; var emailTemplateGuid = dataMap.Get("SystemEmail").ToString().AsGuid(); var groupGuid = dataMap.Get("Group").ToString().AsGuid(); var sendToDescendants = dataMap.Get("SendToDescendantGroups").ToString().AsBoolean(); var rockContext = new RockContext(); var group = new GroupService(rockContext).Get(groupGuid); if (group != null) { List <int> groupIds = new List <int>(); GetGroupIds(groupIds, sendToDescendants, group); var recipients = new List <RecipientData>(); var groupMemberList = new GroupMemberService(rockContext).Queryable().Where(gm => groupIds.Contains(gm.GroupId) && gm.GroupMemberStatus == GroupMemberStatus.Active) .ToList(); foreach (GroupMember groupMember in groupMemberList) { var person = groupMember.Person; if (!person.IsEmailActive || person.Email.IsNullOrWhiteSpace() || person.EmailPreference == EmailPreference.DoNotEmail) { continue; } var mergeFields = Lava.LavaHelper.GetCommonMergeFields(null); mergeFields.Add("Person", person); mergeFields.Add("GroupMember", groupMember); mergeFields.Add("Group", groupMember.Group); recipients.Add(new RecipientData(groupMember.Person.Email, mergeFields)); } var errors = new List <string>(); if (recipients.Any()) { var emailMessage = new RockEmailMessage(emailTemplateGuid); emailMessage.SetRecipients(recipients); emailMessage.Send(out errors); } context.Result = string.Format("{0} emails sent", recipients.Count()); if (errors.Any()) { StringBuilder sb = new StringBuilder(); sb.AppendLine(); sb.Append(string.Format("{0} Errors: ", errors.Count())); errors.ForEach(e => { sb.AppendLine(); sb.Append(e); }); string errorMessage = sb.ToString(); context.Result += errorMessage; var exception = new Exception(errorMessage); HttpContext context2 = HttpContext.Current; ExceptionLogService.LogException(exception, context2); throw exception; } } }
/// <summary> /// Gets the objects in folder without recursion. i.e. will get the list of files /// and folders in the folder but not the contents of the subfolders. Subfolders /// will not have the ModifiedDate prop filled in as Amazon doesn't provide it in /// this context. /// </summary> /// <param name="assetStorageProvider">The asset storage Provider.</param> /// <param name="asset">The asset.</param> /// <returns></returns> public override List <Asset> ListObjectsInFolder(AssetStorageProvider assetStorageProvider, Asset asset) { string rootFolder = FixRootFolder(GetAttributeValue(assetStorageProvider, AttributeKeys.RootFolder)); string bucketName = GetAttributeValue(assetStorageProvider, AttributeKeys.Bucket); asset.Key = FixKey(asset, rootFolder); HasRequirementsFolder(asset); try { AmazonS3Client client = GetAmazonS3Client(assetStorageProvider); ListObjectsV2Request request = new ListObjectsV2Request(); request.BucketName = bucketName; request.Prefix = asset.Key == "/" ? rootFolder : asset.Key; request.Delimiter = "/"; var assets = new List <Asset>(); var subFolders = new HashSet <string>(); ListObjectsV2Response response; // S3 will only return 1,000 keys per response and sets IsTruncated = true, the do-while loop will run and fetch keys until IsTruncated = false. do { response = client.ListObjectsV2(request); foreach (S3Object s3Object in response.S3Objects) { if (s3Object.Key == null) { continue; } var responseAsset = CreateAssetFromS3Object(assetStorageProvider, s3Object, client.Config.RegionEndpoint.SystemName); assets.Add(responseAsset); } // After setting the delimiter S3 will filter out any prefixes below that in response.S3Objects. // So we need to inspect response.CommonPrefixes to get the prefixes inside the folder. foreach (string subFolder in response.CommonPrefixes) { if (subFolder.IsNotNullOrWhiteSpace()) { subFolders.Add(subFolder); } } request.ContinuationToken = response.NextContinuationToken; } while (response.IsTruncated); // Add the subfolders to the asset collection foreach (string subFolder in subFolders) { var subFolderAsset = CreateAssetFromCommonPrefix(subFolder, client.Config.RegionEndpoint.SystemName, bucketName); assets.Add(subFolderAsset); } return(assets.OrderBy(a => a.Key, StringComparer.OrdinalIgnoreCase).ToList()); } catch (Exception ex) { ExceptionLogService.LogException(ex); throw; } }
/// <summary> /// Trigger Future Followup Workflow /// </summary> /// <param name="context">The context.</param> /// <param name="futureFollowupDateWorkflows">The future follow-up date workflows.</param> /// <returns></returns> private string TriggerFutureFollowupWorkFlow(IJobExecutionContext context, List <ConnectionWorkflow> futureFollowupDateWorkflows) { try { JobDataMap dataMap = context.JobDetail.JobDataMap; context.UpdateLastStatusMessage($"Processing future follow-up workFlows."); int recordsUpdated = 0; int triggerWorkflow = 0; int recordsWithError = 0; if (futureFollowupDateWorkflows.Any()) { var rockContext = new RockContext(); var connectionOpportunityIds = futureFollowupDateWorkflows .Where(a => a.ConnectionOpportunityId.HasValue) .Select(a => a.ConnectionOpportunityId.Value) .ToList(); var connectionTypeIds = futureFollowupDateWorkflows .Where(a => a.ConnectionTypeId.HasValue) .Select(a => a.ConnectionTypeId.Value) .ToList(); var allConnectionOpportunities = new List <ConnectionOpportunity>(); if (connectionOpportunityIds.Any() || connectionTypeIds.Any()) { var relatedConnectionOpportunities = new ConnectionOpportunityService(rockContext) .Queryable() .AsNoTracking() .Where(o => connectionOpportunityIds.Contains(o.Id) || connectionTypeIds.Contains(o.ConnectionTypeId)) .ToList(); allConnectionOpportunities.AddRange(relatedConnectionOpportunities); } if (allConnectionOpportunities.Any()) { connectionOpportunityIds = allConnectionOpportunities.Select(a => a.Id).ToList(); var numberOfDaysToLookBack = dataMap.GetString(AttributeKeys.NumberOfDaysToLookBack).AsInteger(); DateTime midnightToday = RockDateTime.Today.AddDays(1); DateTime startDate = RockDateTime.Today.AddDays(-numberOfDaysToLookBack); var connectionRequestService = new ConnectionRequestService(rockContext); var eligibleConnectionRequests = connectionRequestService .Queryable("ConnectionRequestWorkflows") .AsNoTracking() .Where(c => connectionOpportunityIds.Contains(c.ConnectionOpportunityId) && c.ConnectionState == ConnectionState.FutureFollowUp && c.FollowupDate.HasValue && c.FollowupDate >= startDate && c.FollowupDate < midnightToday ) .ToList(); foreach (var connectionRequest in eligibleConnectionRequests) { try { using (var updateRockContext = new RockContext()) { // increase the timeout just in case. updateRockContext.Database.CommandTimeout = 180; updateRockContext.SourceOfChange = SOURCE_OF_CHANGE; var connectionOpportunity = allConnectionOpportunities.SingleOrDefault(a => a.Id == connectionRequest.ConnectionOpportunityId); if (connectionOpportunity != null) { var opportunityWorkflows = futureFollowupDateWorkflows .Where(w => (w.ConnectionOpportunityId.HasValue && w.ConnectionOpportunityId.Value == connectionOpportunity.Id) || (w.ConnectionTypeId.HasValue && w.ConnectionTypeId.Value == connectionOpportunity.ConnectionTypeId)); foreach (var connectionWorkflow in opportunityWorkflows .Where(a => !connectionRequest.ConnectionRequestWorkflows.Any(b => b.ConnectionWorkflowId == a.Id))) { LaunchWorkflow(updateRockContext, connectionRequest, connectionWorkflow, ConnectionWorkflowTriggerType.FutureFollowupDateReached.ConvertToString()); triggerWorkflow += 1; } updateRockContext.ConnectionRequests.Attach(connectionRequest); connectionRequest.ConnectionState = ConnectionState.Active; updateRockContext.SaveChanges(); recordsUpdated += 1; } } } catch (Exception ex) { // Log exception and keep on trucking. ExceptionLogService.LogException(new Exception($"Exception occurred trying to trigger future followup workFlow:{connectionRequest.Id}.", ex), _httpContext); recordsWithError += 1; } } } } // Format the result message string result = $"{recordsUpdated:N0} connection request records triggered {triggerWorkflow} workflows."; if (recordsWithError > 0) { result += $"{recordsWithError:N0} records logged an exception."; } return(result); } catch (Exception ex) { // Log exception and return the exception messages. ExceptionLogService.LogException(ex, _httpContext); return(ex.Messages().AsDelimited("; ")); } }
/// <summary> /// Executes the specified context. /// </summary> /// <param name="context">The context.</param> public virtual void Execute(IJobExecutionContext context) { JobDataMap dataMap = context.JobDetail.JobDataMap; int expirationDays = dataMap.GetInt("ExpirationPeriod"); int delayMinutes = dataMap.GetInt("DelayPeriod"); int maxParallelization = dataMap.GetInt("ParallelCommunications"); List <Model.Communication> sendCommunications = null; var stopWatch = Stopwatch.StartNew(); using (var rockContext = new RockContext()) { sendCommunications = new CommunicationService(rockContext) .GetQueued(expirationDays, delayMinutes, false, false) .AsNoTracking() .ToList() .OrderBy(c => c.Id) .ToList(); } RockLogger.Log.Information(RockLogDomains.Jobs, "{0}: Queued communication query runtime: {1} ms", nameof(SendCommunications), stopWatch.ElapsedMilliseconds); if (sendCommunications == null) { context.Result = "No communications to send"; } var exceptionMsgs = new List <string>(); int communicationsSent = 0; stopWatch = Stopwatch.StartNew(); var sendCommunicationTasks = new List <Task <SendCommunicationAsyncResult> >(); RockLogger.Log.Debug(RockLogDomains.Jobs, "{0}: Send communications {1} communications.", nameof(SendCommunications), sendCommunicationTasks.Count); using (var mutex = new SemaphoreSlim(maxParallelization)) { for (var i = 0; i < sendCommunications.Count(); i++) { mutex.Wait(); var comm = sendCommunications[i]; sendCommunicationTasks.Add(Task.Run <SendCommunicationAsyncResult>(async() => await SendCommunicationAsync(comm, mutex).ConfigureAwait(false))); } /* * Now that we have fired off all of the task, we need to wait for them to complete, get their results, * and then process that result. Once all of the task have been completed we can continue. */ while (sendCommunicationTasks.Count > 0) { // Wait for a task to complete using WhenAny and then return the completed task. Since we are not running in an asynchronous method we need to use RunSync. var completedTask = AsyncHelper.RunSync(() => Task.WhenAny <SendCommunicationAsyncResult>(sendCommunicationTasks.ToArray())); // Get and process the result of the completed task. var communicationResult = completedTask.Result; if (communicationResult.Exception != null) { var agException = communicationResult.Exception as AggregateException; if (agException == null) { exceptionMsgs.Add($"Exception occurred sending communication ID:{communicationResult.Communication.Id}:{Environment.NewLine} {communicationResult.Exception.Messages().AsDelimited( Environment.NewLine + " " )}"); } else { var allExceptions = agException.Flatten(); foreach (var ex in allExceptions.InnerExceptions) { exceptionMsgs.Add($"Exception occurred sending communication ID:{communicationResult.Communication.Id}:{Environment.NewLine} {ex.Messages().AsDelimited( Environment.NewLine + " " )}"); } } ExceptionLogService.LogException(communicationResult.Exception, System.Web.HttpContext.Current); } else { communicationsSent++; } sendCommunicationTasks.Remove(completedTask); } } RockLogger.Log.Information(RockLogDomains.Jobs, "{0}: Send communications runtime: {1} ms", nameof(SendCommunications), stopWatch.ElapsedMilliseconds); if (communicationsSent > 0) { context.Result = string.Format("Sent {0} {1}", communicationsSent, "communication".PluralizeIf(communicationsSent > 1)); } else { context.Result = "No communications to send"; } if (exceptionMsgs.Any()) { throw new Exception("One or more exceptions occurred sending communications..." + Environment.NewLine + exceptionMsgs.AsDelimited(Environment.NewLine)); } // check for communications that have not been sent but are past the expire date. Mark them as failed and set a warning. var expireDateTimeEndWindow = RockDateTime.Now.AddDays(0 - expirationDays); // limit the query to only look a week prior to the window to avoid performance issue (it could be slow to query at ALL the communication recipient before the expire date, as there could several years worth ) var expireDateTimeBeginWindow = expireDateTimeEndWindow.AddDays(-7); stopWatch = Stopwatch.StartNew(); using (var rockContext = new RockContext()) { var qryExpiredRecipients = new CommunicationRecipientService(rockContext).Queryable() .Where(cr => cr.Communication.Status == CommunicationStatus.Approved && cr.Status == CommunicationRecipientStatus.Pending && ( (!cr.Communication.FutureSendDateTime.HasValue && cr.Communication.ReviewedDateTime.HasValue && cr.Communication.ReviewedDateTime <expireDateTimeEndWindow && cr.Communication.ReviewedDateTime> expireDateTimeBeginWindow) || (cr.Communication.FutureSendDateTime.HasValue && cr.Communication.FutureSendDateTime <expireDateTimeEndWindow && cr.Communication.FutureSendDateTime> expireDateTimeBeginWindow) )); rockContext.BulkUpdate(qryExpiredRecipients, c => new CommunicationRecipient { Status = CommunicationRecipientStatus.Failed, StatusNote = "Communication was not sent before the expire window (possibly due to delayed approval)." }); } RockLogger.Log.Information(RockLogDomains.Jobs, "{0}: Query expired communications runtime: {1} ms", nameof(SendCommunications), stopWatch.ElapsedMilliseconds); }
/// <summary> /// Uses Lava to resolve any merge codes within the content using the values in the merge objects. /// </summary> /// <param name="content">The content.</param> /// <param name="mergeObjects">The merge objects.</param> /// <param name="enabledLavaCommands">The enabled lava commands.</param> /// <param name="encodeStrings">if set to <c>true</c> [encode strings].</param> /// <param name="throwExceptionOnErrors">if set to <c>true</c> [throw exception on errors].</param> /// <returns></returns> public static string ResolveMergeFields(this string content, IDictionary <string, object> mergeObjects, string enabledLavaCommands, bool encodeStrings = false, bool throwExceptionOnErrors = false) { try { if (!content.HasMergeFields()) { return(content ?? string.Empty); } if (GlobalAttributesCache.Get().LavaSupportLevel == Lava.LavaSupportLevel.LegacyWithWarning && mergeObjects.ContainsKey("GlobalAttribute")) { if (hasLegacyGlobalAttributeLavaMergeFields.IsMatch(content)) { Rock.Model.ExceptionLogService.LogException(new Rock.Lava.LegacyLavaSyntaxDetectedException("GlobalAttribute", ""), System.Web.HttpContext.Current); } } Template template = GetTemplate(content); template.Registers.AddOrReplace("EnabledCommands", enabledLavaCommands); string result; if (encodeStrings) { // if encodeStrings = true, we want any string values to be XML Encoded ( RenderParameters renderParameters = new RenderParameters(); renderParameters.LocalVariables = Hash.FromDictionary(mergeObjects); renderParameters.ValueTypeTransformers = new Dictionary <Type, Func <object, object> >(); renderParameters.ValueTypeTransformers[typeof(string)] = EncodeStringTransformer; result = template.Render(renderParameters); } else { result = template.Render(Hash.FromDictionary(mergeObjects)); } if (throwExceptionOnErrors && template.Errors.Any()) { if (template.Errors.Count == 1) { throw template.Errors[0]; } else { throw new AggregateException(template.Errors); } } return(result); } catch (System.Threading.ThreadAbortException) { // Do nothing...it's just a Lava PageRedirect that just happened. return(string.Empty); } catch (Exception ex) { if (throwExceptionOnErrors) { throw; } else { ExceptionLogService.LogException(ex, System.Web.HttpContext.Current); return("Error resolving Lava merge fields: " + ex.Message); } } }
/// <summary> /// Shows the error message. /// </summary> /// <param name="ex">The ex.</param> /// <param name="friendlyMessage">The friendly message.</param> private void ShowErrorMessage(Exception ex, string friendlyMessage) { nbErrorMessage.Text = friendlyMessage; nbErrorMessage.Visible = true; ExceptionLogService.LogException(ex, this.Context); }
/// <summary> /// Sends the specified rock message. /// </summary> /// <param name="rockMessage">The rock message.</param> /// <param name="mediumEntityTypeId">The medium entity type identifier.</param> /// <param name="mediumAttributes">The medium attributes.</param> /// <param name="errorMessages">The error messages.</param> /// <returns></returns> public override bool Send(RockMessage rockMessage, int mediumEntityTypeId, Dictionary <string, string> mediumAttributes, out List <string> errorMessages) { errorMessages = new List <string>(); var smsMessage = rockMessage as RockSMSMessage; if (smsMessage != null) { // Validate From Number if (smsMessage.FromNumber == null) { errorMessages.Add("A From Number was not provided."); return(false); } string accountSid = GetAttributeValue("SID"); string authToken = GetAttributeValue("Token"); TwilioClient.Init(accountSid, authToken); // Common Merge Field var mergeFields = Lava.LavaHelper.GetCommonMergeFields(null, rockMessage.CurrentPerson); foreach (var mergeField in rockMessage.AdditionalMergeFields) { mergeFields.AddOrReplace(mergeField.Key, mergeField.Value); } int?throttlingWaitTimeMS = null; if (this.IsLongCodePhoneNumber(smsMessage.FromNumber.Value)) { throttlingWaitTimeMS = this.GetAttributeValue("Long-CodeThrottling").AsIntegerOrNull(); } List <Uri> attachmentMediaUrls = GetAttachmentMediaUrls(rockMessage.Attachments.AsQueryable()); foreach (var recipientData in rockMessage.GetRecipientData()) { try { foreach (var mergeField in mergeFields) { recipientData.MergeFields.AddOrIgnore(mergeField.Key, mergeField.Value); } string message = ResolveText(smsMessage.Message, smsMessage.CurrentPerson, smsMessage.EnabledLavaCommands, recipientData.MergeFields, smsMessage.AppRoot, smsMessage.ThemeRoot); MessageResource response = SendToTwilio(smsMessage.FromNumber.Value, null, attachmentMediaUrls, message, recipientData.To); if (response.ErrorMessage.IsNotNullOrWhitespace()) { errorMessages.Add(response.ErrorMessage); } } catch (Exception ex) { errorMessages.Add(ex.Message); ExceptionLogService.LogException(ex); } if (throttlingWaitTimeMS.HasValue) { System.Threading.Thread.Sleep(throttlingWaitTimeMS.Value); } } } return(!errorMessages.Any()); }
/// <summary> /// Sends the specified communication. /// </summary> /// <param name="communication">The communication.</param> /// <param name="mediumEntityTypeId">The medium entity type identifier.</param> /// <param name="mediumAttributes">The medium attributes.</param> public override void Send(Model.Communication communication, int mediumEntityTypeId, Dictionary <string, string> mediumAttributes) { using (var communicationRockContext = new RockContext()) { // Requery the Communication communication = new CommunicationService(communicationRockContext).Get(communication.Id); bool hasPendingRecipients; if (communication != null && communication.Status == Model.CommunicationStatus.Approved && (!communication.FutureSendDateTime.HasValue || communication.FutureSendDateTime.Value.CompareTo(RockDateTime.Now) <= 0)) { var qryRecipients = new CommunicationRecipientService(communicationRockContext).Queryable(); hasPendingRecipients = qryRecipients .Where(r => r.CommunicationId == communication.Id && r.Status == Model.CommunicationRecipientStatus.Pending && r.MediumEntityTypeId.HasValue && r.MediumEntityTypeId.Value == mediumEntityTypeId) .Any(); } else { hasPendingRecipients = false; } if (hasPendingRecipients) { var currentPerson = communication.CreatedByPersonAlias?.Person; var globalAttributes = Rock.Web.Cache.GlobalAttributesCache.Read(); string publicAppRoot = globalAttributes.GetValue("PublicApplicationRoot").EnsureTrailingForwardslash(); var mergeFields = Rock.Lava.LavaHelper.GetCommonMergeFields(null, currentPerson); string fromPhone = communication.SMSFromDefinedValue?.Value; if (string.IsNullOrWhiteSpace(fromPhone)) { // just in case we got this far without a From Number, throw an exception throw new Exception("A From Number was not provided for communication: " + communication.Id.ToString()); } if (!string.IsNullOrWhiteSpace(fromPhone)) { int?throttlingWaitTimeMS = null; if (this.IsLongCodePhoneNumber(fromPhone)) { throttlingWaitTimeMS = this.GetAttributeValue("Long-CodeThrottling").AsIntegerOrNull(); } string accountSid = GetAttributeValue("SID"); string authToken = GetAttributeValue("Token"); TwilioClient.Init(accountSid, authToken); var personEntityTypeId = EntityTypeCache.Read("Rock.Model.Person").Id; var communicationEntityTypeId = EntityTypeCache.Read("Rock.Model.Communication").Id; var communicationCategoryId = CategoryCache.Read(Rock.SystemGuid.Category.HISTORY_PERSON_COMMUNICATIONS.AsGuid(), communicationRockContext).Id; string callbackUrl = publicAppRoot + "Webhooks/Twilio.ashx"; var smsAttachmentsBinaryFileIdList = communication.GetAttachmentBinaryFileIds(CommunicationType.SMS); List <Uri> attachmentMediaUrls = new List <Uri>(); if (smsAttachmentsBinaryFileIdList.Any()) { attachmentMediaUrls = this.GetAttachmentMediaUrls(new BinaryFileService(communicationRockContext).GetByIds(smsAttachmentsBinaryFileIdList)); } bool recipientFound = true; while (recipientFound) { // make a new rockContext per recipient var recipientRockContext = new RockContext(); var recipient = Model.Communication.GetNextPending(communication.Id, mediumEntityTypeId, recipientRockContext); if (recipient != null) { if (ValidRecipient(recipient, communication.IsBulkCommunication)) { try { var phoneNumber = recipient.PersonAlias.Person.PhoneNumbers .Where(p => p.IsMessagingEnabled) .FirstOrDefault(); if (phoneNumber != null) { // Create merge field dictionary var mergeObjects = recipient.CommunicationMergeValues(mergeFields); string message = ResolveText(communication.SMSMessage, currentPerson, communication.EnabledLavaCommands, mergeObjects, publicAppRoot); string twilioNumber = phoneNumber.Number; if (!string.IsNullOrWhiteSpace(phoneNumber.CountryCode)) { twilioNumber = "+" + phoneNumber.CountryCode + phoneNumber.Number; } MessageResource response = SendToTwilio(fromPhone, callbackUrl, attachmentMediaUrls, message, twilioNumber); recipient.Status = CommunicationRecipientStatus.Delivered; recipient.TransportEntityTypeName = this.GetType().FullName; recipient.UniqueMessageId = response.Sid; try { var historyService = new HistoryService(recipientRockContext); historyService.Add(new History { CreatedByPersonAliasId = communication.SenderPersonAliasId, EntityTypeId = personEntityTypeId, CategoryId = communicationCategoryId, EntityId = recipient.PersonAlias.PersonId, Summary = "Sent SMS message.", Caption = message.Truncate(200), RelatedEntityTypeId = communicationEntityTypeId, RelatedEntityId = communication.Id }); } catch (Exception ex) { ExceptionLogService.LogException(ex, null); } } else { recipient.Status = CommunicationRecipientStatus.Failed; recipient.StatusNote = "No Phone Number with Messaging Enabled"; } } catch (Exception ex) { recipient.Status = CommunicationRecipientStatus.Failed; recipient.StatusNote = "Twilio Exception: " + ex.Message; } } recipientRockContext.SaveChanges(); if (throttlingWaitTimeMS.HasValue) { System.Threading.Thread.Sleep(throttlingWaitTimeMS.Value); } } else { recipientFound = false; } } } } } }
/// <summary> /// Provides an end method for an asynchronous process. /// </summary> /// <param name="result">An IAsyncResult that contains information about the status of the process.</param> public void EndProcessRequest(IAsyncResult result) { // restore the context from the asyncResult.AsyncState HttpContext context = (HttpContext)result.AsyncState; try { context.Response.Clear(); bool isBinaryFile = (bool)context.Items["isBinaryFile"]; if (isBinaryFile) { var rockContext = new RockContext(); bool requiresViewSecurity = false; BinaryFile binaryFile = new BinaryFileService(rockContext).EndGet(result, context, out requiresViewSecurity); if (binaryFile != null) { //// if the binaryFile's BinaryFileType requires security, check security //// note: we put a RequiresViewSecurity flag on BinaryFileType because checking security for every file would be slow (~40ms+ per request) if (requiresViewSecurity) { var currentUser = new UserLoginService(rockContext).GetByUserName(UserLogin.GetCurrentUserName()); Person currentPerson = currentUser != null ? currentUser.Person : null; binaryFile.BinaryFileType = binaryFile.BinaryFileType ?? new BinaryFileTypeService(rockContext).Get(binaryFile.BinaryFileTypeId.Value); if (!binaryFile.IsAuthorized(Authorization.VIEW, currentPerson)) { SendNotAuthorized(context); return; } } SendFile(context, binaryFile.ContentStream, binaryFile.MimeType, binaryFile.FileName, binaryFile.Guid.ToString("N")); return; } } else { Stream fileContents = (Stream)context.Items["fileContents"]; string physicalContentFileName = context.Items["physicalContentFileName"] as string; if (fileContents != null) { string mimeType = System.Web.MimeMapping.GetMimeMapping(physicalContentFileName); string fileName = Path.GetFileName(physicalContentFileName); SendFile(context, fileContents, mimeType, fileName, ""); return; } } context.Response.StatusCode = 404; context.Response.StatusDescription = "Unable to find the requested file."; } catch (Exception ex) { ExceptionLogService.LogException(ex, context); try { context.Response.StatusCode = 500; context.Response.StatusDescription = ex.Message; context.Response.Flush(); context.ApplicationInstance.CompleteRequest(); } catch (Exception ex2) { ExceptionLogService.LogException(ex2, context); } } }
/// <summary> /// Binds the grid. /// </summary> private void BindGrid() { var rockContext = new RockContext(); var communicationsQuery = new CommunicationService(rockContext) .Queryable().AsNoTracking() .Where(c => c.Status != CommunicationStatus.Transient); string subject = tbSubject.Text; if (!string.IsNullOrWhiteSpace(subject)) { communicationsQuery = communicationsQuery.Where(c => (string.IsNullOrEmpty(c.Subject) && c.Name.Contains(subject)) || c.Subject.Contains(subject)); } var communicationType = ddlType.SelectedValueAsEnumOrNull <CommunicationType>(); if (communicationType != null) { communicationsQuery = communicationsQuery.Where(c => c.CommunicationType == communicationType); } string status = ddlStatus.SelectedValue; if (!string.IsNullOrWhiteSpace(status)) { var communicationStatus = ( CommunicationStatus )System.Enum.Parse(typeof(CommunicationStatus), status); communicationsQuery = communicationsQuery.Where(c => c.Status == communicationStatus); } if (canApprove) { if (ppSender.PersonId.HasValue) { communicationsQuery = communicationsQuery .Where(c => c.SenderPersonAlias != null && c.SenderPersonAlias.PersonId == ppSender.PersonId.Value); } } else { // If can't approve, only show current person's communications communicationsQuery = communicationsQuery .Where(c => c.SenderPersonAlias != null && c.SenderPersonAlias.PersonId == CurrentPersonId); } if (nreRecipientCount.LowerValue.HasValue) { communicationsQuery = communicationsQuery.Where(a => a.Recipients.Count() >= nreRecipientCount.LowerValue.Value); } if (nreRecipientCount.UpperValue.HasValue) { communicationsQuery = communicationsQuery.Where(a => a.Recipients.Count() <= nreRecipientCount.UpperValue.Value); } if (drpCreatedDates.LowerValue.HasValue) { communicationsQuery = communicationsQuery.Where(a => a.CreatedDateTime.HasValue && a.CreatedDateTime.Value >= drpCreatedDates.LowerValue.Value); } if (drpCreatedDates.UpperValue.HasValue) { DateTime upperDate = drpCreatedDates.UpperValue.Value.Date.AddDays(1); communicationsQuery = communicationsQuery.Where(a => a.CreatedDateTime.HasValue && a.CreatedDateTime.Value < upperDate); } if (drpSentDates.LowerValue.HasValue) { communicationsQuery = communicationsQuery.Where(a => (a.SendDateTime ?? a.FutureSendDateTime) >= drpSentDates.LowerValue.Value); } if (drpSentDates.UpperValue.HasValue) { DateTime upperDate = drpSentDates.UpperValue.Value.Date.AddDays(1); communicationsQuery = communicationsQuery.Where(a => (a.SendDateTime ?? a.FutureSendDateTime) < upperDate); } string content = tbContent.Text; if (!string.IsNullOrWhiteSpace(content)) { communicationsQuery = communicationsQuery.Where(c => c.Message.Contains(content) || c.SMSMessage.Contains(content) || c.PushMessage.Contains(content)); } var recipients = new CommunicationRecipientService(rockContext).Queryable(); var communicationItemQuery = communicationsQuery .WherePersonAuthorizedToView(rockContext, this.CurrentPerson) .Select(c => new CommunicationItem { Id = c.Id, CommunicationType = c.CommunicationType, Subject = string.IsNullOrEmpty(c.Subject) ? (string.IsNullOrEmpty(c.PushTitle) ? c.Name : c.PushTitle) : c.Subject, CreatedDateTime = c.CreatedDateTime, SendDateTime = c.SendDateTime ?? c.FutureSendDateTime, SendDateTimePrefix = c.SendDateTime == null && c.FutureSendDateTime != null ? "<span class='label label-info'>Future</span> " : string.Empty, Sender = c.SenderPersonAlias != null ? c.SenderPersonAlias.Person : null, ReviewedDateTime = c.ReviewedDateTime, Reviewer = c.ReviewerPersonAlias != null ? c.ReviewerPersonAlias.Person : null, Status = c.Status, Recipients = recipients.Where(r => r.CommunicationId == c.Id).Count(), PendingRecipients = recipients.Where(r => r.CommunicationId == c.Id && r.Status == CommunicationRecipientStatus.Pending).Count(), CancelledRecipients = recipients.Where(r => r.CommunicationId == c.Id && r.Status == CommunicationRecipientStatus.Cancelled).Count(), FailedRecipients = recipients.Where(r => r.CommunicationId == c.Id && r.Status == CommunicationRecipientStatus.Failed).Count(), DeliveredRecipients = recipients.Where(r => r.CommunicationId == c.Id && (r.Status == CommunicationRecipientStatus.Delivered || r.Status == CommunicationRecipientStatus.Opened)).Count(), OpenedRecipients = recipients.Where(r => r.CommunicationId == c.Id && r.Status == CommunicationRecipientStatus.Opened).Count() }); var sortProperty = gCommunication.SortProperty; if (sortProperty != null) { communicationItemQuery = communicationItemQuery.Sort(sortProperty); } else { communicationItemQuery = communicationItemQuery.OrderByDescending(c => c.SendDateTime); } gCommunication.EntityTypeId = EntityTypeCache.Get <Rock.Model.Communication>().Id; nbBindError.Text = string.Empty; try { gCommunication.SetLinqDataSource(communicationItemQuery); gCommunication.DataBind(); } catch (Exception exception) { ExceptionLogService.LogException(exception); var sqlTimeoutException = ReportingHelper.FindSqlTimeoutException(exception); nbBindError.Text = string.Format( "<p>An error occurred trying to retrieve the communication history. Please try adjusting your filter settings and try again.</p><p>Error: {0}</p>", sqlTimeoutException != null ? sqlTimeoutException.Message : exception.Message); // if an error occurred, bind the grid with an empty object list gCommunication.DataSource = new List <object>(); gCommunication.DataBind(); } }
/// <summary> /// Job that will send scheduled group emails. /// /// 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; var messages = new List <string>(); var workflowsStarted = 0; var workflowTypeGuid = dataMap.GetString("WorkflowType").AsGuidOrNull(); if (workflowTypeGuid.HasValue) { var workflowType = WorkflowTypeCache.Get(workflowTypeGuid.Value); if (workflowType != null) { var username = Encryption.DecryptString(dataMap.GetString("Username")); var password = Encryption.DecryptString(dataMap.GetString("Password")); var emailAddress = dataMap.GetString("EmailAddress"); var url = new Uri(dataMap.GetString("ServerUrl")); var maxEmails = dataMap.GetString("MaxEmails").AsInteger(); var delete = dataMap.GetString("DeleteMessages").AsBoolean(); var onePer = dataMap.GetString("OneWorkflowPerConversation").AsBoolean(); Dictionary <string, string> attributeKeyMap = null; var workflowAttributeKeys = dataMap.GetString("WorkflowAttributes"); if (workflowAttributeKeys.IsNotNullOrWhiteSpace()) { attributeKeyMap = workflowAttributeKeys.AsDictionaryOrNull(); } attributeKeyMap = attributeKeyMap ?? new Dictionary <string, string>(); try { using (var rockContext = new RockContext()) { var service = new ExchangeService(); service.Credentials = new WebCredentials(username, password); service.TraceEnabled = true; service.TraceFlags = TraceFlags.All; service.Url = url; var findItemPropertySet = new PropertySet(BasePropertySet.IdOnly); findItemPropertySet.Add(ItemSchema.DateTimeReceived); var userMailbox = new Mailbox(emailAddress); var folderId = new FolderId(WellKnownFolderName.Inbox, userMailbox); var sf = new SearchFilter.SearchFilterCollection(LogicalOperator.And, new SearchFilter.IsEqualTo(EmailMessageSchema.IsRead, false)); var view = new ItemView(maxEmails); view.PropertySet = findItemPropertySet; view.OrderBy.Add(ItemSchema.DateTimeReceived, SortDirection.Descending); var findResults = service.FindItems(folderId, sf, view); if (findResults.Items.Count > 0) { var getMessagePropertySet = new PropertySet(BasePropertySet.FirstClassProperties); //getMessagePropertySet.Add( ItemSchema.Attachments ); // for future foreach (var item in findResults) { var message = EmailMessage.Bind(service, item.Id, getMessagePropertySet); message.Load(); var foreignKey = string.Empty; var existingWorkflow = new Workflow(); var createWorkflow = true; if (onePer) { var conversationId = message.ConversationId.UniqueId.ToString(); if (conversationId.Length > 100) { foreignKey = conversationId.Substring(conversationId.Length - 100, 100); } else { foreignKey = conversationId; } existingWorkflow = new WorkflowService(rockContext) .Queryable() .AsNoTracking() .FirstOrDefault(w => w.ActivatedDateTime.HasValue && !w.CompletedDateTime.HasValue && w.ForeignKey.Equals(foreignKey, StringComparison.Ordinal)); createWorkflow = existingWorkflow.IsNull(); } else { var emailId = message.Id.UniqueId.ToString(); if (emailId.Length > 100) { foreignKey = emailId.Substring(emailId.Length - 100, 100); } else { foreignKey = emailId; } } if (createWorkflow) { var workflow = Rock.Model.Workflow.Activate(workflowType, string.Format("{0} <{1}> [{2}]", message.From.Name.ToStringSafe(), message.From.Address.ToStringSafe(), message.DateTimeReceived.ToShortDateTimeString())); workflow.ForeignKey = foreignKey; workflow.LoadAttributes(rockContext); foreach (var keyPair in attributeKeyMap) { var value = string.Empty; switch (keyPair.Value) { case "DateReceived": value = message.DateTimeReceived.ToString("o", CultureInfo.CreateSpecificCulture("en-US")); break; case "FromEmail": value = message.From.Address.ToStringSafe(); break; case "FromName": value = message.From.Name.ToStringSafe(); break; case "Subject": value = message.Subject.ToStringSafe(); break; case "Body": value = message.Body.Text; break; default: break; } if (workflow.Attributes.ContainsKey(keyPair.Key)) { workflow.SetAttributeValue(keyPair.Key, value); } else { messages.Add(string.Format("'{0}' is not an attribute key in the activated workflow: '{1}'", keyPair.Key, workflow.Name)); } } List <string> workflowErrorMessages = new List <string>(); new Rock.Model.WorkflowService(rockContext).Process(workflow, out workflowErrorMessages); messages.AddRange(workflowErrorMessages); workflowsStarted++; } else if (existingWorkflow?.Id > 0) { existingWorkflow.LoadAttributes(); foreach (var keyPair in attributeKeyMap) { if (keyPair.Value.Contains("Body")) { var newValue = message.Body.Text; if (existingWorkflow.Attributes.ContainsKey(keyPair.Key)) { var existingValue = existingWorkflow.GetAttributeValue(keyPair.Key); var value = string.Format("{0}{1}{2}", existingValue, Environment.NewLine, newValue); existingWorkflow.SetAttributeValue(keyPair.Key, value); } else { messages.Add(string.Format("'{0}' is not an attribute key in the activated workflow: '{1}'", keyPair.Key, existingWorkflow.Name)); } } } existingWorkflow.SaveAttributeValues(); messages.Add(string.Format("{0} workflow was appended.", existingWorkflow.Name)); } message.IsRead = true; message.Update(ConflictResolutionMode.AlwaysOverwrite, true); if (delete) { message.Delete(DeleteMode.MoveToDeletedItems); } } } if (workflowsStarted > 0) { messages.Add(string.Format("Started {0} {1}", workflowsStarted, "workflow".PluralizeIf(workflowsStarted > 1))); } else { messages.Add("No workflows started"); } var results = new StringBuilder(); foreach (var message in messages) { results.AppendLine(message); } context.Result = results.ToString(); } } catch (System.Exception ex) { var context2 = HttpContext.Current; ExceptionLogService.LogException(ex, context2); throw; } } else { context.Result = "No valid workflow type found."; } } else { context.Result = "Valid workflow type guid was not set."; } }
/// <summary> /// Provides an end method for an asynchronous process. /// </summary> /// <param name="result">An IAsyncResult that contains information about the status of the process.</param> public void EndProcessRequest(IAsyncResult result) { HttpContext context = (HttpContext)result.AsyncState; try { // restore the command from the context SqlCommand cmd = (SqlCommand)context.Items["cmd"]; using (SqlDataReader reader = cmd.EndExecuteReader(result)) { reader.Read(); context.Response.Clear(); var fileName = (string)reader["FileName"]; context.Response.AddHeader("content-disposition", string.Format("inline;filename={0}", fileName)); context.Response.ContentType = (string)reader["MimeType"]; var entityTypeName = (string)reader["StorageTypeName"]; var provider = ProviderContainer.GetComponent(entityTypeName); if (provider is Database) { context.Response.BinaryWrite((byte[])reader["Data"]); } else { var url = (string)reader["Url"]; Stream stream; if (url.StartsWith("~/")) { var path = context.Server.MapPath(url); var fileInfo = new FileInfo(path); stream = fileInfo.Open(FileMode.Open, FileAccess.Read); } else { var request = WebRequest.Create(url); var response = request.GetResponse(); stream = response.GetResponseStream(); } if (stream != null) { using (var memoryStream = new MemoryStream()) { stream.CopyTo(memoryStream); stream.Close(); context.Response.BinaryWrite(memoryStream.ToArray()); } } else { context.Response.StatusCode = 404; context.Response.StatusDescription = "Unable to find the requested file."; } } context.Response.Flush(); context.Response.End(); } } catch (Exception ex) { ExceptionLogService.LogException(ex, context); context.Response.StatusCode = 500; context.Response.StatusDescription = ex.Message; context.Response.Flush(); context.Response.End(); } }
/// <summary> /// Executes the specified context. /// </summary> /// <param name="context">The context.</param> public void Execute( IJobExecutionContext context ) { JobDataMap dataMap = context.JobDetail.JobDataMap; int sqlCommandTimeout = dataMap.GetString( TIMEOUT_KEY ).AsIntegerOrNull() ?? 300; StringBuilder results = new StringBuilder(); int updatedDataViewCount = 0; var errors = new List<string>(); List<Exception> exceptions = new List<Exception>(); using ( var rockContext = new RockContext() ) { var currentDateTime = RockDateTime.Now; // get a list of all the data views that need to be refreshed var expiredPersistedDataViews = new DataViewService( rockContext ).Queryable() .Where( a => a.PersistedScheduleIntervalMinutes.HasValue ) .Where( a => ( a.PersistedLastRefreshDateTime == null ) || ( System.Data.Entity.SqlServer.SqlFunctions.DateAdd( "mi", a.PersistedScheduleIntervalMinutes.Value, a.PersistedLastRefreshDateTime.Value ) < currentDateTime ) ); var expiredPersistedDataViewsList = expiredPersistedDataViews.ToList(); foreach ( var dataView in expiredPersistedDataViewsList ) { var name = dataView.Name; try { context.UpdateLastStatusMessage( $"Updating {dataView.Name}" ); dataView.PersistResult( sqlCommandTimeout ); dataView.PersistedLastRefreshDateTime = RockDateTime.Now; rockContext.SaveChanges(); updatedDataViewCount++; } catch ( Exception ex ) { // Capture and log the exception because we're not going to fail this job // unless all the data views fail. var errorMessage = $"An error occurred while trying to update persisted data view '{name}' so it was skipped. Error: {ex.Message}"; errors.Add( errorMessage ); var ex2 = new Exception( errorMessage, ex ); exceptions.Add( ex2 ); ExceptionLogService.LogException( ex2, null ); continue; } } } // Format the result message results.AppendLine( $"Updated {updatedDataViewCount} {"dataview".PluralizeIf( updatedDataViewCount != 1 )}" ); context.Result = results.ToString(); if ( errors.Any() ) { StringBuilder sb = new StringBuilder(); sb.AppendLine(); sb.Append( "Errors: " ); errors.ForEach( e => { sb.AppendLine(); sb.Append( e ); } ); string errorMessage = sb.ToString(); context.Result += errorMessage; // We're not going to throw an aggregate exception unless there were no successes. // Otherwise the status message does not show any of the success messages in // the last status message. if ( updatedDataViewCount == 0 ) { throw new AggregateException( exceptions.ToArray() ); } } }
/// <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; // get registrations where // + template is active // + instance is active // + template has a number of days between reminders // + template as fields needed to send a reminder email // + the registration has a cost // + the registration has been closed within the last xx days (to prevent eternal nagging) using (RockContext rockContext = new RockContext()) { int sendCount = 0; int registrationInstanceCount = 0; var appRoot = GlobalAttributesCache.Get().GetValue("PublicApplicationRoot"); RegistrationService registrationService = new RegistrationService(rockContext); var currentDate = RockDateTime.Today; var cutoffDays = dataMap.GetString("CutoffDate").AsIntegerOrNull() ?? 30; var registrations = registrationService.Queryable("RegistrationInstance") .Where(r => r.RegistrationInstance.RegistrationTemplate.IsActive && r.RegistrationInstance.IsActive == true && (r.RegistrationInstance.RegistrationTemplate.PaymentReminderTimeSpan != null && r.RegistrationInstance.RegistrationTemplate.PaymentReminderTimeSpan != 0) && r.RegistrationInstance.RegistrationTemplate.PaymentReminderEmailTemplate != null && r.RegistrationInstance.RegistrationTemplate.PaymentReminderEmailTemplate.Length > 0 && r.RegistrationInstance.RegistrationTemplate.PaymentReminderFromEmail != null && r.RegistrationInstance.RegistrationTemplate.PaymentReminderFromEmail.Length > 0 && r.RegistrationInstance.RegistrationTemplate.PaymentReminderSubject != null && r.RegistrationInstance.RegistrationTemplate.PaymentReminderSubject.Length > 0 && (r.RegistrationInstance.RegistrationTemplate.Cost != 0 || (r.RegistrationInstance.Cost != null && r.RegistrationInstance.Cost != 0)) && (r.RegistrationInstance.EndDateTime == null || currentDate <= System.Data.Entity.SqlServer.SqlFunctions.DateAdd("day", cutoffDays, r.RegistrationInstance.EndDateTime))) .ToList(); registrationInstanceCount = registrations.Select(r => r.RegistrationInstance.Id).Distinct().Count(); var errors = new List <string>(); foreach (var registration in registrations) { if (registration.DiscountedCost > registration.TotalPaid) { var reminderDate = RockDateTime.Now.AddDays(registration.RegistrationInstance.RegistrationTemplate.PaymentReminderTimeSpan.Value * -1); if (registration.LastPaymentReminderDateTime < reminderDate) { Dictionary <string, object> mergeObjects = new Dictionary <string, object>(); mergeObjects.Add("Registration", registration); mergeObjects.Add("RegistrationInstance", registration.RegistrationInstance); var emailMessage = new RockEmailMessage(); emailMessage.AdditionalMergeFields = mergeObjects; emailMessage.AddRecipient(new RecipientData(registration.ConfirmationEmail, mergeObjects)); emailMessage.FromEmail = registration.RegistrationInstance.RegistrationTemplate.PaymentReminderFromEmail; emailMessage.FromName = registration.RegistrationInstance.RegistrationTemplate.PaymentReminderSubject; emailMessage.Subject = registration.RegistrationInstance.RegistrationTemplate.PaymentReminderFromName; emailMessage.Message = registration.RegistrationInstance.RegistrationTemplate.PaymentReminderEmailTemplate; var emailErrors = new List <string>(); emailMessage.Send(out errors); registration.LastPaymentReminderDateTime = RockDateTime.Now; rockContext.SaveChanges(); if (!emailErrors.Any()) { sendCount++; } } } } context.Result = string.Format("Sent {0} from {1}", "reminder".ToQuantity(sendCount), "registration instances".ToQuantity(registrationInstanceCount)); if (errors.Any()) { StringBuilder sb = new StringBuilder(); sb.AppendLine(); sb.Append(string.Format("{0} Errors: ", errors.Count())); errors.ForEach(e => { sb.AppendLine(); sb.Append(e); }); string errorMessage = sb.ToString(); context.Result += errorMessage; var exception = new Exception(errorMessage); HttpContext context2 = HttpContext.Current; ExceptionLogService.LogException(exception, context2); throw exception; } } }
/// <summary> /// Updates the scheduled jobs. /// </summary> /// <param name="context">The context.</param> private void UpdateScheduledJobs(IJobExecutionContext context) { var scheduler = context.Scheduler; int jobsDeleted = 0; int jobsScheduleUpdated = 0; var rockContext = new Rock.Data.RockContext(); ServiceJobService jobService = new ServiceJobService(rockContext); List <ServiceJob> activeJobList = jobService.GetActiveJobs().ToList(); List <Quartz.JobKey> scheduledQuartzJobs = scheduler.GetJobKeys(GroupMatcher <JobKey> .GroupStartsWith(string.Empty)).ToList(); // delete any jobs that are no longer exist (are are not set to active) in the database var quartsJobsToDelete = scheduledQuartzJobs.Where(a => !activeJobList.Any(j => j.Guid.Equals(a.Name.AsGuid()))); foreach (JobKey jobKey in quartsJobsToDelete) { scheduler.DeleteJob(jobKey); jobsDeleted++; } // add any jobs that are not yet scheduled var newActiveJobs = activeJobList.Where(a => !scheduledQuartzJobs.Any(q => q.Name.AsGuid().Equals(a.Guid))); foreach (Rock.Model.ServiceJob job in newActiveJobs) { const string errorSchedulingStatus = "Error scheduling Job"; try { IJobDetail jobDetail = jobService.BuildQuartzJob(job); ITrigger jobTrigger = jobService.BuildQuartzTrigger(job); scheduler.ScheduleJob(jobDetail, jobTrigger); jobsScheduleUpdated++; if (job.LastStatus == errorSchedulingStatus) { job.LastStatusMessage = string.Empty; job.LastStatus = string.Empty; rockContext.SaveChanges(); } } catch (Exception ex) { ExceptionLogService.LogException(ex, null); // create a friendly error message string message = string.Format("Error scheduling the job: {0}.\n\n{2}", job.Name, job.Assembly, ex.Message); job.LastStatusMessage = message; job.LastStatus = errorSchedulingStatus; } } rockContext.SaveChanges(); // reload the jobs in case any where added/removed scheduledQuartzJobs = scheduler.GetJobKeys(GroupMatcher <JobKey> .GroupStartsWith(string.Empty)).ToList(); // update schedule if the schedule has changed foreach (var jobKey in scheduledQuartzJobs) { var jobCronTrigger = scheduler.GetTriggersOfJob(jobKey).OfType <ICronTrigger>().FirstOrDefault(); if (jobCronTrigger != null) { var activeJob = activeJobList.FirstOrDefault(a => a.Guid.Equals(jobKey.Name.AsGuid())); if (activeJob != null) { bool rescheduleJob = false; // fix up the schedule if it has changed if (activeJob.CronExpression != jobCronTrigger.CronExpressionString) { rescheduleJob = true; } // update the job detail if it has changed var scheduledJobDetail = scheduler.GetJobDetail(jobKey); var jobDetail = jobService.BuildQuartzJob(activeJob); if (scheduledJobDetail != null && jobDetail != null) { if (scheduledJobDetail.JobType != jobDetail.JobType) { rescheduleJob = true; } if (scheduledJobDetail.JobDataMap.ToJson() != jobDetail.JobDataMap.ToJson()) { rescheduleJob = true; } } if (rescheduleJob) { const string errorReschedulingStatus = "Error re-scheduling Job"; try { ITrigger newJobTrigger = jobService.BuildQuartzTrigger(activeJob); bool deletedSuccessfully = scheduler.DeleteJob(jobKey); scheduler.ScheduleJob(jobDetail, newJobTrigger); jobsScheduleUpdated++; if (activeJob.LastStatus == errorReschedulingStatus) { activeJob.LastStatusMessage = string.Empty; activeJob.LastStatus = string.Empty; rockContext.SaveChanges(); } } catch (Exception ex) { ExceptionLogService.LogException(ex, null); // create a friendly error message string message = string.Format("Error re-scheduling the job: {0}.\n\n{2}", activeJob.Name, activeJob.Assembly, ex.Message); activeJob.LastStatusMessage = message; activeJob.LastStatus = errorReschedulingStatus; } } } } } context.Result = string.Empty; if (jobsDeleted > 0) { context.Result += string.Format("Deleted {0} job schedule(s)", jobsDeleted); } if (jobsScheduleUpdated > 0) { context.Result += (string.IsNullOrEmpty(context.Result as string) ? "" : " and ") + string.Format("Updated {0} schedule(s)", jobsScheduleUpdated); } }
/// <summary> /// Handles the Click event of the lbSave control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> protected void lbSave_Click(object sender, EventArgs e) { if (_group != null && _occurrence != null) { var rockContext = new RockContext(); var attendanceService = new AttendanceService(rockContext); var personAliasService = new PersonAliasService(rockContext); var locationService = new LocationService(rockContext); bool dateAdjusted = false; DateTime startDate = _occurrence.Date; // If this is a manuall entered occurrence, check to see if date was changed if (!_occurrence.ScheduleId.HasValue) { DateTime?originalDate = PageParameter("Date").AsDateTime(); if (originalDate.HasValue && originalDate.Value.Date != startDate.Date) { startDate = originalDate.Value.Date; dateAdjusted = true; } } DateTime endDate = startDate.AddDays(1); var existingAttendees = attendanceService .Queryable("PersonAlias") .Where(a => a.GroupId == _group.Id && a.LocationId == _occurrence.LocationId && a.ScheduleId == _occurrence.ScheduleId && a.StartDateTime >= startDate && a.StartDateTime < endDate); if (dateAdjusted) { foreach (var attendee in existingAttendees) { attendee.StartDateTime = _occurrence.Date; } } // If did not meet was selected and this was a manually entered occurrence (not based on a schedule/location) // then just delete all the attendance records instead of tracking a 'did not meet' value if (cbDidNotMeet.Checked && !_occurrence.ScheduleId.HasValue) { foreach (var attendance in existingAttendees) { attendanceService.Delete(attendance); } } else { if (cbDidNotMeet.Checked) { // If the occurrence is based on a schedule, set the did not meet flags foreach (var attendance in existingAttendees) { attendance.DidAttend = null; attendance.DidNotOccur = true; } } foreach (var attendee in _attendees) { var attendance = existingAttendees .Where(a => a.PersonAlias.PersonId == attendee.PersonId) .FirstOrDefault(); if (attendance == null) { int?personAliasId = personAliasService.GetPrimaryAliasId(attendee.PersonId); if (personAliasId.HasValue) { attendance = new Attendance(); attendance.GroupId = _group.Id; attendance.ScheduleId = _group.ScheduleId; attendance.PersonAliasId = personAliasId; attendance.StartDateTime = _occurrence.Date.Date.Add(_occurrence.StartTime); attendance.LocationId = _occurrence.LocationId; attendance.CampusId = locationService.GetCampusIdForLocation(_occurrence.LocationId); attendance.ScheduleId = _occurrence.ScheduleId; attendanceService.Add(attendance); } } if (attendance != null) { if (cbDidNotMeet.Checked) { attendance.DidAttend = null; attendance.DidNotOccur = true; } else { attendance.DidAttend = attendee.Attended; attendance.DidNotOccur = null; } } } } if (_occurrence.LocationId.HasValue) { Rock.CheckIn.KioskLocationAttendance.Flush(_occurrence.LocationId.Value); } rockContext.SaveChanges(); WorkflowType workflowType = null; Guid? workflowTypeGuid = GetAttributeValue("Workflow").AsGuidOrNull(); if (workflowTypeGuid.HasValue) { var workflowTypeService = new WorkflowTypeService(rockContext); workflowType = workflowTypeService.Get(workflowTypeGuid.Value); if (workflowType != null) { try { var workflow = Workflow.Activate(workflowType, _group.Name); workflow.SetAttributeValue("StartDateTime", _occurrence.Date.ToString("o")); workflow.SetAttributeValue("Schedule", _group.Schedule.Guid.ToString()); List <string> workflowErrors; new WorkflowService(rockContext).Process(workflow, _group, out workflowErrors); } catch (Exception ex) { ExceptionLogService.LogException(ex, this.Context); } } } NavigateToParentPage(new Dictionary <string, string> { { "GroupId", _group.Id.ToString() } }); } }
/// <summary> /// Creates the filter control. /// </summary> /// <param name="parentControl">The parent control.</param> /// <param name="filter">The filter.</param> /// <param name="setSelection">if set to <c>true</c> [set selection].</param> /// <param name="rockContext">The rock context.</param> /// <param name="contentChannel">The content channel.</param> private void CreateFilterControl(Control parentControl, DataViewFilter filter, bool setSelection, RockContext rockContext, ContentChannel contentChannel) { try { if (filter.ExpressionType == FilterExpressionType.Filter) { var filterControl = new FilterField { Entity = new ContentChannelItem { ContentChannelId = contentChannel.Id, ContentChannelTypeId = contentChannel.ContentChannelTypeId } }; parentControl.Controls.Add(filterControl); filterControl.DataViewFilterGuid = filter.Guid; filterControl.ID = string.Format("ff_{0}", filterControl.DataViewFilterGuid.ToString("N")); // Remove the 'Other Data View' Filter as it doesn't really make sense to have it available in this scenario filterControl.ExcludedFilterTypes = new string[] { typeof(Rock.Reporting.DataFilter.OtherDataViewFilter).FullName }; filterControl.FilteredEntityTypeName = ITEM_TYPE_NAME; if (filter.EntityTypeId.HasValue) { var entityTypeCache = EntityTypeCache.Get(filter.EntityTypeId.Value, rockContext); if (entityTypeCache != null) { filterControl.FilterEntityTypeName = entityTypeCache.Name; } } filterControl.Expanded = filter.Expanded; if (setSelection) { try { filterControl.SetSelection(filter.Selection); } catch (Exception ex) { ExceptionLogService.LogException(new Exception("Exception setting selection for DataViewFilter: " + filter.Guid, ex)); } } filterControl.DeleteClick += filterControl_DeleteClick; } else { var groupControl = new FilterGroup(); parentControl.Controls.Add(groupControl); groupControl.DataViewFilterGuid = filter.Guid; groupControl.ID = string.Format("fg_{0}", groupControl.DataViewFilterGuid.ToString("N")); groupControl.FilteredEntityTypeName = ITEM_TYPE_NAME; groupControl.IsDeleteEnabled = parentControl is FilterGroup; if (setSelection) { groupControl.FilterType = filter.ExpressionType; } groupControl.AddFilterClick += groupControl_AddFilterClick; groupControl.AddGroupClick += groupControl_AddGroupClick; groupControl.DeleteGroupClick += groupControl_DeleteGroupClick; foreach (var childFilter in filter.ChildFilters) { CreateFilterControl(groupControl, childFilter, setSelection, rockContext, contentChannel); } } } catch (Exception ex) { ExceptionLogService.LogException(new Exception("Exception creating FilterControl for DataViewFilter: " + filter.Guid, ex)); } }
/// <summary> /// Called by the <see cref="IScheduler"/> after a <see cref="IJobDetail"/> /// has been executed, and before the associated <see cref="Quartz.Spi.IOperableTrigger"/>'s /// <see cref="Quartz.Spi.IOperableTrigger.Triggered"/> method has been called. /// </summary> /// <param name="context"></param> /// <param name="jobException"></param> public void JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException) { StringBuilder message = new StringBuilder(); bool sendMessage = false; // get job type id int jobId = Convert.ToInt16(context.JobDetail.Description); // load job var rockContext = new RockContext(); var jobService = new ServiceJobService(rockContext); var job = jobService.Get(jobId); // format the message message.Append(string.Format("The job {0} ran for {1} seconds on {2}. Below is the results:<p>", job.Name, context.JobRunTime.TotalSeconds, context.FireTimeUtc.Value.DateTime.ToLocalTime())); // if noticiation staus is all set flag to send message if (job.NotificationStatus == JobNotificationStatus.All) { sendMessage = true; } // set last run date job.LastRunDateTime = RockDateTime.Now; // context.FireTimeUtc.Value.DateTime.ToLocalTime(); // set run time job.LastRunDurationSeconds = Convert.ToInt32(context.JobRunTime.TotalSeconds); // set the scheduler name job.LastRunSchedulerName = context.Scheduler.SchedulerName; // determine if an error occured if (jobException == null) { job.LastSuccessfulRunDateTime = job.LastRunDateTime; job.LastStatus = "Success"; job.LastStatusMessage = string.Empty; message.Append("Result: Success"); // determine if message should be sent if (job.NotificationStatus == JobNotificationStatus.Success) { sendMessage = true; } } else { Exception exceptionToLog = jobException; // drill down to the interesting exception while (exceptionToLog is Quartz.SchedulerException && exceptionToLog.InnerException != null) { exceptionToLog = exceptionToLog.InnerException; } // log the exception to the database ExceptionLogService.LogException(exceptionToLog, null); var summaryException = exceptionToLog; // put the exception into the status job.LastStatus = "Exception"; AggregateException aggregateException = summaryException as AggregateException; if (aggregateException != null && aggregateException.InnerExceptions != null && aggregateException.InnerExceptions.Count == 1) { // if it's an aggregate, but there is only one, convert it to a single exception summaryException = aggregateException.InnerExceptions[0]; aggregateException = null; } if (aggregateException != null) { var firstException = aggregateException.InnerExceptions.First(); job.LastStatusMessage = "One or more exceptions occurred. First Exception: " + firstException.Message; summaryException = firstException; } else { job.LastStatusMessage = summaryException.Message; } message.Append("Result: Exception<p>Message:<br>" + job.LastStatusMessage); if (summaryException.InnerException != null) { job.LastStatusMessage += " (" + summaryException.InnerException.Message + ")"; message.Append("<p>Inner Exception:<br>" + summaryException.InnerException.Message); } if (job.NotificationStatus == JobNotificationStatus.Error) { sendMessage = true; } } rockContext.SaveChanges(); // send notification if (sendMessage) { // TODO: implement email send once it's available } }
/// <summary> /// Executes the specified context. /// </summary> /// <param name="context">The context.</param> public void Execute(IJobExecutionContext context) { var exceptionMsgs = new List <string>(); JobDataMap dataMap = context.JobDetail.JobDataMap; Guid? groupGuid = dataMap.GetString("EligibleFollowers").AsGuidOrNull(); Guid? systemEmailGuid = dataMap.GetString("EmailTemplate").AsGuidOrNull(); int followingSuggestionsEmailsSent = 0; int followingSuggestionsSuggestionsTotal = 0; if (groupGuid.HasValue && systemEmailGuid.HasValue) { using (var rockContext = new RockContext()) { var followingService = new FollowingService(rockContext); // The people who are eligible to get following suggestions based on the group type setting for this job var eligiblePersonIds = new GroupMemberService(rockContext) .Queryable().AsNoTracking() .Where(m => m.Group != null && m.Group.Guid.Equals(groupGuid.Value) && m.GroupMemberStatus == GroupMemberStatus.Active && m.Person != null && m.Person.Email != null && m.Person.Email != string.Empty && m.Person.EmailPreference != EmailPreference.DoNotEmail && m.Person.IsEmailActive) .Select(m => m.PersonId) .Distinct(); // check to see if there are any event types that require notification var followerPersonIds = new List <int>(); if (new FollowingEventTypeService(rockContext) .Queryable().AsNoTracking() .Any(e => e.IsNoticeRequired)) { // if so, include all eligible people followerPersonIds = eligiblePersonIds.ToList(); } else { // if not, filter the list of eligible people down to only those that actually have subscribed to one or more following events followerPersonIds = new FollowingEventSubscriptionService(rockContext) .Queryable().AsNoTracking() .Where(f => eligiblePersonIds.Contains(f.PersonAlias.PersonId)) .Select(f => f.PersonAlias.PersonId) .Distinct() .ToList(); } if (followerPersonIds.Any()) { // Get the primary person alias id for each of the followers var primaryAliasIds = new Dictionary <int, int>(); new PersonAliasService(rockContext) .Queryable().AsNoTracking() .Where(a => followerPersonIds.Contains(a.PersonId) && a.PersonId == a.AliasPersonId) .ToList() .ForEach(a => primaryAliasIds.AddOrIgnore(a.PersonId, a.Id)); // Get current date/time. var timestamp = RockDateTime.Now; var suggestionTypes = new FollowingSuggestionTypeService(rockContext) .Queryable().AsNoTracking() .Where(s => s.IsActive) .OrderBy(s => s.Name) .ToList(); var components = new Dictionary <int, SuggestionComponent>(); var suggestedEntities = new Dictionary <int, Dictionary <int, IEntity> >(); foreach (var suggestionType in suggestionTypes) { try { // Get the suggestion type component var suggestionComponent = suggestionType.GetSuggestionComponent(); if (suggestionComponent != null) { components.Add(suggestionType.Id, suggestionComponent); // Get the entitytype for this suggestion type var suggestionEntityType = EntityTypeCache.Get(suggestionComponent.FollowedType); if (suggestionEntityType != null) { var entityIds = new List <int>(); // Call the components method to return all of it's suggestions var personEntitySuggestions = suggestionComponent.GetSuggestions(suggestionType, followerPersonIds); // If any suggestions were returned by the component if (personEntitySuggestions.Any()) { int entityTypeId = suggestionEntityType.Id; string reasonNote = suggestionType.ReasonNote; // Get the existing followings for any of the followers var existingFollowings = new Dictionary <int, List <int> >(); foreach (var following in followingService.Queryable("PersonAlias").AsNoTracking() .Where(f => f.EntityTypeId == entityTypeId && followerPersonIds.Contains(f.PersonAlias.PersonId))) { existingFollowings.AddOrIgnore(following.PersonAlias.PersonId, new List <int>()); existingFollowings[following.PersonAlias.PersonId].Add(following.EntityId); } // Loop through each follower foreach (var followerPersonId in personEntitySuggestions .Select(s => s.PersonId) .Distinct()) { using (var suggestionContext = new RockContext()) { var followingSuggestedService = new FollowingSuggestedService(suggestionContext); // Read all the existing suggestions for this type and the returned followers var existingSuggestions = followingSuggestedService .Queryable("PersonAlias") .Where(s => s.SuggestionTypeId == suggestionType.Id && s.PersonAlias.PersonId == followerPersonId) .ToList(); // Look through the returned suggestions foreach (var followedEntityId in personEntitySuggestions .Where(s => s.PersonId == followerPersonId) .Select(s => s.EntityId)) { // Make sure person isn't already following this entity if (!existingFollowings.ContainsKey(followerPersonId) || !existingFollowings[followerPersonId].Contains(followedEntityId)) { // If this person had a primary alias id if (primaryAliasIds.ContainsKey(followerPersonId)) { entityIds.Add(followedEntityId); // Look for existing suggestion for this person and entity var suggestion = existingSuggestions .Where(s => s.EntityId == followedEntityId) .OrderByDescending(s => s.StatusChangedDateTime) .FirstOrDefault(); // If not found, add one if (suggestion == null) { suggestion = new FollowingSuggested(); suggestion.EntityTypeId = entityTypeId; suggestion.EntityId = followedEntityId; suggestion.PersonAliasId = primaryAliasIds[followerPersonId]; suggestion.SuggestionTypeId = suggestionType.Id; suggestion.Status = FollowingSuggestedStatus.PendingNotification; suggestion.StatusChangedDateTime = timestamp; followingSuggestedService.Add(suggestion); } else { // If found, and it has not been ignored, and it's time to promote again, update the promote date if (suggestion.Status != FollowingSuggestedStatus.Ignored && suggestionType.ReminderDays.HasValue && ( !suggestion.LastPromotedDateTime.HasValue || suggestion.LastPromotedDateTime.Value.AddDays(suggestionType.ReminderDays.Value) <= timestamp )) { if (suggestion.Status != FollowingSuggestedStatus.PendingNotification) { suggestion.StatusChangedDateTime = timestamp; suggestion.Status = FollowingSuggestedStatus.PendingNotification; } } } } } } // Save the suggestions for this type suggestionContext.SaveChanges(); } } } // If any entities are being suggested for this type, query database for them and save to dictionary if (entityIds.Any()) { if (suggestionEntityType.AssemblyName != null) { // get the actual type of what is being followed Type entityType = suggestionEntityType.GetEntityType(); if (entityType != null) { // Get generic queryable method and query all the entities that are being followed Type[] modelType = { entityType }; Type genericServiceType = typeof(Rock.Data.Service <>); Type modelServiceType = genericServiceType.MakeGenericType(modelType); Rock.Data.IService serviceInstance = Activator.CreateInstance(modelServiceType, new object[] { rockContext }) as IService; MethodInfo qryMethod = serviceInstance.GetType().GetMethod("Queryable", new Type[] { }); var entityQry = qryMethod.Invoke(serviceInstance, new object[] { }) as IQueryable <IEntity>; var entityList = entityQry.AsNoTracking().Where(q => entityIds.Contains(q.Id)).ToList(); if (entityList != null && entityList.Any()) { var entities = new Dictionary <int, IEntity>(); entityList.ForEach(e => entities.Add(e.Id, e)); suggestedEntities.Add(suggestionType.Id, entities); } } } } } } } catch (Exception ex) { exceptionMsgs.Add(string.Format("An exception occurred calculating suggestions for the '{0}' suggestion type:{1} {2}", suggestionType.Name, Environment.NewLine, ex.Messages().AsDelimited(Environment.NewLine + " "))); ExceptionLogService.LogException(ex, System.Web.HttpContext.Current); } } var allSuggestions = new FollowingSuggestedService(rockContext) .Queryable("PersonAlias") .Where(s => s.Status == FollowingSuggestedStatus.PendingNotification) .ToList(); var suggestionPersonIds = allSuggestions .Where(s => followerPersonIds.Contains(s.PersonAlias.PersonId)) .Select(s => s.PersonAlias.PersonId) .Distinct() .ToList(); var appRoot = GlobalAttributesCache.Get().GetValue("PublicApplicationRoot", rockContext); foreach (var person in new PersonService(rockContext) .Queryable().AsNoTracking() .Where(p => suggestionPersonIds.Contains(p.Id)) .ToList()) { try { var personSuggestionNotices = new List <FollowingSuggestionNotices>(); foreach (var suggestionType in suggestionTypes) { var component = components.ContainsKey(suggestionType.Id) ? components[suggestionType.Id] : null; if (component != null && suggestedEntities.ContainsKey(suggestionType.Id)) { var entities = new List <IEntity>(); foreach (var suggestion in allSuggestions .Where(s => s.PersonAlias.PersonId == person.Id && s.SuggestionTypeId == suggestionType.Id) .ToList()) { if (suggestedEntities[suggestionType.Id].ContainsKey(suggestion.EntityId)) { entities.Add(suggestedEntities[suggestionType.Id][suggestion.EntityId]); suggestion.LastPromotedDateTime = timestamp; suggestion.Status = FollowingSuggestedStatus.Suggested; } } var notices = new List <string>(); foreach (var entity in component.SortEntities(entities)) { notices.Add(component.FormatEntityNotification(suggestionType, entity)); } if (notices.Any()) { personSuggestionNotices.Add(new FollowingSuggestionNotices(suggestionType, notices)); } } } if (personSuggestionNotices.Any()) { // Send the notice var mergeFields = new Dictionary <string, object>(); mergeFields.Add("Person", person); mergeFields.Add("Suggestions", personSuggestionNotices.OrderBy(s => s.SuggestionType.Order).ToList()); var emailMessage = new RockEmailMessage(systemEmailGuid.Value); emailMessage.AddRecipient(new RockEmailMessageRecipient(person, mergeFields)); var errors = new List <string>(); emailMessage.Send(out errors); exceptionMsgs.AddRange(errors); followingSuggestionsEmailsSent += 1; followingSuggestionsSuggestionsTotal += personSuggestionNotices.Count(); } rockContext.SaveChanges(); } catch (Exception ex) { exceptionMsgs.Add(string.Format("An exception occurred sending suggestions to '{0}':{1} {2}", person.FullName, Environment.NewLine, ex.Messages().AsDelimited(Environment.NewLine + " "))); ExceptionLogService.LogException(ex, System.Web.HttpContext.Current); } } } } } context.Result = string.Format("A total of {0} following suggestions sent to {1} people", followingSuggestionsSuggestionsTotal, followingSuggestionsEmailsSent); if (exceptionMsgs.Any()) { throw new Exception("One or more exceptions occurred calculating suggestions..." + Environment.NewLine + exceptionMsgs.AsDelimited(Environment.NewLine)); } }
private void LogError(Exception ex, int parentException, string status, System.Web.HttpContext context) { try { // get the current user Rock.CMS.User user = Rock.CMS.UserService.GetCurrentUser(); // save the exception info to the db ExceptionLogService service = new ExceptionLogService(); ExceptionLog exceptionLog = new ExceptionLog();; exceptionLog.ParentId = parentException; exceptionLog.ExceptionDate = DateTime.Now; if (ex.InnerException != null) { exceptionLog.HasInnerException = true; } exceptionLog.Description = ex.Message; exceptionLog.StackTrace = ex.StackTrace; exceptionLog.Source = ex.Source; exceptionLog.StatusCode = status; if (context.Items["Rock:SiteId"] != null) { exceptionLog.SiteId = Int32.Parse(context.Items["Rock:SiteId"].ToString()); } if (context.Items["Rock:PageId"] != null) { exceptionLog.PageId = Int32.Parse(context.Items["Rock:PageId"].ToString()); } exceptionLog.ExceptionType = ex.GetType().Name; exceptionLog.PageUrl = context.Request.RawUrl; exceptionLog.QueryString = context.Request.QueryString.ToString(); // write cookies StringBuilder cookies = new StringBuilder(); cookies.Append("<table class=\"cookies\">"); foreach (string cookie in context.Request.Cookies) { cookies.Append("<tr><td><b>" + cookie + "</b></td><td>" + context.Request.Cookies[cookie].Value + "</td></tr>"); } cookies.Append("</table>"); exceptionLog.Cookies = cookies.ToString(); // write form items StringBuilder formItems = new StringBuilder(); cookies.Append("<table class=\"formItems\">"); foreach (string formItem in context.Request.Form) { cookies.Append("<tr><td><b>" + formItem + "</b></td><td>" + context.Request.Form[formItem].ToString() + "</td></tr>"); } cookies.Append("</table>"); exceptionLog.Form = formItems.ToString(); // write server vars StringBuilder serverVars = new StringBuilder(); cookies.Append("<table class=\"server-variables\">"); foreach (string serverVar in context.Request.ServerVariables) { serverVars.Append("<tr><td><b>" + serverVar + "</b></td><td>" + context.Request.ServerVariables[serverVar].ToString() + "</td></tr>"); } cookies.Append("</table>"); exceptionLog.ServerVariables = serverVars.ToString(); if (user != null) { exceptionLog.PersonId = user.PersonId; } service.Add(exceptionLog, null); service.Save(exceptionLog, null); // log inner exceptions if (ex.InnerException != null) { LogError(ex.InnerException, exceptionLog.Id, status, context); } } catch (Exception exception) { // if you get an exception while logging an exception I guess you're hosed... } }
/// <summary> /// Sends the specified communication. /// </summary> /// <param name="communication">The communication.</param> /// <param name="mediumEntityTypeId">The medium entity type identifier.</param> /// <param name="mediumAttributes">The medium attributes.</param> /// <exception cref="System.NotImplementedException"></exception> public override void Send(Model.Communication communication, int mediumEntityTypeId, Dictionary <string, string> mediumAttributes) { using (var communicationRockContext = new RockContext()) { // Requery the Communication communication = new CommunicationService(communicationRockContext) .Queryable("CreatedByPersonAlias.Person") .FirstOrDefault(c => c.Id == communication.Id); bool hasPendingRecipients; if (communication != null && communication.Status == Model.CommunicationStatus.Approved && (!communication.FutureSendDateTime.HasValue || communication.FutureSendDateTime.Value.CompareTo(RockDateTime.Now) <= 0)) { var qryRecipients = new CommunicationRecipientService(communicationRockContext).Queryable(); hasPendingRecipients = qryRecipients .Where(r => r.CommunicationId == communication.Id && r.Status == Model.CommunicationRecipientStatus.Pending && r.MediumEntityTypeId.HasValue && r.MediumEntityTypeId.Value == mediumEntityTypeId) .Any(); } else { hasPendingRecipients = false; } if (hasPendingRecipients) { var currentPerson = communication.CreatedByPersonAlias?.Person; var globalAttributes = GlobalAttributesCache.Get(); string publicAppRoot = globalAttributes.GetValue("PublicApplicationRoot").EnsureTrailingForwardslash(); var mergeFields = Lava.LavaHelper.GetCommonMergeFields(null, currentPerson); string serverKey = GetAttributeValue("ServerKey"); var sender = new Sender(serverKey); var personEntityTypeId = EntityTypeCache.Get("Rock.Model.Person").Id; var communicationEntityTypeId = EntityTypeCache.Get("Rock.Model.Communication").Id; var communicationCategoryId = CategoryCache.Get(Rock.SystemGuid.Category.HISTORY_PERSON_COMMUNICATIONS.AsGuid(), communicationRockContext).Id; bool recipientFound = true; while (recipientFound) { // make a new rockContext per recipient var recipientRockContext = new RockContext(); var recipient = Model.Communication.GetNextPending(communication.Id, mediumEntityTypeId, recipientRockContext); if (recipient != null) { if (ValidRecipient(recipient, communication.IsBulkCommunication)) { try { int personAlias = recipient.PersonAliasId; var service = new PersonalDeviceService(recipientRockContext); List <string> devices = service.Queryable() .Where(p => p.PersonAliasId.HasValue && p.PersonAliasId.Value == personAlias && p.NotificationsEnabled && !string.IsNullOrEmpty(p.DeviceRegistrationId)) .Select(p => p.DeviceRegistrationId) .ToList(); if (devices.Any()) { // Create merge field dictionary var mergeObjects = recipient.CommunicationMergeValues(mergeFields); var message = ResolveText(communication.PushMessage, currentPerson, communication.EnabledLavaCommands, mergeObjects, publicAppRoot); var title = ResolveText(communication.PushTitle, currentPerson, communication.EnabledLavaCommands, mergeObjects, publicAppRoot); var sound = ResolveText(communication.PushSound, currentPerson, communication.EnabledLavaCommands, mergeObjects, publicAppRoot); var notification = new Message { RegistrationIds = devices.Distinct().ToList(), Notification = new FCM.Net.Notification { Title = title, Body = message, Sound = sound, } }; ResponseContent response = Utility.AsyncHelpers.RunSync(() => sender.SendAsync(notification)); bool failed = response.MessageResponse.Failure == devices.Count; var status = failed ? CommunicationRecipientStatus.Failed : CommunicationRecipientStatus.Delivered; if (failed) { recipient.StatusNote = "Firebase failed to notify devices"; } else { recipient.SendDateTime = RockDateTime.Now; } recipient.Status = status; recipient.TransportEntityTypeName = this.GetType().FullName; recipient.UniqueMessageId = response.MessageResponse.MulticastId; try { var historyService = new HistoryService(recipientRockContext); historyService.Add(new History { CreatedByPersonAliasId = communication.SenderPersonAliasId, EntityTypeId = personEntityTypeId, CategoryId = communicationCategoryId, EntityId = recipient.PersonAlias.PersonId, Verb = History.HistoryVerb.Sent.ConvertToString().ToUpper(), ChangeType = History.HistoryChangeType.Record.ToString(), ValueName = "Push Notification", Caption = message.Truncate(200), RelatedEntityTypeId = communicationEntityTypeId, RelatedEntityId = communication.Id }); } catch (Exception ex) { ExceptionLogService.LogException(ex, null); } } else { recipient.Status = CommunicationRecipientStatus.Failed; recipient.StatusNote = "No Personal Devices with Messaging Enabled"; } } catch (Exception ex) { recipient.Status = CommunicationRecipientStatus.Failed; recipient.StatusNote = "Firebase Exception: " + ex.Message; } } recipientRockContext.SaveChanges(); } else { recipientFound = false; } } } } }
private void LogError( Exception ex, int parentException, string status, System.Web.HttpContext context ) { try { // get the current user Rock.CMS.User user = Rock.CMS.UserService.GetCurrentUser(); // save the exception info to the db ExceptionLogService service = new ExceptionLogService(); ExceptionLog exceptionLog = new ExceptionLog(); ; exceptionLog.ParentId = parentException; exceptionLog.ExceptionDate = DateTime.Now; if ( ex.InnerException != null ) exceptionLog.HasInnerException = true; exceptionLog.Description = ex.Message; exceptionLog.StackTrace = ex.StackTrace; exceptionLog.Source = ex.Source; exceptionLog.StatusCode = status; if ( context.Items["Rock:SiteId"] != null ) exceptionLog.SiteId = Int32.Parse( context.Items["Rock:SiteId"].ToString() ); if ( context.Items["Rock:PageId"] != null ) exceptionLog.PageId = Int32.Parse( context.Items["Rock:PageId"].ToString() ); exceptionLog.ExceptionType = ex.GetType().Name; exceptionLog.PageUrl = context.Request.RawUrl; exceptionLog.QueryString = context.Request.QueryString.ToString(); // write cookies StringBuilder cookies = new StringBuilder(); cookies.Append("<table class=\"cookies\">"); foreach ( string cookie in context.Request.Cookies ) cookies.Append( "<tr><td><b>" + cookie + "</b></td><td>" + context.Request.Cookies[cookie].Value + "</td></tr>" ); cookies.Append( "</table>" ); exceptionLog.Cookies = cookies.ToString(); // write form items StringBuilder formItems = new StringBuilder(); cookies.Append( "<table class=\"formItems\">" ); foreach ( string formItem in context.Request.Form ) cookies.Append( "<tr><td><b>" + formItem + "</b></td><td>" + context.Request.Form[formItem].ToString() + "</td></tr>" ); cookies.Append( "</table>" ); exceptionLog.Form = formItems.ToString(); // write server vars StringBuilder serverVars = new StringBuilder(); cookies.Append( "<table class=\"server-variables\">" ); foreach ( string serverVar in context.Request.ServerVariables ) serverVars.Append( "<tr><td><b>" + serverVar + "</b></td><td>" + context.Request.ServerVariables[serverVar].ToString() + "</td></tr>" ); cookies.Append( "</table>" ); exceptionLog.ServerVariables = serverVars.ToString(); if (user != null) exceptionLog.PersonId = user.PersonId; service.Add( exceptionLog, null ); service.Save( exceptionLog, null ); // log inner exceptions if ( ex.InnerException != null ) LogError( ex.InnerException, exceptionLog.Id, status, context ); } catch ( Exception exception ) { // if you get an exception while logging an exception I guess you're hosed... } }
/// <summary> /// If the JWT is valid, the person claimed by that token will be returned. This method uses the configured validation parameters from the /// JSON Web Token Configuration Defined Type. /// </summary> /// <param name="jwtString"></param> /// <returns></returns> public static Person GetPersonFromJWTPersonSearchKey(string jwtString) { /* * 2/16/2022 MDP * * Note that this feature is not very well documented, see why at https://app.asana.com/0/0/1201611176520656/f * */ if (jwtString.IsNullOrWhiteSpace()) { return(null); } // Get the configs from the JSON Web Token Configuration defined values var configs = GetJwtConfigs(); if (configs == null) { return(null); } // The configs are required to specify a person search key type. The subject of the JWT should match a search key value so that we know // which Person the sender of the token claims to be. var rockContext = new RockContext(); var personSearchKeyService = new PersonSearchKeyService(rockContext); var query = personSearchKeyService.Queryable().AsNoTracking(); // Try each config in order, which are pre-ordered. The SearchTypeValueId is required (if null we couldn't match a person even if the // token validates) foreach (var config in configs.Where(c => c.SearchTypeValueId.HasValue)) { // If the token is valid, this method will return it as an object that we can pull subject from. Even if the token is valid, // if the subject is not set, we cannot match it to a person search key var jwt = ValidateToken(config, jwtString); if (jwt == null || jwt.Subject.IsNullOrWhiteSpace()) { continue; } // Get all the people (it is possible to get more than one) that have the matching search key var people = query .Where(psk => psk.SearchTypeValueId == config.SearchTypeValueId.Value && psk.SearchValue == jwt.Subject) .Select(psk => psk.PersonAlias.Person) .ToList(); // If there are more than one match, then the Rock admin needs to take action and fix the data as this could be a security hole and we // cannot tell which person is the bearer of the JWT if (people.Count > 1) { ExceptionLogService.LogException($"{people.Count} people matched the JWT subject '{jwt.Subject}' for search value id '{config.SearchTypeValueId.Value}'"); continue; } // If there is a single match, then this method is done and there is no need to check more configurations if (people.Count == 1) { return(people.Single()); } } // None of the configurations was able to validate the token and find a matching person return(null); }