public string GenerateTicketNotificationHtmlEmailBody(TicketEventNotification notification, int firstUnsentCommentId)
        {
            var forOutlook       = (AppSettings.EnableOutlookFriendlyHtmlEmail);
            var templateToRender = (forOutlook) ? MVC.Admin.EmailTemplate.Views.TicketNotificationOutlookHtmlEmailTemplate : MVC.Admin.EmailTemplate.Views.TicketNotificationHtmlEmailTemplate;

            return(GenerateTicketNotificationEmailBody(notification, firstUnsentCommentId, templateToRender));
        }
        private string GenerateTicketNotificationEmailBody(TicketEventNotification notification, int firstUnsentCommentId, string templateToRender)
        {
            this.Security.GetCurrentUserName = delegate() { return(notification.NotifyUser); };
            var ticket = notification.TicketComment.Ticket;

            var vd = new ViewDataDictionary(ticket);

            vd.Add("siteRootUrl", AppSettings.SiteRootUrlForEmail);
            vd.Add("firstUnsentCommentId", firstUnsentCommentId);
            vd.Add("formatForEmail", true);
            using (StringWriter sw = new StringWriter())
            {
                var fakeResponse          = new HttpResponse(sw);
                var fakeContext           = new HttpContext(new HttpRequest("", AppSettings.SiteRootUrlForEmail, ""), fakeResponse);
                var fakeControllerContext = new ControllerContext
                                            (
                    new HttpContextWrapper(fakeContext),
                    new RouteData(),
                    this
                                            );
                fakeControllerContext.RouteData.Values.Add("controller", "EmailTemplate");

                ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(fakeControllerContext, templateToRender);
                ViewContext      vc         = new ViewContext(fakeControllerContext, new FakeView(), vd, new TempDataDictionary(), sw);

                HtmlHelper h = new HtmlHelper(vc, new ViewPage());

                h.RenderPartial(templateToRender, ticket, vd);

                return(sw.GetStringBuilder().ToString());
            }
        }
        private List <TicketEventNotification> CreateNotesForUsers(Dictionary <string, string> userReasons, TicketComment comment)
        {
            List <TicketEventNotification> newNotes = new List <TicketEventNotification>();
            var dt = DateTime.Now;

            foreach (var userReason in userReasons)
            {
                var note = new TicketEventNotification();
                note.CreatedDate          = dt;
                note.EventGeneratedByUser = comment.CommentedBy;

                //validate email address, if not valid we'll queue the note but not bother sending it (and having to go through the retries)
                bool   emailValid = false;
                string email      = Security.GetUserEmailAddress(userReason.Key);
                var    rxv        = new RegexStringValidator(@"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$");
                try
                {
                    rxv.Validate(email);
                    emailValid = true;
                }
                catch { }

                note.NotifyEmail           = (emailValid && !string.IsNullOrEmpty(email)) ? email : "invalid";
                note.NotifyUser            = userReason.Key;
                note.NotifyUserDisplayName = Security.GetUserDisplayName(userReason.Key);
                note.NotifyUserReason      = userReason.Value;
                newNotes.Add(note);
            }
            return(newNotes);
        }
示例#4
0
 private void SendTicketEventNotificationEmail(TicketEventNotification note, List <TicketEventNotification> consolidations)
 {
     lock (sendingLock)
     {
         PrepareTicketEventNotificationForDelivery(note, consolidations);
         DeliverTicketEventNotificationEmail(note, consolidations);
     }
 }
示例#5
0
        private void PrepareTicketEventNotificationForDelivery(TicketEventNotification note, List <TicketEventNotification> consolidations)
        {
            double resendDelay = GetEmailResendDelayMinutes();

            int maxRetries = GetEmailMaxDeliveryAttempts();

            note.DeliveryAttempts += 1;
            if (note.DeliveryAttempts < maxRetries)
            {
                note.Status = "pending";
                note.NextDeliveryAttemptDate = DateTime.Now.AddMinutes(resendDelay * note.DeliveryAttempts);
                int backTrackSeconds = -1;
                foreach (var consolidateNote in consolidations.OrderByDescending(c => c.CommentId))
                {
                    //this will reorder the next delivery attempt date for consolidations in descending order keeping the relative order for them all
                    consolidateNote.NextDeliveryAttemptDate = note.NextDeliveryAttemptDate.Value.AddSeconds(backTrackSeconds);
                    consolidateNote.DeliveryAttempts       += 1;
                    backTrackSeconds--;
                }
            }
            else
            {
                note.Status = "final-attempt";
                note.NextDeliveryAttemptDate = null;
                foreach (var consolidateNote in consolidations)
                {
                    consolidateNote.NextDeliveryAttemptDate = null;
                    consolidateNote.DeliveryAttempts       += 1;
                }
            }


            /* Unhandled edge case:
             *      We've set the next delivery date for the note we are sending to a future date in case something seriously goes wrong during sending the email.
             *      This is important because if the sending routine exits and we hadn't committed a next delivery date and all that, it would potentially cause
             *      an inifite retry-loop.
             *
             *      The edge case is that new notes could have been queued after we read the queue from the DB into memory. Those notes would have a delivery
             *      date much lower than the retry we just scheduled for this note. We could try to predict and work around this potential case, but it is
             *      such an extreme edge case (something has to queue AND an unhandled exception has to happen in the sending routine both).
             *
             *      By not handling this here, the worst that can happen is that the new queued note will simply cause a retry far sooner than we wanted.
             *      A work-around for this case has a significant perforance impact though, and so we are not going to implement that here... besides, if
             *      there is an unhandled exception in the send routine it will likely be something so severe that future send attemps are all going to fail
             *      for this user's notes indefinitly until the issue is debugged or a major data corruption issue is fixed anyway.
             */
        }
示例#6
0
        private TicketEventNotification CreateTicketEventNotificationForUser(int commentId, string commentBy, string user, string userType)
        {
            TicketEventNotification note = null;

            if (!string.IsNullOrEmpty(user))
            {
                bool   emailValid = false;
                string email      = SecurityManager.GetUserEmailAddress(user);

                // replaced pattern with variation based on the stock MS regex validator control's built-in pattern for email addresses
                //var rxv = new RegexStringValidator(@"^[a-zA-Z\.\-_]+@([a-zA-Z\.\-_]+\.)+[a-zA-Z]{2,4}$");
                var rxv = new RegexStringValidator(@"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$");
                try
                {
                    rxv.Validate(email);
                    emailValid = true;
                }
                catch { }

                note            = new TicketEventNotification();
                note.TicketId   = TicketId;
                note.CommentId  = commentId;
                note.NotifyUser = SecurityManager.GetFormattedUserName(user);

                note.NotifyUserDisplayName = SecurityManager.GetUserDisplayName(user);
                note.NotifyUserReason      = userType;
                note.EventGeneratedByUser  = commentBy;
                if (!string.IsNullOrEmpty(email) && emailValid)
                {
                    note.NotifyEmail = email;
                }
                else
                {
                    note.NotifyEmail = "invalid";
                }
            }
            return(note);
        }
示例#7
0
        private static void DeliverTicketEventNotificationEmail(TicketDataDataContext ctx, TicketEventNotification note, List <TicketEventNotification> consolidations)
        {
            bool status = false;

            try
            {
                //TODO: Modify body generation to flag comments for the current event and all consolidations.
                TicketComment comment = note.TicketComment;
                Ticket        ticket  = comment.Ticket;

                SmtpClient client  = new SmtpClient();
                string     url     = null;
                string     rootUrl = ConfigurationManager.AppSettings["WebRootUrlForEmailLinks"];
                if (!string.IsNullOrEmpty(rootUrl))
                {
                    if (!rootUrl.EndsWith("/"))
                    {
                        rootUrl += "/";
                    }
                    url = string.Format("{0}ViewTicket.aspx?id={1}", rootUrl, ticket.TicketId.ToString());
                }

                int minComment = note.CommentId;
                if (consolidations.Count() > 0)
                {
                    minComment = consolidations.Min(c => c.CommentId);
                }
                string body = NotificationUtilities.GetHTMLBody(ticket, url, note.NotifyUser, minComment);


                string      displayFrom = ConfigurationManager.AppSettings["FromEmailDisplayName"];
                string      addressFrom = ConfigurationManager.AppSettings["FromEmailAddress"];
                string      blindCopyTo = ConfigurationManager.AppSettings["BlindCopyToEmailAddress"];
                MailAddress bccAddr     = null;
                if (!string.IsNullOrEmpty(blindCopyTo))
                {
                    bccAddr = new MailAddress(blindCopyTo);
                }

                MailAddress fromAddr = new MailAddress(addressFrom, displayFrom);
                string      tStat    = ticket.CurrentStatus;
                if (string.IsNullOrEmpty(ticket.AssignedTo))
                {
                    tStat = (ticket.TicketComments.Count() < 2)? "New" : "Unassigned";
                }
                string      subject = string.Format("Ticket {0} ({1}): {2}", ticket.TicketId.ToString(), tStat, ticket.Title);
                MailAddress toAddr  = new MailAddress(note.NotifyEmail, note.NotifyUserDisplayName);

                MailMessage msg = new MailMessage(fromAddr, toAddr);
                if (bccAddr != null)
                {
                    msg.Bcc.Add(bccAddr);
                }
                msg.Subject    = subject;
                msg.Body       = body; //body;
                msg.IsBodyHtml = true;

                client.Send(msg);

                status = true;
            }
            catch (Exception ex)
            {
                Exception   newEx       = new ApplicationException("Failure in Email Delivery Timer", ex);
                HttpContext mockContext = (new TicketDesk.Engine.MockHttpContext(false)).Context;
                Elmah.ErrorLog.GetDefault(mockContext).Log(new Elmah.Error(newEx));
            }
            DateTime now = DateTime.Now;

            note.LastDeliveryAttemptDate = now;
            foreach (var consolidateNote in consolidations)
            {
                consolidateNote.LastDeliveryAttemptDate = now;
            }

            if (!status)
            {
                if (note.Status == "final-attempt")
                {
                    note.Status = "failed";
                    foreach (var consolidateNote in consolidations)
                    {
                        consolidateNote.Status = "consolidated-failed";
                    }
                }
                if (note.Status == "pending")
                {
                    note.Status = "queued";
                }

                //check for notes that queued after the send process first started here, and increment their nextDeliveryDate so they don't cause
                //  resends to happen sooner than the next delivery retry interval warrants.
                TicketDataDataContext ctxNew = new TicketDataDataContext();//existing context will have cached data from the past read. Do this on new context
                var newNotes = from tn in ctxNew.TicketEventNotifications
                               where tn.CommentId > note.CommentId && tn.NextDeliveryAttemptDate <= note.NextDeliveryAttemptDate
                               select tn;
                if (newNotes.Count() > 0)
                {
                    int advanceSeconds = 1;
                    foreach (var newNote in newNotes)
                    {
                        newNote.NextDeliveryAttemptDate = note.NextDeliveryAttemptDate.Value.AddSeconds(advanceSeconds);
                        advanceSeconds++;
                    }
                    ctxNew.SubmitChanges();
                }
            }
            else
            {
                note.Status = "sent";
                note.NextDeliveryAttemptDate = null;
                foreach (var consolidateNote in consolidations)
                {
                    consolidateNote.Status = "consolidated";
                    consolidateNote.NextDeliveryAttemptDate = null;
                }
            }
            ctx.SubmitChanges();//submit changes ticket-by-ticket so a failure later doesn't scrap the status update for tickets previously sent
        }
示例#8
0
 private static void SendTicketEventNotificationEmail(TicketDataDataContext ctx, TicketEventNotification note, List <TicketEventNotification> consolidations)
 {
     sendingLock = new object();
     lock (sendingLock)
     {
         PrepareTicketEventNotificationForDelivery(ctx, note, consolidations);
         DeliverTicketEventNotificationEmail(ctx, note, consolidations);
     }
 }
        public string GenerateTicketNotificationTextEmailBody(TicketEventNotification notification, int firstUnsentCommentId)
        {
            var templateName = MVC.Admin.EmailTemplate.Views.TicketNotificationTextEmailTemplate;

            return(GenerateTicketNotificationEmailBody(notification, firstUnsentCommentId, templateName));
        }
示例#10
0
 private string GetTextBody(TicketEventNotification note, string notifyUser, int minComment)
 {
     return(GetTicketNotificationTextEmailContent(note, minComment));
 }
示例#11
0
        private void DeliverTicketEventNotificationEmail(TicketEventNotification note, List <TicketEventNotification> consolidations)
        {
            bool status = false;

            try
            {
                //TODO: Modify body generation to flag comments for the current event and all consolidations.
                TicketComment comment = note.TicketComment;
                Ticket        ticket  = comment.Ticket;



                int minComment = note.CommentId;
                if (consolidations.Count() > 0)
                {
                    minComment = consolidations.Min(c => c.CommentId);
                }
                string htmlBody = GetHtmlBody(note, note.NotifyUser, minComment);
                string textBody = GetTextBody(note, note.NotifyUser, minComment);

                string displayFrom = GetFromEmailDisplayName();
                string addressFrom = GetFromEmailAddress();
                string blindCopyTo = GetBlindCopyToEmailAddress();

                string tStat = ticket.CurrentStatus;
                if (string.IsNullOrEmpty(ticket.AssignedTo))
                {
                    tStat = (ticket.TicketComments.Count() < 2) ? "New" : "Unassigned";
                }
                string subject = string.Format("Ticket {0} ({1}): {2}", ticket.TicketId.ToString(), tStat, ticket.Title);

                status = EmailHandler.SendEmail(addressFrom, displayFrom, note.NotifyEmail, note.NotifyUserDisplayName, blindCopyTo, subject, htmlBody, textBody);
            }
            catch (Exception ex)
            {
                var exception = new ApplicationException("Failure in Email Delivery", ex);
                //TODO: we need to log/record this here and NOT rethrow it
            }


            //TODO: There remains a small possibility that a note could be send above, but the below database update might break.
            //      this is a case that could lead to a user being notified multiple times.
            //      I would like to avoid the transactions namespace since it can require MSDTC in some cases.

            //wrap-up after sending, and update the notification status
            DateTime now = DateTime.Now;

            note.LastDeliveryAttemptDate = now;
            foreach (var consolidateNote in consolidations)
            {
                consolidateNote.LastDeliveryAttemptDate = now;
            }

            if (!status)
            {
                if (note.Status == "final-attempt")
                {
                    note.Status = "failed";
                    foreach (var consolidateNote in consolidations)
                    {
                        consolidateNote.Status = "consolidated-failed";
                    }
                }
                if (note.Status == "pending")
                {
                    note.Status = "queued";
                }

                //check for notes that queued after the send process first started here, and increment their nextDeliveryDate so they don't cause
                //  resends to happen sooner than the next delivery retry interval warrants. This is only needed when we are rescheduling a note
                //  for later delivery.
                NotificationRepository.EnsureValidNextDeliveryDateForNewlyQueudNotifications(note.TicketId, note.NotifyUser, note.CommentId, note.NextDeliveryAttemptDate.Value);
            }
            else
            {
                note.Status = "sent";
                note.NextDeliveryAttemptDate = null;
                foreach (var consolidateNote in consolidations)
                {
                    consolidateNote.Status = "consolidated";
                    consolidateNote.NextDeliveryAttemptDate = null;
                }
            }
        }