/// <summary>
        /// Processes the binary file request.
        /// </summary>
        /// <param name="context">The context.</param>
        private void ProcessBinaryFileRequest(HttpContext context)
        {
            int  fileId   = context.Request.QueryString["id"].AsInteger();
            Guid fileGuid = context.Request.QueryString["guid"].AsGuid();

            if (fileId == 0 && fileGuid.Equals(Guid.Empty))
            {
                SendNotFound(context);
            }

            var rockContext = new RockContext();

            var binaryFileQuery = new BinaryFileService(rockContext).Queryable();

            if (fileGuid != Guid.Empty)
            {
                binaryFileQuery = binaryFileQuery.Where(a => a.Guid == fileGuid);
            }
            else
            {
                binaryFileQuery = binaryFileQuery.Where(a => a.Id == fileId);
            }

            //// get just the binaryFileMetaData (not the file content) just in case we can get the filecontent faster from the cache
            //// a null ModifiedDateTime shouldn't happen, but just in case, set it to DateTime.MaxValue so we error on the side of not getting it from the cache
            var binaryFileMetaData = binaryFileQuery.Select(a => new
            {
                BinaryFileType_AllowCaching         = a.BinaryFileType.AllowCaching,
                BinaryFileType_RequiresViewSecurity = a.BinaryFileType.RequiresViewSecurity,
                ModifiedDateTime = a.ModifiedDateTime ?? DateTime.MaxValue,
                a.MimeType,
                a.FileName
            }).FirstOrDefault();

            if (binaryFileMetaData == null)
            {
                SendNotFound(context);
                return;
            }

            //// if the binaryFile's BinaryFileType requires view security, check security
            //// note: we put a RequiresViewSecurity flag on BinaryFileType because checking security for every image would be slow (~40ms+ per image request)
            if (binaryFileMetaData.BinaryFileType_RequiresViewSecurity)
            {
                var        currentUser    = new UserLoginService(rockContext).GetByUserName(UserLogin.GetCurrentUserName());
                Person     currentPerson  = currentUser != null ? currentUser.Person : null;
                BinaryFile binaryFileAuth = new BinaryFileService(rockContext).Queryable("BinaryFileType").First(a => a.Guid == fileGuid || a.Id == fileId);
                if (!binaryFileAuth.IsAuthorized(Authorization.VIEW, currentPerson))
                {
                    SendNotAuthorized(context);
                    return;
                }
            }


            Stream fileContent = null;

            try
            {
                // Is it cached
                string cacheName          = UrlQueryToCachedFileName(context.Request.QueryString, binaryFileMetaData.MimeType);
                string physCachedFilePath = context.Request.MapPath(string.Format("~/App_Data/Cache/{0}", cacheName));
                if (binaryFileMetaData.BinaryFileType_AllowCaching && File.Exists(physCachedFilePath))
                {
                    //// Compare the File's Creation DateTime (which comes from the OS's clock), adjust it for the Rock OrgTimeZone, then compare to BinaryFile's ModifiedDateTime (which is already in OrgTimeZone).
                    //// If the BinaryFile record in the database is less recent than the last time this was cached, it is safe to use the Cached version.
                    //// NOTE: A BinaryFile record is typically just added and never modified (a modify is just creating a new BinaryFile record and deleting the old one), so the cached version will probably always be the correct choice.
                    DateTime cachedFileDateTime = RockDateTime.ConvertLocalDateTimeToRockDateTime(File.GetCreationTime(physCachedFilePath));
                    if (binaryFileMetaData.ModifiedDateTime < cachedFileDateTime)
                    {
                        // NOTE: the cached file has already been resized (the size is part of the cached file's filename), so we don't need to resize it again
                        fileContent = FetchFromCache(physCachedFilePath);
                    }
                }

                if (fileContent == null)
                {
                    // If we didn't get it from the cache, get it from the binaryFileService
                    BinaryFile binaryFile = GetFromBinaryFileService(context, fileId, fileGuid);

                    if (binaryFile != null)
                    {
                        fileContent = binaryFile.ContentStream;
                    }

                    // If we got the image from the binaryFileService, it might need to be resized and cached
                    if (fileContent != null)
                    {
                        // If more than 1 query string param is passed in, or the mime type is TIFF, assume resize is needed
                        // Note: we force "image/tiff" to get resized so that it gets converted into a jpg (browsers don't like tiffs)
                        if (context.Request.QueryString.Count > 1 || binaryFile.MimeType == "image/tiff")
                        {
                            // if it isn't an SVG file, do a Resize
                            if (binaryFile.MimeType != "image/svg+xml")
                            {
                                fileContent = GetResized(context.Request.QueryString, fileContent);
                            }
                        }

                        if (binaryFileMetaData.BinaryFileType_AllowCaching)
                        {
                            Cache(fileContent, physCachedFilePath);
                        }
                    }
                }

                if (fileContent == null)
                {
                    // if we couldn't get the file from the binaryFileServie or the cache, respond with NotFound
                    SendNotFound(context);
                    return;
                }

                // respond with File
                if (binaryFileMetaData.BinaryFileType_AllowCaching)
                {
                    // if binaryFileType is set to allowcaching, also tell the browser to cache it for 365 days
                    context.Response.Cache.SetLastModified(binaryFileMetaData.ModifiedDateTime);
                    context.Response.Cache.SetMaxAge(new TimeSpan(365, 0, 0, 0));
                }

                context.Response.ContentType = binaryFileMetaData.MimeType != "image/tiff" ? binaryFileMetaData.MimeType : "image/jpg";

                using (var responseStream = fileContent)
                {
                    context.Response.AddHeader("content-disposition", "inline;filename=" + binaryFileMetaData.FileName);
                    if (responseStream.CanSeek)
                    {
                        responseStream.Seek(0, SeekOrigin.Begin);
                    }
                    responseStream.CopyTo(context.Response.OutputStream);
                    context.Response.Flush();
                }
            }
            finally
            {
                if (fileContent != null)
                {
                    fileContent.Dispose();
                }
            }
        }
Exemple #2
0
        public IHttpActionResult PostInteractions([FromBody] List <MobileInteractionSession> sessions, Guid?personalDeviceGuid = null)
        {
            var person    = GetPerson();
            var ipAddress = System.Web.HttpContext.Current?.Request?.UserHostAddress;

            using (var rockContext = new Data.RockContext())
            {
                var interactionChannelService   = new InteractionChannelService(rockContext);
                var interactionComponentService = new InteractionComponentService(rockContext);
                var interactionSessionService   = new InteractionSessionService(rockContext);
                var interactionService          = new InteractionService(rockContext);
                var userLoginService            = new UserLoginService(rockContext);
                var channelMediumTypeValue      = DefinedValueCache.Get(SystemGuid.DefinedValue.INTERACTIONCHANNELTYPE_WEBSITE);
                var pageEntityTypeId            = EntityTypeCache.Get(typeof(Model.Page)).Id;

                //
                // Check to see if we have a site and the API key is valid.
                //
                if (MobileHelper.GetCurrentApplicationSite() == null)
                {
                    return(StatusCode(System.Net.HttpStatusCode.Forbidden));
                }

                //
                // Get the personal device identifier if they provided it's unique identifier.
                //
                int?personalDeviceId = null;
                if (personalDeviceGuid.HasValue)
                {
                    personalDeviceId = new PersonalDeviceService(rockContext).GetId(personalDeviceGuid.Value);
                }

                //
                // Create a quick way to cache data since we have to loop twice.
                //
                var interactionComponentLookup = new Dictionary <string, int>();

                //
                // Helper method to get a cache key for looking up the component Id.
                //
                string GetComponentCacheKey(MobileInteraction mi)
                {
                    return($"{mi.AppId}:{mi.PageGuid}:{mi.ChannelGuid}:{mi.ChannelId}:{mi.ComponentId}:{mi.ComponentName}");
                }

                //
                // Interactions Components will now try to load from cache which
                // causes problems if we are inside a transaction. So first loop through
                // everything and make sure all our components and channels exist.
                //
                var prePassInteractions = sessions.SelectMany(a => a.Interactions)
                                          .DistinctBy(a => GetComponentCacheKey(a));

                //
                // It's safe to do this pre-pass outside the transaction since we are just creating
                // the channels and components (if necessary), which is going to have to be done at
                // at some point no matter what.
                //
                foreach (var mobileInteraction in prePassInteractions)
                {
                    //
                    // Lookup the interaction channel, and create it if it doesn't exist
                    //
                    if (mobileInteraction.AppId.HasValue && mobileInteraction.PageGuid.HasValue)
                    {
                        var site = SiteCache.Get(mobileInteraction.AppId.Value);
                        var page = PageCache.Get(mobileInteraction.PageGuid.Value);

                        if (site == null || page == null)
                        {
                            continue;
                        }

                        //
                        // Try to find an existing interaction channel.
                        //
                        var interactionChannelId = InteractionChannelCache.GetChannelIdByTypeIdAndEntityId(channelMediumTypeValue.Id, site.Id, site.Name, pageEntityTypeId, null);

                        //
                        // Get an existing or create a new component.
                        //
                        var interactionComponentId = InteractionComponentCache.GetComponentIdByChannelIdAndEntityId(interactionChannelId, page.Id, page.InternalName);

                        interactionComponentLookup.AddOrReplace(GetComponentCacheKey(mobileInteraction), interactionComponentId);
                    }
                    else if (mobileInteraction.ChannelId.HasValue || mobileInteraction.ChannelGuid.HasValue)
                    {
                        int?interactionChannelId = null;

                        if (mobileInteraction.ChannelId.HasValue)
                        {
                            interactionChannelId = mobileInteraction.ChannelId.Value;
                        }
                        else if (mobileInteraction.ChannelGuid.HasValue)
                        {
                            interactionChannelId = InteractionChannelCache.Get(mobileInteraction.ChannelGuid.Value)?.Id;
                        }

                        if (interactionChannelId.HasValue)
                        {
                            if (mobileInteraction.ComponentId.HasValue)
                            {
                                // Use the provided component identifier.
                                interactionComponentLookup.AddOrReplace(GetComponentCacheKey(mobileInteraction), mobileInteraction.ComponentId.Value);
                            }
                            else if (mobileInteraction.ComponentName.IsNotNullOrWhiteSpace())
                            {
                                int interactionComponentId;

                                // Get or create a new component with the details we have.
                                if (mobileInteraction.ComponentEntityId.HasValue)
                                {
                                    interactionComponentId = InteractionComponentCache.GetComponentIdByChannelIdAndEntityId(interactionChannelId.Value, mobileInteraction.ComponentEntityId, mobileInteraction.ComponentName);
                                }
                                else
                                {
                                    var interactionComponent = interactionComponentService.GetComponentByComponentName(interactionChannelId.Value, mobileInteraction.ComponentName);

                                    rockContext.SaveChanges();

                                    interactionComponentId = interactionComponent.Id;
                                }

                                interactionComponentLookup.AddOrReplace(GetComponentCacheKey(mobileInteraction), interactionComponentId);
                            }
                        }
                    }
                }

                //
                // Now wrap the actual interaction creation inside a transaction. We should
                // probably move this so it uses the InteractionTransaction class for better
                // performance. This is so we can inform the client that either everything
                // saved or that nothing saved. No partial saves here.
                //
                rockContext.WrapTransaction(() =>
                {
                    foreach (var mobileSession in sessions)
                    {
                        var interactionGuids         = mobileSession.Interactions.Select(i => i.Guid).ToList();
                        var existingInteractionGuids = interactionService.Queryable()
                                                       .Where(i => interactionGuids.Contains(i.Guid))
                                                       .Select(i => i.Guid)
                                                       .ToList();

                        //
                        // Loop through all interactions that don't already exist and add each one.
                        //
                        foreach (var mobileInteraction in mobileSession.Interactions.Where(i => !existingInteractionGuids.Contains(i.Guid)))
                        {
                            string cacheKey = GetComponentCacheKey(mobileInteraction);

                            if (!interactionComponentLookup.ContainsKey(cacheKey))
                            {
                                // Shouldn't happen, but just in case.
                                continue;
                            }

                            var interactionComponentId = interactionComponentLookup[cacheKey];

                            //
                            // Add the interaction
                            //
                            var interaction = interactionService.CreateInteraction(interactionComponentId,
                                                                                   mobileInteraction.EntityId,
                                                                                   mobileInteraction.Operation,
                                                                                   mobileInteraction.Summary,
                                                                                   mobileInteraction.Data,
                                                                                   person?.PrimaryAliasId,
                                                                                   RockDateTime.ConvertLocalDateTimeToRockDateTime(mobileInteraction.DateTime.LocalDateTime),
                                                                                   mobileSession.Application,
                                                                                   mobileSession.OperatingSystem,
                                                                                   mobileSession.ClientType,
                                                                                   null,
                                                                                   ipAddress,
                                                                                   mobileSession.Guid);

                            interaction.Guid                  = mobileInteraction.Guid;
                            interaction.PersonalDeviceId      = personalDeviceId;
                            interaction.RelatedEntityTypeId   = mobileInteraction.RelatedEntityTypeId;
                            interaction.RelatedEntityId       = mobileInteraction.RelatedEntityId;
                            interaction.ChannelCustom1        = mobileInteraction.ChannelCustom1;
                            interaction.ChannelCustom2        = mobileInteraction.ChannelCustom2;
                            interaction.ChannelCustomIndexed1 = mobileInteraction.ChannelCustomIndexed1;

                            interactionService.Add(interaction);

                            // Attempt to process this as a communication interaction.
                            ProcessCommunicationInteraction(mobileSession, mobileInteraction, rockContext);
                        }
                    }

                    rockContext.SaveChanges();
                });
            }

            return(Ok());
        }
Exemple #3
0
    /// <summary>
    /// Processes for recipient.
    /// </summary>
    /// <param name="eventType">Type of the event.</param>
    /// <param name="communicationRecipientGuid">The communication recipient unique identifier.</param>
    /// <param name="rockContext">The rock context.</param>
    private void ProcessForReceipent(string eventType, Guid?communicationRecipientGuid, Rock.Data.RockContext rockContext)
    {
        if (!communicationRecipientGuid.HasValue)
        {
            return;
        }

        var communicationRecipient = new CommunicationRecipientService(rockContext).Get(communicationRecipientGuid.Value);

        if (communicationRecipient != null && communicationRecipient.Communication != null)
        {
            int      secs = request.Form["timestamp"].AsInteger();
            DateTime ts   = RockDateTime.ConvertLocalDateTimeToRockDateTime(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(secs).ToLocalTime());

            var communicationGuid = Rock.SystemGuid.InteractionChannel.COMMUNICATION.AsGuid();

            InteractionComponent interactionComponent = new InteractionComponentService(rockContext)
                                                        .GetComponentByEntityId(communicationGuid, communicationRecipient.CommunicationId, communicationRecipient.Communication.Subject);

            rockContext.SaveChanges();

            var interactionService = new InteractionService(rockContext);

            switch (eventType)
            {
            case "delivered":
                communicationRecipient.Status     = CommunicationRecipientStatus.Delivered;
                communicationRecipient.StatusNote = string.Format("Confirmed delivered by Mailgun at {0}", ts.ToString());
                break;

            case "opened":
                communicationRecipient.Status         = CommunicationRecipientStatus.Opened;
                communicationRecipient.OpenedDateTime = ts;
                communicationRecipient.OpenedClient   = string.Format(
                    "{0} {1} ({2})",
                    request.Form["client-os"] ?? "unknown",
                    request.Form["client-name"] ?? "unknown",
                    request.Form["device-type"] ?? "unknown");

                if (interactionComponent != null)
                {
                    interactionService.AddInteraction(
                        interactionComponent.Id,
                        communicationRecipient.Id,
                        "Opened",
                        string.Empty,
                        communicationRecipient.PersonAliasId,
                        ts,
                        request.Form["client-name"],
                        request.Form["client-os"],
                        request.Form["client-type"],
                        request.Form["device-type"],
                        request.Form["ip"],
                        null);
                }

                break;

            case "clicked":
                if (interactionComponent != null)
                {
                    interactionService.AddInteraction(
                        interactionComponent.Id,
                        communicationRecipient.Id,
                        "Click",
                        request.Form["url"],
                        communicationRecipient.PersonAliasId,
                        ts,
                        request.Form["client-name"],
                        request.Form["client-os"],
                        request.Form["client-type"],
                        request.Form["device-type"],
                        request.Form["ip"],
                        null);
                }

                break;

            case "complained":
                break;

            case "unsubscribed":
                break;

            case "dropped":
                communicationRecipient.Status     = CommunicationRecipientStatus.Failed;
                communicationRecipient.StatusNote = request.Form["description"];
                break;

            case "bounced":
                communicationRecipient.Status     = CommunicationRecipientStatus.Failed;
                communicationRecipient.StatusNote = request.Form["notification"];

                Rock.Communication.Email.ProcessBounce(
                    request.Form["recipient"],
                    Rock.Communication.BounceType.HardBounce,
                    request.Form["notification"],
                    ts);

                break;
            }

            rockContext.SaveChanges();
        }
    }
Exemple #4
0
    /// <summary>
    /// Processes for recipient.
    /// </summary>
    /// <param name="eventType">Type of the event.</param>
    /// <param name="communicationRecipientGuid">The communication recipient unique identifier.</param>
    /// <param name="rockContext">The rock context.</param>
    private void ProcessForRecipient(Guid?communicationRecipientGuid, Rock.Data.RockContext rockContext, SendGridEvent payload)
    {
        RockLogger.Log.Debug(RockLogDomains.Communications, "ProcessForRecipient {@payload}", payload);

        if (!communicationRecipientGuid.HasValue)
        {
            return;
        }

        var communicationRecipient = new CommunicationRecipientService(rockContext).Get(communicationRecipientGuid.Value);

        if (communicationRecipient != null && communicationRecipient.Communication != null)
        {
            var communicationGuid    = Rock.SystemGuid.InteractionChannel.COMMUNICATION.AsGuid();
            var interactionComponent = new InteractionComponentService(rockContext)
                                       .GetComponentByEntityId(communicationGuid, communicationRecipient.CommunicationId, communicationRecipient.Communication.Subject);

            rockContext.SaveChanges();

            var      interactionService = new InteractionService(rockContext);
            DateTime timeStamp          = RockDateTime.ConvertLocalDateTimeToRockDateTime(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(payload.Timestamp).ToLocalTime());

            switch (payload.EventType)
            {
            case "processed":
                // Do nothing.
                break;

            case "dropped":
                communicationRecipient.Status     = CommunicationRecipientStatus.Failed;
                communicationRecipient.StatusNote = payload.EventTypeReason;

                if (payload.EventTypeReason == "Bounced Address")
                {
                    Rock.Communication.Email.ProcessBounce(
                        payload.Email,
                        Rock.Communication.BounceType.HardBounce,
                        payload.EventTypeReason,
                        timeStamp);
                }

                break;

            case "delivered":
                communicationRecipient.Status     = CommunicationRecipientStatus.Delivered;
                communicationRecipient.StatusNote = string.Format("Confirmed delivered by SendGrid at {0}", timeStamp.ToString());
                break;

            case "deferred":
                // TODO: handle deferred.
                break;

            case "bounce":
                communicationRecipient.Status     = CommunicationRecipientStatus.Failed;
                communicationRecipient.StatusNote = payload.EventTypeReason + payload.ServerResponse;

                Rock.Communication.Email.ProcessBounce(
                    payload.Email,
                    Rock.Communication.BounceType.HardBounce,
                    payload.EventTypeReason,
                    timeStamp);
                break;

            case "blocked":
                // TODO: handle blocked.
                break;

            case "open":
                communicationRecipient.Status         = CommunicationRecipientStatus.Opened;
                communicationRecipient.OpenedDateTime = timeStamp;
                communicationRecipient.OpenedClient   = string.Format(
                    "{0} {1} ({2})",
                    payload.ClientOs ?? "unknown",
                    payload.ClientBrowser ?? "unknown",
                    payload.ClientDeviceType ?? "unknown");

                if (interactionComponent != null)
                {
                    interactionService.AddInteraction(
                        interactionComponent.Id,
                        communicationRecipient.Id,
                        "Opened",
                        payload.SendGridEventId,
                        communicationRecipient.PersonAliasId,
                        timeStamp,
                        payload.ClientBrowser,
                        payload.ClientOs,
                        payload.ClientDeviceType,
                        payload.ClientDeviceBrand,
                        payload.IpAddress,
                        null);
                }

                break;

            case "click":
                if (interactionComponent != null)
                {
                    interactionService.AddInteraction(
                        interactionComponent.Id,
                        communicationRecipient.Id,
                        "Click",
                        payload.Url,
                        communicationRecipient.PersonAliasId,
                        timeStamp,
                        payload.ClientBrowser,
                        payload.ClientOs,
                        payload.ClientDeviceType,
                        payload.ClientDeviceBrand,
                        payload.IpAddress,
                        null);
                }

                break;

            case "spamreport":
            case "unsubscribe":
            case "group_unsubscribe":
            case "group_resubscribe":
                // Do nothing.
                break;
            }

            rockContext.SaveChanges();
        }
    }
Exemple #5
0
    /// <summary>
    /// Processes for workflow.
    /// </summary>
    /// <param name="eventType">Type of the event.</param>
    /// <param name="actionGuid">The action unique identifier.</param>
    /// <param name="rockContext">The rock context.</param>
    private void ProcessForWorkflow(Guid?actionGuid, Rock.Data.RockContext rockContext)
    {
        string status = string.Empty;

        switch (mailgunRequestPayload.EventType)
        {
        case "complained":
        case "unsubscribed":
        case "delivered":
            status = SendEmailWithEvents.SENT_STATUS;
            break;

        case "clicked":
            status = SendEmailWithEvents.CLICKED_STATUS;
            break;

        case "opened":
            status = SendEmailWithEvents.OPENED_STATUS;
            break;

        case "dropped":
            status = SendEmailWithEvents.FAILED_STATUS;
            break;

        case "bounced":
            status = SendEmailWithEvents.FAILED_STATUS;
            string message = mailgunRequestPayload.Notification.IsNotNullOrWhiteSpace() ? mailgunRequestPayload.Notification : mailgunRequestPayload.Description;

            Rock.Communication.Email.ProcessBounce(
                mailgunRequestPayload.Recipient,
                Rock.Communication.BounceType.HardBounce,
                message,
                RockDateTime.ConvertLocalDateTimeToRockDateTime(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(mailgunRequestPayload.TimeStamp).ToLocalTime()));
            break;

        case "failed":
            // The new mailgun API bundles undeliverable mail into a failed event. The reason (e.g. bounced) is in a seperate property called reason.
            if (mailgunRequestPayload.EventTypeReason.IsNotNullOrWhiteSpace())
            {
                switch (mailgunRequestPayload.EventTypeReason)
                {
                case "bounce":
                case "suppress-bounce":
                    status = SendEmailWithEvents.FAILED_STATUS;

                    Rock.Communication.Email.ProcessBounce(
                        mailgunRequestPayload.Recipient,
                        Rock.Communication.BounceType.HardBounce,
                        mailgunRequestPayload.Description,
                        RockDateTime.ConvertLocalDateTimeToRockDateTime(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(mailgunRequestPayload.TimeStamp).ToLocalTime()));
                    break;

                default:
                    status = SendEmailWithEvents.FAILED_STATUS;
                    break;
                }
            }
            break;
        }

        if (actionGuid != null && !string.IsNullOrWhiteSpace(status))
        {
            SendEmailWithEvents.UpdateEmailStatus(actionGuid.Value, status, mailgunRequestPayload.EventType, rockContext, true);
        }
    }
Exemple #6
0
    /// <summary>
    /// Processes for recipient.
    /// </summary>
    /// <param name="eventType">Type of the event.</param>
    /// <param name="communicationRecipientGuid">The communication recipient unique identifier.</param>
    /// <param name="rockContext">The rock context.</param>
    private void ProcessForReceipent(Guid?communicationRecipientGuid, Rock.Data.RockContext rockContext)
    {
        if (!communicationRecipientGuid.HasValue)
        {
            return;
        }

        var communicationRecipient = new CommunicationRecipientService(rockContext).Get(communicationRecipientGuid.Value);

        if (communicationRecipient != null && communicationRecipient.Communication != null)
        {
            var communicationGuid    = Rock.SystemGuid.InteractionChannel.COMMUNICATION.AsGuid();
            var interactionComponent = new InteractionComponentService(rockContext)
                                       .GetComponentByEntityId(communicationGuid, communicationRecipient.CommunicationId, communicationRecipient.Communication.Subject);

            rockContext.SaveChanges();

            var      interactionService = new InteractionService(rockContext);
            DateTime timeStamp          = RockDateTime.ConvertLocalDateTimeToRockDateTime(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(mailgunRequestPayload.TimeStamp).ToLocalTime());

            switch (mailgunRequestPayload.EventType)
            {
            case "delivered":
                communicationRecipient.Status     = CommunicationRecipientStatus.Delivered;
                communicationRecipient.StatusNote = string.Format("Confirmed delivered by Mailgun at {0}", timeStamp.ToString());
                break;

            case "opened":
                communicationRecipient.Status         = CommunicationRecipientStatus.Opened;
                communicationRecipient.OpenedDateTime = timeStamp;
                communicationRecipient.OpenedClient   = string.Format(
                    "{0} {1} ({2})",
                    mailgunRequestPayload.ClientOs ?? "unknown",
                    mailgunRequestPayload.ClientName ?? "unknown",
                    mailgunRequestPayload.DeviceType ?? "unknown");

                if (interactionComponent != null)
                {
                    interactionService.AddInteraction(
                        interactionComponent.Id,
                        communicationRecipient.Id,
                        "Opened",
                        string.Empty,
                        communicationRecipient.PersonAliasId,
                        timeStamp,
                        mailgunRequestPayload.ClientName,
                        mailgunRequestPayload.ClientOs,
                        mailgunRequestPayload.ClientType,
                        mailgunRequestPayload.DeviceType,
                        mailgunRequestPayload.Ip,
                        null);
                }

                break;

            case "clicked":
                if (interactionComponent != null)
                {
                    interactionService.AddInteraction(
                        interactionComponent.Id,
                        communicationRecipient.Id,
                        "Click",
                        mailgunRequestPayload.Url,
                        communicationRecipient.PersonAliasId,
                        timeStamp,
                        mailgunRequestPayload.ClientName,
                        mailgunRequestPayload.ClientOs,
                        mailgunRequestPayload.ClientType,
                        mailgunRequestPayload.DeviceType,
                        mailgunRequestPayload.Ip,
                        null);
                }

                break;

            case "complained":
                break;

            case "unsubscribed":
                break;

            case "dropped":
                communicationRecipient.Status     = CommunicationRecipientStatus.Failed;
                communicationRecipient.StatusNote = mailgunRequestPayload.Description;
                break;

            case "bounced":
                communicationRecipient.Status     = CommunicationRecipientStatus.Failed;
                communicationRecipient.StatusNote = mailgunRequestPayload.Notification;

                Rock.Communication.Email.ProcessBounce(
                    mailgunRequestPayload.Recipient,
                    Rock.Communication.BounceType.HardBounce,
                    mailgunRequestPayload.Notification,
                    timeStamp);
                break;

            case "failed":
                // The new mailgun API bundles undeliverable mail into a failed event. The reason (e.g. bounced) is in a seperate property called reason.
                if (mailgunRequestPayload.EventTypeReason.IsNotNullOrWhiteSpace())
                {
                    switch (mailgunRequestPayload.EventTypeReason)
                    {
                    case "bounce":
                    case "suppress-bounce":
                        communicationRecipient.Status     = CommunicationRecipientStatus.Failed;
                        communicationRecipient.StatusNote = mailgunRequestPayload.Description;

                        Rock.Communication.Email.ProcessBounce(
                            mailgunRequestPayload.Recipient,
                            Rock.Communication.BounceType.HardBounce,
                            mailgunRequestPayload.Description,
                            timeStamp);
                        break;

                    default:
                        communicationRecipient.Status     = CommunicationRecipientStatus.Failed;
                        communicationRecipient.StatusNote = mailgunRequestPayload.Description;
                        break;
                    }
                }

                break;
            }

            rockContext.SaveChanges();
        }
    }
        public void Execute(IJobExecutionContext context)
        {
            var      rockContext = new RockContext();
            var      dataMap     = context.JobDetail.JobDataMap;
            DateTime?lastRun     = null;

            if (context.PreviousFireTimeUtc.HasValue)
            {
                lastRun = RockDateTime.ConvertLocalDateTimeToRockDateTime(context.PreviousFireTimeUtc.Value.LocalDateTime);
            }

            var systemEmailGuid = dataMap.GetString("Email").AsGuidOrNull();

            string appRoot = Rock.Web.Cache.GlobalAttributesCache.Read().GetValue("PublicApplicationRoot");

            if (appRoot.IsNullOrWhiteSpace())
            {
                throw new Exception("Couldn't fetch application root!");
            }

            if (systemEmailGuid == null)
            {
                throw new Exception("A system email template needs to be set.");
            }

            var systemEmailTemplate = new SystemEmailService(rockContext).Get(systemEmailGuid.Value);

            if (systemEmailTemplate == null)
            {
                throw new Exception("The system email template setting is not a valid system email template.");
            }

            var cutOffHours = dataMap.GetString("IncludeHours").AsIntegerOrNull();

            if (!cutOffHours.HasValue)
            {
                throw new Exception("A cutoff period needs to be set.");
            }

            if (lastRun == null)
            {
                lastRun = RockDateTime.Now.AddHours(-1 * cutOffHours.Value);
            }

            var connectionRequestService = new ConnectionRequestService(rockContext);
            var openConnectionRequests   =
                connectionRequestService.Queryable()
                .AsNoTracking()
                .Where(cr => cr.CreatedDateTime >= lastRun && cr.ConnectionState != ConnectionState.Connected && cr.ConnectorPersonAliasId != null);

            if (!openConnectionRequests.Any())
            {
                context.Result = "There are no open and assigned connection requests to send alerts for";
                return;
            }

            var groupedRequests = openConnectionRequests
                                  .ToList()
                                  .GroupBy(cr => cr.ConnectorPersonAlias);

            int mailedCount = 0;

            foreach (var connectionRequestGrouping in groupedRequests)
            {
                if (connectionRequestGrouping.Key == null)
                {
                    continue;
                }
                var connectionRequests = connectionRequestGrouping.ToList();

                var mergeFields = new Dictionary <string, object>
                {
                    { "ConnectionRequests", connectionRequests },
                    { "Person", connectionRequestGrouping.Key.Person }
                };

                var recipients = new List <string> {
                    connectionRequestGrouping.Key.Person.Email
                };

                Email.Send(systemEmailTemplate.From.ResolveMergeFields(mergeFields), systemEmailTemplate.FromName.ResolveMergeFields(mergeFields), systemEmailTemplate.Subject.ResolveMergeFields(mergeFields), recipients, systemEmailTemplate.Body.ResolveMergeFields(mergeFields), appRoot, null);
                mailedCount++;
            }
            context.Result = string.Format("{0} reminders were sent ", mailedCount);
        }
        /// <summary>
        /// Processes the binary file request.
        /// </summary>
        /// <param name="context">The context.</param>
        private void ProcessBinaryFileRequest(HttpContext context, RockContext rockContext)
        {
            int  fileId   = context.Request.QueryString["id"].AsInteger();
            Guid fileGuid = context.Request.QueryString["guid"].AsGuid();

            if (fileId == 0 && fileGuid == Guid.Empty)
            {
                SendBadRequest(context, "File id key must be a guid or an int.");
                return;
            }

            var binaryFileQuery = new BinaryFileService(rockContext).Queryable();

            if (fileGuid != Guid.Empty)
            {
                binaryFileQuery = binaryFileQuery.Where(a => a.Guid == fileGuid);
            }
            else
            {
                binaryFileQuery = binaryFileQuery.Where(a => a.Id == fileId);
            }

            //// get just the binaryFileMetaData (not the file content) just in case we can get the filecontent faster from the cache
            //// a null ModifiedDateTime shouldn't happen, but just in case, set it to DateTime.MaxValue so we error on the side of not getting it from the cache
            var binaryFileMetaData = binaryFileQuery.Select(a => new
            {
                a.Id,
                BinaryFileType_CacheToServerFileSystem = a.BinaryFileType.CacheToServerFileSystem,
                BinaryFileType_RequiresViewSecurity    = a.BinaryFileType.RequiresViewSecurity,
                a.BinaryFileTypeId,
                ModifiedDateTime = a.ModifiedDateTime ?? DateTime.MaxValue,
                a.MimeType,
                a.FileName
            }).FirstOrDefault();

            if (binaryFileMetaData == null)
            {
                SendNotFound(context);
                return;
            }

            // Add cache validation headers
            context.Response.AddHeader("Last-Modified", binaryFileMetaData.ModifiedDateTime.ToUniversalTime().ToString("R"));
            context.Response.AddHeader("ETag", binaryFileMetaData.ModifiedDateTime.ToString().XxHash());

            //// if the binaryFile's BinaryFileType requires view security, check security
            //// note: we put a RequiresViewSecurity flag on BinaryFileType because checking security for every image would be slow (~40ms+ per image request)
            if (binaryFileMetaData.BinaryFileType_RequiresViewSecurity)
            {
                var        currentUser    = new UserLoginService(rockContext).GetByUserName(UserLogin.GetCurrentUserName());
                Person     currentPerson  = currentUser != null ? currentUser.Person : null;
                BinaryFile binaryFileAuth = new BinaryFileService(rockContext).Queryable("BinaryFileType").AsNoTracking().First(a => a.Id == binaryFileMetaData.Id);
                if (!binaryFileAuth.IsAuthorized(Authorization.VIEW, currentPerson))
                {
                    SendNotAuthorized(context);
                    return;
                }
            }


            Stream fileContent = null;

            try
            {
                // Is it cached
                string cacheName          = UrlQueryToCachedFileName(context.Request.QueryString, binaryFileMetaData.MimeType);
                string physCachedFilePath = context.Request.MapPath(string.Format("~/App_Data/Cache/{0}", cacheName));
                if (binaryFileMetaData.BinaryFileType_CacheToServerFileSystem && File.Exists(physCachedFilePath))
                {
                    //// Compare the File's LastWrite DateTime (which comes from the OS's clock), adjust it for the Rock OrgTimeZone, then compare to BinaryFile's ModifiedDateTime (which is already in OrgTimeZone).
                    //// If the BinaryFile record in the database is less recent than the last time this was cached, it is safe to use the Cached version.
                    //// NOTE: A BinaryFile record is typically just added and never modified (a modify is just creating a new BinaryFile record and deleting the old one), so the cached version will probably always be the correct choice.
                    DateTime cachedFileDateTime = RockDateTime.ConvertLocalDateTimeToRockDateTime(File.GetLastWriteTime(physCachedFilePath));
                    if (binaryFileMetaData.ModifiedDateTime < cachedFileDateTime)
                    {
                        // NOTE: the cached file has already been resized (the size is part of the cached file's filename), so we don't need to resize it again
                        fileContent = FetchFromCache(physCachedFilePath);
                    }
                }

                if (fileContent == null)
                {
                    // If we didn't get it from the cache, get it from the binaryFileService
                    BinaryFile binaryFile = GetFromBinaryFileService(context, binaryFileMetaData.Id, rockContext);

                    if (binaryFile != null)
                    {
                        fileContent = binaryFile.ContentStream;
                    }

                    // If we got the image from the binaryFileService, it might need to be resized and cached
                    if (fileContent != null)
                    {
                        // If more than 1 query string param is passed in, or the mime type is TIFF, assume resize is needed
                        // Note: we force "image/tiff" to get resized so that it gets converted into a jpg (browsers don't like tiffs)
                        if (context.Request.QueryString.Count > GetQueryCount(context) || binaryFile.MimeType == "image/tiff")
                        {
                            // if it isn't an SVG file, do a Resize
                            if (binaryFile.MimeType != "image/svg+xml")
                            {
                                fileContent = GetResized(context.Request.QueryString, fileContent);
                            }
                        }

                        if (binaryFileMetaData.BinaryFileType_CacheToServerFileSystem)
                        {
                            Cache(fileContent, physCachedFilePath);

                            // Reset stream
                            if (fileContent.CanSeek)
                            {
                                fileContent.Seek(0, SeekOrigin.Begin);
                            }
                            else
                            {
                                fileContent = FetchFromCache(physCachedFilePath);
                            }
                        }
                    }
                }

                if (fileContent == null)
                {
                    // if we couldn't get the file from the binaryFileServie or the cache, respond with NotFound
                    SendNotFound(context);
                    return;
                }

                // respond with File
                if (binaryFileMetaData.BinaryFileTypeId != null)
                {
                    var binaryFileCache = BinaryFileTypeCache.Get(binaryFileMetaData.BinaryFileTypeId.Value);
                    binaryFileCache.CacheControlHeader.SetupHttpCachePolicy(context.Response.Cache);
                }

                // set the mime-type to that of the binary file
                context.Response.ContentType = binaryFileMetaData.MimeType != "image/tiff" ? binaryFileMetaData.MimeType : "image/jpg";

                // check that the format of the image wasn't changed by a format query parm if so adjust the mime-type to reflect the conversion
                if (context.Request["format"].IsNotNullOrWhiteSpace())
                {
                    switch (context.Request["format"])
                    {
                    case "png":
                    {
                        context.Response.ContentType = "image/png";
                        break;
                    }

                    case "gif":
                    {
                        context.Response.ContentType = "image/gif";
                        break;
                    }

                    case "jpg":
                    {
                        context.Response.ContentType = "image/jpeg";
                        break;
                    }
                    }
                }

                using (var responseStream = fileContent)
                {
                    context.Response.AddHeader("content-disposition", "inline;filename=" + binaryFileMetaData.FileName.MakeValidFileName().UrlEncode());
                    if (responseStream.CanSeek)
                    {
                        responseStream.Seek(0, SeekOrigin.Begin);
                    }
                    responseStream.CopyTo(context.Response.OutputStream);
                    context.Response.Flush();
                }
            }
            finally
            {
                if (fileContent != null)
                {
                    fileContent.Dispose();
                }
            }
        }