Newsletter bug and fix

Dec 21, 2008 at 8:13 AM
Edited Dec 21, 2008 at 9:13 PM
Hi, I noticed that when I create an unpublished post and then publish it, no notifications are sent. After digging around I found out that the SaveAction.Insert condition is set when you "save" an unpublished post, so when you get around to publishing it treats it as an update. This is also the case for anyone using the enhanced newsletter widget.

This is the criteria I went by
- no unpublished posts will send notifications
- a notification is only sent the first time a post is published
- no notifications sent on additional updates

Observerations:
- SaveAction enums can't be trusted due to unpublished/published bug
- DateModified is misleading (only modifies when comments added?)

** Code below doesn't fully work, but it will make you blog timings consistent - see solution below this post.
I made some modifications to the core because it was adding timezones and offsets which made it a living hell to figure out, so I removed it. Date/Time is complicated enough.

[BlogBusinessBase]
       /// <summary>
       /// The date on which the instance was created.
        /// </summary>
        public DateTime DateCreated
        {
            get
            {
                return _DateCreated; 
            }
            set
            {
                if (_DateCreated != value) MarkChanged("DateCreated");
                _DateCreated = value;
            }
        }

[Post.cs]
        public Post()
        {
            base.Id = Guid.NewGuid();
            _Comments = new List<Comment>();
            _Categories = new StateList<Category>();
            _Tags = new StateList<string>();
            DateCreated = DateTime.Now;          <-------   I can't remeber if I changed this, but make you use this settings
            _IsPublished = true;
            _IsCommentsEnabled = true;
        }

[admin/pages/add_entry.cs]

page_load method

txtDate.Text = DateTime.Now.ToString("yyyy-MM-dd HH\\:mm:ss");       <------ add the :ss
 
btnSave_Click method

- comment out the line below
 // DateTime.ParseExact(txtDate.Text, "yyyy-MM-dd HH\\:mm:ss", null).AddHours(-BlogSettings.Instance.Timezone);

other changes in add_entry.cs
Change any string that has "yyyy-MM-dd HH\\:mm"  to
                                         "yyyy-MM-dd HH\\:mm:ss"

[admin/pages/add_entry.aspx]
Modify the regular expression validator and add :[0-9][0-9]
<asp:RegularExpressionValidator runat="server" ControlToValidate="txtDate" ValidationExpression="[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]" ErrorMessage="Please enter a valid date (yyyy-mm-dd hh:mm:ss)" Display="dynamic" />


[NewsLetter Widget.cs]
   // Goes through each email in list and sends it to proper email address
    // after a new post has been created
    static void Post_Saved(object sender, SavedEventArgs e)
    {
  
      Post post = (Post)sender;

        // If post is saved and published send to all newsletter subscribers
        if (post.IsPublished)
        {
            // Checks if this is the first time post is being created, if the post
            // is greater then 10 seconds old it will not send a notification
            if (isNewPost(post.DateCreated, 10))
            {
                LoadEmails();

                foreach (XmlNode node in _Doc.SelectNodes("emails/email"))
                {
                    MailMessage mail = CreateEmail(post);

                    //Create remove link
                    RemoveLink(post, node, mail);

                    mail.To.Add(node.InnerText.Trim());
                    Utils.SendMailMessageAsync(mail);
                }
            }
        }
        //else if (!post.IsPublished)  //Unposted so notify the webmaster
        //{
        //    MailMessage mail = CreateEmail(post);
        //    mail.To.Add(BlogEngine.Core.BlogSettings.Instance.Email.ToString());
        //    Utils.SendMailMessageAsync(mail);
        //}
    }


[NewsLetter Widget.cs]
    /// <summary>
    /// // Compare the date the post was created with the current d
    /// </summary>
    /// <param name="dateCreated">Date post was created</param>
    /// <param name="updateWindow">Account for difference in seconds caused by processing time</param>
    /// <returns></returns>
    static private bool isNewPost(DateTime dateCreated, int updateWindow)
    {
        string dateFormat = "yyyy-MM-dd H:mm:ss";
       
        // get date post was create
        DateTime dateTimeCreated = dateCreated;

        // Get current date and put it in proper format
        DateTime tempDate = DateTime.Now;
        string dateString = tempDate.ToString(dateFormat);
        DateTime currentDate = DateTime.ParseExact(dateString, dateFormat, null);
        
        // difference in time
        TimeSpan timeDifference = currentDate.Subtract(dateTimeCreated);

        // compare difference
        if (timeDifference.Seconds <= updateWindow)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
Dec 21, 2008 at 6:15 PM
I fixed a bug with the above code and changed a few things around to make it more accurate. Now you can compare based on seconds, so you don't have to wait that extra minute or two. I would suggest at least a 10 second window because even when I was debugging on my home system the time difference between date created and current date was 3 seconds. I'm not sure how long it would take on the server.
Dec 21, 2008 at 6:45 PM
Scratch that... Major bug found. The second you go into the add_entry page it sets the post created date. So, if you take an hour to write your post well that's the difference. I going to try and fix that one
Coordinator
Dec 21, 2008 at 7:58 PM
Here's another approach.  Subscribe to the post's Saving event (pre-save) and check the pre-saved post to see if it was published or not.  My first attempt at this using Post.GetPost() didn't work because even though the post isn't saved yet, Post.GetPost() will return the Post object with the values that are going to be saved.  Instead, you can go directly to the data store to see if the pre-save post is published or not.  Here's the relevant code I ended up with.

static widgets_Newsletter_widget()
{
    Post.Saved += new EventHandler<SavedEventArgs>(Post_Saved);
    Post.Saving += new EventHandler<SavedEventArgs>(Post_Saving);
}

private static string _SendNewsletterEmailsCacheItemKey = "SendNewsletterEmails";

private static bool SendNewsletterEmails
{
    get
    {
        if (HttpContext.Current.Items.Contains(_SendNewsletterEmailsCacheItemKey))
            return (bool)HttpContext.Current.Items[_SendNewsletterEmailsCacheItemKey];
        else
            return false;
    }
    set
    {
        if (HttpContext.Current.Items.Contains(_SendNewsletterEmailsCacheItemKey))
            HttpContext.Current.Items[_SendNewsletterEmailsCacheItemKey] = value;
        else
            HttpContext.Current.Items.Add(_SendNewsletterEmailsCacheItemKey, value);
    }
}

static void Post_Saving(object sender, SavedEventArgs e)
{
    Post post = (Post)sender;
    if (e.Action == SaveAction.Insert && post.IsPublished)
        SendNewsletterEmails = true;
    else if (e.Action == SaveAction.Update && post.IsPublished)
    {
        Post preUpdatePost = BlogEngine.Core.Providers.BlogService.SelectPost(post.Id);
        if (!preUpdatePost.IsPublished)
            SendNewsletterEmails = true;
    }        
}

static void Post_Saved(object sender, SavedEventArgs e)
{
    if (SendNewsletterEmails)
    {
        Post post = (Post)sender;
        LoadEmails();
        foreach (XmlNode node in _Doc.SelectNodes("emails/email"))
        {
            MailMessage mail = CreateEmail(post);
            mail.To.Add(node.InnerText);
            Utils.SendMailMessageAsync(mail);
        }
    }        
}
Dec 21, 2008 at 8:09 PM
I haven't tested out your solution Ben, but it looks really good. With your solution say I create a published post and then edit it in an unpublished state then republish it would sent a notification more then once?
Dec 21, 2008 at 8:29 PM
Edited Dec 21, 2008 at 8:37 PM
Another bug in mine.... It seems when you create an unpublished post first (sets the datecreated), then when you go back to edit it you get that time difference. I'm going to add a datepublished property to the post class, see if that works.
Coordinator
Dec 21, 2008 at 8:37 PM
Yeah, whenever the state of the post goes from unpublished to published, emails will be sent out.  So if you keep toggling the published state, then emails would be sent out multiple times.  This would be desirable behavior in some cases (and undesirable in other cases :-)

If the content of the post changes when you go to republish, then sending emails again might be okay.  Or if you accidently published the post (forgot to uncheck publish), emails are sent out, then you unpublish it, since no one will be able to view the post they found out about in the email, when you go to republish it (a day or two later), sending emails again might be helpful for subscribers.
Dec 21, 2008 at 8:43 PM
I like that reasonsing. I think all edits can be done in published mode if they are small. If it is a major rewrite then a renotification makes sense. I think your solution and reasoning make a lot of sense. I'm might have to use that solution of yours :)
Coordinator
Dec 21, 2008 at 8:48 PM
Even small edits would result in emails being sent out again -- if the post is marked as published while you are saving the small edits.  So it's probably best to minimize the number of times you Save the post while it is in a published state.  The logic in Post_Saving could be modified too if emails shouldn't be sent out for certain conditions.
Dec 21, 2008 at 8:52 PM
Oh, I was just running through the debugger and saw that small edits did not result in a new notification, but I guess when the post is out of the cache additional edits would sent the notification.
Coordinator
Dec 21, 2008 at 8:56 PM
Edited Dec 21, 2008 at 9:04 PM
Sorry, you're right!  To recap the conditions when emails would be sent (this is for me too!)

(1) When a new post is being created and is marked as published.

(2) When an old post is being saved and its new state is published and its old state is not published (i.e. It's going from unpublished to published).

Whether the post is cached or not has no impact on this logic.
Dec 21, 2008 at 9:09 PM
How I understand it is, if you edit any existing published posts it will not send a notification unless you edit and save the post as unpublished while making those changes and then republishing it when you are done? Sorry for being a bit slow, but this confusing sutff :)
Coordinator
Dec 21, 2008 at 9:11 PM
You got it perfect.
Dec 21, 2008 at 9:14 PM
Thanks Ben, you saved me a lot of grief once again :)
Coordinator
Dec 21, 2008 at 9:32 PM
Edited Dec 21, 2008 at 9:32 PM
I'm glad you spotted this.  I'm not using the Newsletter widget, but if I did, I'd be in trouble since I always initially save my post as unpublished so I can give it a spot check before going back to publish it.

By the way, I noticed there's actually an issue created for this.  I just uploaded this code to the issue and voted for it.

http://www.codeplex.com/blogengine/WorkItem/View.aspx?WorkItemId=7854
Dec 21, 2008 at 9:37 PM
Thanks Ben, I gave it my vote up.