createBody)
{
if (performingUser == null) throw new ArgumentNullException("performingUser");
if (mailingUser == null) throw new ArgumentNullException("mailingUser");
if (content == null) throw new ArgumentNullException("content");
if (http == null) throw new ArgumentNullException("http");
if (createSubject == null) throw new ArgumentNullException("createSubject");
if (createBody == null) throw new ArgumentNullException("createBody");
// build summary
var summary = new StringBuilder();
var props = content.Properties.ToArray();
foreach (var p in props)
{
//fixme doesn't take into account variants
var newText = p.GetValue() != null ? p.GetValue().ToString() : "";
var oldText = newText;
// check if something was changed and display the changes otherwise display the fields
if (oldDoc.Properties.Contains(p.PropertyType.Alias))
{
var oldProperty = oldDoc.Properties[p.PropertyType.Alias];
oldText = oldProperty.GetValue() != null ? oldProperty.GetValue().ToString() : "";
// replace html with char equivalent
ReplaceHtmlSymbols(ref oldText);
ReplaceHtmlSymbols(ref newText);
}
// make sure to only highlight changes done using TinyMCE editor... other changes will be displayed using default summary
// TODO: We should probably allow more than just tinymce??
if ((p.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.TinyMce)
&& string.CompareOrdinal(oldText, newText) != 0)
{
summary.Append("");
summary.Append("| Note: | ");
summary.Append(
" Red for deleted characters Yellow for inserted characters | ");
summary.Append("
");
summary.Append("");
summary.Append("| New ");
summary.Append(p.PropertyType.Name);
summary.Append(" | ");
summary.Append("");
summary.Append(ReplaceLinks(CompareText(oldText, newText, true, false, "", string.Empty), http.Request));
summary.Append(" | ");
summary.Append("
");
summary.Append("");
summary.Append("| Old ");
summary.Append(p.PropertyType.Name);
summary.Append(" | ");
summary.Append("");
summary.Append(ReplaceLinks(CompareText(newText, oldText, true, false, "", string.Empty), http.Request));
summary.Append(" | ");
summary.Append("
");
}
else
{
summary.Append("");
summary.Append("| ");
summary.Append(p.PropertyType.Name);
summary.Append(" | ");
summary.Append("");
summary.Append(newText);
summary.Append(" | ");
summary.Append("
");
}
summary.Append(
"| |
");
}
string protocol = _globalSettings.UseHttps ? "https" : "http";
string[] subjectVars = {
string.Concat(http.Request.ServerVariables["SERVER_NAME"], ":", http.Request.Url.Port, IOHelper.ResolveUrl(SystemDirectories.Umbraco)),
actionName,
content.Name
};
string[] bodyVars = {
mailingUser.Name,
actionName,
content.Name,
performingUser.Name,
string.Concat(http.Request.ServerVariables["SERVER_NAME"], ":", http.Request.Url.Port, IOHelper.ResolveUrl(SystemDirectories.Umbraco)),
content.Id.ToString(CultureInfo.InvariantCulture), summary.ToString(),
string.Format("{2}://{0}/{1}",
string.Concat(http.Request.ServerVariables["SERVER_NAME"], ":", http.Request.Url.Port),
//TODO: RE-enable this so we can have a nice url
/*umbraco.library.NiceUrl(documentObject.Id))*/
string.Concat(content.Id, ".aspx"),
protocol)
};
// create the mail message
var mail = new MailMessage(UmbracoConfig.For.UmbracoSettings().Content.NotificationEmailAddress, mailingUser.Email);
// populate the message
mail.Subject = createSubject(mailingUser, subjectVars);
if (UmbracoConfig.For.UmbracoSettings().Content.DisableHtmlEmail)
{
mail.IsBodyHtml = false;
mail.Body = createBody(mailingUser, bodyVars);
}
else
{
mail.IsBodyHtml = true;
mail.Body =
string.Concat(@"
", createBody(mailingUser, bodyVars));
}
// nh, issue 30724. Due to hardcoded http strings in resource files, we need to check for https replacements here
// adding the server name to make sure we don't replace external links
if (_globalSettings.UseHttps && string.IsNullOrEmpty(mail.Body) == false)
{
string serverName = http.Request.ServerVariables["SERVER_NAME"];
mail.Body = mail.Body.Replace(
string.Format("http://{0}", serverName),
string.Format("https://{0}", serverName));
}
return new NotificationRequest(mail, actionName, mailingUser.Name, mailingUser.Email);
}
private string ReplaceLinks(string text, HttpRequestBase request)
{
var sb = new StringBuilder(_globalSettings.UseHttps ? "https://" : "http://");
sb.Append(request.ServerVariables["SERVER_NAME"]);
sb.Append(":");
sb.Append(request.Url.Port);
sb.Append("/");
var domain = sb.ToString();
text = text.Replace("href=\"/", "href=\"" + domain);
text = text.Replace("src=\"/", "src=\"" + domain);
return text;
}
///
/// Replaces the HTML symbols with the character equivalent.
///
/// The old string.
private static void ReplaceHtmlSymbols(ref string oldString)
{
oldString = oldString.Replace(" ", " ");
oldString = oldString.Replace("’", "'");
oldString = oldString.Replace("&", "&");
oldString = oldString.Replace("“", "“");
oldString = oldString.Replace("”", "”");
oldString = oldString.Replace(""", "\"");
}
///
/// Compares the text.
///
/// The old text.
/// The new text.
/// if set to true [display inserted text].
/// if set to true [display deleted text].
/// The inserted style.
/// The deleted style.
///
private static string CompareText(string oldText, string newText, bool displayInsertedText,
bool displayDeletedText, string insertedStyle, string deletedStyle)
{
var sb = new StringBuilder();
var diffs = Diff.DiffText1(oldText, newText);
int pos = 0;
for (var n = 0; n < diffs.Length; n++)
{
var it = diffs[n];
// write unchanged chars
while ((pos < it.StartB) && (pos < newText.Length))
{
sb.Append(newText[pos]);
pos++;
} // while
// write deleted chars
if (displayDeletedText && it.DeletedA > 0)
{
sb.Append(deletedStyle);
for (var m = 0; m < it.DeletedA; m++)
{
sb.Append(oldText[it.StartA + m]);
} // for
sb.Append("");
}
// write inserted chars
if (displayInsertedText && pos < it.StartB + it.InsertedB)
{
sb.Append(insertedStyle);
while (pos < it.StartB + it.InsertedB)
{
sb.Append(newText[pos]);
pos++;
} // while
sb.Append("");
} // if
} // while
// write rest of unchanged chars
while (pos < newText.Length)
{
sb.Append(newText[pos]);
pos++;
} // while
return sb.ToString();
}
// manage notifications
// ideally, would need to use IBackgroundTasks - but they are not part of Core!
private static readonly object Locker = new object();
private static readonly BlockingCollection Queue = new BlockingCollection();
private static volatile bool _running;
private void Enqueue(NotificationRequest notification)
{
Queue.Add(notification);
if (_running) return;
lock (Locker)
{
if (_running) return;
Process(Queue);
_running = true;
}
}
private class NotificationRequest
{
public NotificationRequest(MailMessage mail, string action, string userName, string email)
{
Mail = mail;
Action = action;
UserName = userName;
Email = email;
}
public MailMessage Mail { get; private set; }
public string Action { get; private set; }
public string UserName { get; private set; }
public string Email { get; private set; }
}
private void Process(BlockingCollection notificationRequests)
{
ThreadPool.QueueUserWorkItem(state =>
{
var s = new SmtpClient();
try
{
_logger.Debug("Begin processing notifications.");
while (true)
{
NotificationRequest request;
while (notificationRequests.TryTake(out request, 8 * 1000)) // stay on for 8s
{
try
{
if (Sendmail != null) Sendmail(s, request.Mail, _logger); else s.Send(request.Mail);
_logger.Debug(() => string.Format("Notification \"{0}\" sent to {1} ({2})", request.Action, request.UserName, request.Email));
}
catch (Exception ex)
{
_logger.Error("An error occurred sending notification", ex);
s.Dispose();
s = new SmtpClient();
}
finally
{
request.Mail.Dispose();
}
}
lock (Locker)
{
if (notificationRequests.Count > 0) continue; // last chance
_running = false; // going down
break;
}
}
}
finally
{
s.Dispose();
}
_logger.Debug("Done processing notifications.");
});
}
// for tests
internal static Action Sendmail;
//= (_, msg, logger) => logger.Debug("Email " + msg.To.ToString());
#endregion
}
}