diff --git a/src/Umbraco.Core/Services/INotificationService.cs b/src/Umbraco.Core/Services/INotificationService.cs index 801424d3b0..4be6d17f0d 100644 --- a/src/Umbraco.Core/Services/INotificationService.cs +++ b/src/Umbraco.Core/Services/INotificationService.cs @@ -1,7 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Web; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; @@ -17,9 +19,15 @@ namespace Umbraco.Core.Services /// Sends the notifications for the specified user regarding the specified node and action. /// /// - /// + /// /// - void SendNotifications(IEntity entity, IUser user, IAction action); + /// + /// + /// + /// + void SendNotifications(IUser operatingUser, IUmbracoEntity entity, string action, string actionName, HttpContextBase http, + Func createSubject, + Func createBody); /// /// Gets the notifications for the user @@ -28,6 +36,17 @@ namespace Umbraco.Core.Services /// IEnumerable GetUserNotifications(IUser user); + /// + /// Gets the notifications for the user based on the specified node path + /// + /// + /// + /// + /// + /// Notifications are inherited from the parent so any child node will also have notifications assigned based on it's parent (ancestors) + /// + IEnumerable GetUserNotifications(IUser user, string path); + /// /// Returns the notifications for an entity /// diff --git a/src/Umbraco.Core/Services/NotificationService.cs b/src/Umbraco.Core/Services/NotificationService.cs index 31c534470b..4ddde70e86 100644 --- a/src/Umbraco.Core/Services/NotificationService.cs +++ b/src/Umbraco.Core/Services/NotificationService.cs @@ -1,10 +1,20 @@ using System; using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net.Mail; +using System.Text; +using System.Threading; +using System.Web; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Strings; using umbraco.interfaces; namespace Umbraco.Core.Services @@ -12,21 +22,67 @@ namespace Umbraco.Core.Services internal class NotificationService : INotificationService { private readonly IDatabaseUnitOfWorkProvider _uowProvider; + private readonly IUserService _userService; + private readonly IContentService _contentService; - public NotificationService(IDatabaseUnitOfWorkProvider provider) + public NotificationService(IDatabaseUnitOfWorkProvider provider, IUserService userService, IContentService contentService) { _uowProvider = provider; + _userService = userService; + _contentService = contentService; } /// /// Sends the notifications for the specified user regarding the specified node and action. /// /// - /// + /// /// - public void SendNotifications(IEntity entity, IUser user, IAction action) + /// + /// + /// + /// + /// + /// Currently this will only work for Content entities! + /// + public void SendNotifications(IUser operatingUser, IUmbracoEntity entity, string action, string actionName, HttpContextBase http, + Func createSubject, + Func createBody) { - throw new NotImplementedException(); + if ((entity is IContent) == false) + { + throw new NotSupportedException(); + } + var content = (IContent) entity; + //we'll lazily get these if we need to send notifications + IContent[] allVersions = null; + + int totalUsers; + var allUsers = _userService.GetAllMembers(0, int.MaxValue, out totalUsers); + foreach (var u in allUsers) + { + if (u.IsApproved == false) continue; + var userNotifications = GetUserNotifications(u, content.Path).ToArray(); + var notificationForAction = userNotifications.FirstOrDefault(x => x.Action == action); + if (notificationForAction != null) + { + //lazy load versions if notifications are required + if (allVersions == null) + { + allVersions = _contentService.GetVersions(entity.Id).ToArray(); + } + + try + { + SendNotification(operatingUser, u, content, allVersions, actionName, http, createSubject, createBody); + LogHelper.Debug(string.Format("Notification type: {0} sent to {1} ({2})", action, u.Name, u.Email)); + } + catch (Exception ex) + { + LogHelper.Error("An error occurred sending notification", ex); + } + } + } } /// @@ -41,6 +97,23 @@ namespace Umbraco.Core.Services return repository.GetUserNotifications(user); } + /// + /// Gets the notifications for the user based on the specified node path + /// + /// + /// + /// + /// + /// Notifications are inherited from the parent so any child node will also have notifications assigned based on it's parent (ancestors) + /// + public IEnumerable GetUserNotifications(IUser user, string path) + { + var userNotifications = GetUserNotifications(user).ToArray(); + var pathParts = path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); + var result = userNotifications.Where(r => pathParts.InvariantContains(r.EntityId.ToString(CultureInfo.InvariantCulture))).ToList(); + return result; + } + /// /// Deletes notifications by entity /// @@ -99,5 +172,252 @@ namespace Umbraco.Core.Services var repository = new NotificationsRepository(uow); return repository.CreateNotification(user, entity, action); } + + #region private methods + + /// + /// Sends the notification + /// + /// + /// + /// + /// + /// The action readable name - currently an action is just a single letter, this is the name associated with the letter + /// + /// Callback to create the mail subject + /// Callback to create the mail body + private void SendNotification(IUser performingUser, IUser mailingUser, IContent content, IContent[] allVersions, string actionName, HttpContextBase http, + Func createSubject, + Func createBody) + { + if (performingUser == null) throw new ArgumentNullException("performingUser"); + if (mailingUser == null) throw new ArgumentNullException("mailingUser"); + if (content == null) throw new ArgumentNullException("content"); + if (allVersions == null) throw new ArgumentNullException("allVersions"); + if (http == null) throw new ArgumentNullException("http"); + if (createSubject == null) throw new ArgumentNullException("createSubject"); + if (createBody == null) throw new ArgumentNullException("createBody"); + + int versionCount = (allVersions.Length > 1) ? (allVersions.Length - 2) : (allVersions.Length - 1); + var oldDoc = _contentService.GetByVersion(allVersions[versionCount].Version); + + // build summary + var summary = new StringBuilder(); + var props = content.Properties.ToArray(); + foreach (var p in props) + { + var newText = p.Value != null ? p.Value.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.Value != null ? oldProperty.Value.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.DataTypeId == Guid.Parse(Constants.PropertyEditors.TinyMCEv3) || p.PropertyType.DataTypeId == Guid.Parse(Constants.PropertyEditors.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 " + + p.PropertyType.Name + ""); + summary.Append("" + + ReplaceLinks(CompareText(oldText, newText, true, false, "", string.Empty), http.Request) + + ""); + summary.Append(""); + summary.Append(""); + summary.Append(" Old " + + p.PropertyType.Name + ""); + summary.Append("" + + ReplaceLinks(CompareText(newText, oldText, true, false, "", string.Empty), http.Request) + + ""); + summary.Append(""); + } + else + { + summary.Append(""); + summary.Append("" + + p.PropertyType.Name + ""); + summary.Append("" + newText + ""); + summary.Append(""); + } + summary.Append( + " "); + } + + string protocol = GlobalSettings.UseSSL ? "https" : "http"; + + + string[] subjectVars = { + 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, + 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}", + 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))*/ + content.Id + ".aspx", + protocol) + + }; + + // create the mail message + var mail = new MailMessage(UmbracoSettings.NotificationEmailSender, mailingUser.Email); + + // populate the message + mail.Subject = createSubject(mailingUser, subjectVars); + //mail.Subject = ui.Text("notifications", "mailSubject", subjectVars, mailingUser); + if (UmbracoSettings.NotificationDisableHtmlEmail) + { + mail.IsBodyHtml = false; + //mail.Body = ui.Text("notifications", "mailBody", bodyVars, mailingUser); + mail.Body = createBody(mailingUser, bodyVars); + } + else + { + mail.IsBodyHtml = true; + mail.Body = + @" + + +" + createBody(mailingUser, bodyVars); + //ui.Text("notifications", "mailBodyHtml", bodyVars, mailingUser) + ""; + } + + // 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.UseSSL && 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)); + } + + + // send it asynchronously, we don't want to got up all of the request time to send emails! + ThreadPool.QueueUserWorkItem(state => + { + try + { + var sender = new SmtpClient(); + sender.Send(mail); + } + catch (Exception ex) + { + LogHelper.Error("An error occurred sending notification", ex); + } + }); + } + + private static string ReplaceLinks(string text, HttpRequestBase request) + { + string domain = GlobalSettings.UseSSL ? "https://" : "http://"; + domain += request.ServerVariables["SERVER_NAME"] + ":" + request.Url.Port + "/"; + 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 (int n = 0; n < diffs.Length; n++) + { + Diff.Item 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 (int 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(); + } + + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 03e0d96a59..850b26627f 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using System.Xml; +using System.Xml; using System.Xml.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Events; diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index 4bd2f433ba..ac2b8b24be 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -29,6 +29,7 @@ namespace Umbraco.Core.Services private Lazy _sectionService; private Lazy _macroService; private Lazy _memberTypeService; + private Lazy _notificationService; /// /// public ctor - will generally just be used for unit testing @@ -101,6 +102,9 @@ namespace Umbraco.Core.Services var provider = dbUnitOfWorkProvider; var fileProvider = fileUnitOfWorkProvider; + if (_notificationService == null) + _notificationService = new Lazy(() => new NotificationService(provider, _userService.Value, _contentService.Value)); + if (_serverRegistrationService == null) _serverRegistrationService = new Lazy(() => new ServerRegistrationService(provider, repositoryFactory.Value)); @@ -153,6 +157,14 @@ namespace Umbraco.Core.Services _tagService = new Lazy(() => new TagService(provider, repositoryFactory.Value)); } + /// + /// Gets the + /// + internal INotificationService NotificationService + { + get { return _notificationService.Value; } + } + /// /// Gets the /// diff --git a/src/Umbraco.Core/Strings/Diff.cs b/src/Umbraco.Core/Strings/Diff.cs new file mode 100644 index 0000000000..ed381d4f6f --- /dev/null +++ b/src/Umbraco.Core/Strings/Diff.cs @@ -0,0 +1,510 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace Umbraco.Core.Strings +{ + /// + /// This Class implements the Difference Algorithm published in + /// "An O(ND) Difference Algorithm and its Variations" by Eugene Myers + /// Algorithmica Vol. 1 No. 2, 1986, p 251. + /// + /// The algorithm itself is comparing 2 arrays of numbers so when comparing 2 text documents + /// each line is converted into a (hash) number. See DiffText(). + /// + /// diff.cs: A port of the algorithm to C# + /// Copyright (c) by Matthias Hertel, http://www.mathertel.de + /// This work is licensed under a BSD style license. See http://www.mathertel.de/License.aspx + /// + internal class Diff + { + /// Data on one input file being compared. + /// + internal class DiffData + { + + /// Number of elements (lines). + internal int Length; + + /// Buffer of numbers that will be compared. + internal int[] Data; + + /// + /// Array of booleans that flag for modified data. + /// This is the result of the diff. + /// This means deletedA in the first Data or inserted in the second Data. + /// + internal bool[] Modified; + + /// + /// Initialize the Diff-Data buffer. + /// + /// reference to the buffer + internal DiffData(int[] initData) + { + Data = initData; + Length = initData.Length; + Modified = new bool[Length + 2]; + } // DiffData + + } // class DiffData + + /// details of one difference. + public struct Item + { + /// Start Line number in Data A. + public int StartA; + /// Start Line number in Data B. + public int StartB; + + /// Number of changes in Data A. + public int DeletedA; + /// Number of changes in Data B. + public int InsertedB; + } // Item + + /// + /// Shortest Middle Snake Return Data + /// + private struct Smsrd + { + internal int X, Y; + // internal int u, v; // 2002.09.20: no need for 2 points + } + + /// + /// Find the difference in 2 texts, comparing by textlines. + /// + /// A-version of the text (usualy the old one) + /// B-version of the text (usualy the new one) + /// Returns a array of Items that describe the differences. + public static Item[] DiffText(string textA, string textB) + { + return (DiffText(textA, textB, false, false, false)); + } // DiffText + + /// + /// Find the difference in 2 texts, comparing by textlines. + /// This method uses the DiffInt internally by 1st converting the string into char codes + /// then uses the diff int method + /// + /// A-version of the text (usualy the old one) + /// B-version of the text (usualy the new one) + /// Returns a array of Items that describe the differences. + public static Item[] DiffText1(string textA, string textB) + { + return DiffInt(DiffCharCodes(textA, false), DiffCharCodes(textB, false)); + } + + + /// + /// Find the difference in 2 text documents, comparing by textlines. + /// The algorithm itself is comparing 2 arrays of numbers so when comparing 2 text documents + /// each line is converted into a (hash) number. This hash-value is computed by storing all + /// textlines into a common hashtable so i can find dublicates in there, and generating a + /// new number each time a new textline is inserted. + /// + /// A-version of the text (usualy the old one) + /// B-version of the text (usualy the new one) + /// When set to true, all leading and trailing whitespace characters are stripped out before the comparation is done. + /// When set to true, all whitespace characters are converted to a single space character before the comparation is done. + /// When set to true, all characters are converted to their lowercase equivivalence before the comparation is done. + /// Returns a array of Items that describe the differences. + public static Item[] DiffText(string textA, string textB, bool trimSpace, bool ignoreSpace, bool ignoreCase) + { + // prepare the input-text and convert to comparable numbers. + var h = new Hashtable(textA.Length + textB.Length); + + // The A-Version of the data (original data) to be compared. + var dataA = new DiffData(DiffCodes(textA, h, trimSpace, ignoreSpace, ignoreCase)); + + // The B-Version of the data (modified data) to be compared. + var dataB = new DiffData(DiffCodes(textB, h, trimSpace, ignoreSpace, ignoreCase)); + + h = null; // free up hashtable memory (maybe) + + var max = dataA.Length + dataB.Length + 1; + // vector for the (0,0) to (x,y) search + var downVector = new int[2 * max + 2]; + // vector for the (u,v) to (N,M) search + var upVector = new int[2 * max + 2]; + + Lcs(dataA, 0, dataA.Length, dataB, 0, dataB.Length, downVector, upVector); + + Optimize(dataA); + Optimize(dataB); + return CreateDiffs(dataA, dataB); + } // DiffText + + + /// + /// Diffs the char codes. + /// + /// A text. + /// if set to true [ignore case]. + /// + private static int[] DiffCharCodes(string aText, bool ignoreCase) + { + if (ignoreCase) + aText = aText.ToUpperInvariant(); + + var codes = new int[aText.Length]; + + for (int n = 0; n < aText.Length; n++) + codes[n] = (int)aText[n]; + + return (codes); + } // DiffCharCodes + + /// + /// If a sequence of modified lines starts with a line that contains the same content + /// as the line that appends the changes, the difference sequence is modified so that the + /// appended line and not the starting line is marked as modified. + /// This leads to more readable diff sequences when comparing text files. + /// + /// A Diff data buffer containing the identified changes. + private static void Optimize(DiffData data) + { + var startPos = 0; + while (startPos < data.Length) + { + while ((startPos < data.Length) && (data.Modified[startPos] == false)) + startPos++; + int endPos = startPos; + while ((endPos < data.Length) && (data.Modified[endPos] == true)) + endPos++; + + if ((endPos < data.Length) && (data.Data[startPos] == data.Data[endPos])) + { + data.Modified[startPos] = false; + data.Modified[endPos] = true; + } + else + { + startPos = endPos; + } // if + } // while + } // Optimize + + + /// + /// Find the difference in 2 arrays of integers. + /// + /// A-version of the numbers (usualy the old one) + /// B-version of the numbers (usualy the new one) + /// Returns a array of Items that describe the differences. + public static Item[] DiffInt(int[] arrayA, int[] arrayB) + { + // The A-Version of the data (original data) to be compared. + var dataA = new DiffData(arrayA); + + // The B-Version of the data (modified data) to be compared. + var dataB = new DiffData(arrayB); + + var max = dataA.Length + dataB.Length + 1; + // vector for the (0,0) to (x,y) search + var downVector = new int[2 * max + 2]; + // vector for the (u,v) to (N,M) search + var upVector = new int[2 * max + 2]; + + Lcs(dataA, 0, dataA.Length, dataB, 0, dataB.Length, downVector, upVector); + return CreateDiffs(dataA, dataB); + } // Diff + + + /// + /// This function converts all textlines of the text into unique numbers for every unique textline + /// so further work can work only with simple numbers. + /// + /// the input text + /// This extern initialized hashtable is used for storing all ever used textlines. + /// ignore leading and trailing space characters + /// + /// + /// a array of integers. + private static int[] DiffCodes(string aText, IDictionary h, bool trimSpace, bool ignoreSpace, bool ignoreCase) + { + // get all codes of the text + var lastUsedCode = h.Count; + + // strip off all cr, only use lf as textline separator. + aText = aText.Replace("\r", ""); + var lines = aText.Split('\n'); + + var codes = new int[lines.Length]; + + for (int i = 0; i < lines.Length; ++i) + { + string s = lines[i]; + if (trimSpace) + s = s.Trim(); + + if (ignoreSpace) + { + s = Regex.Replace(s, "\\s+", " "); // TODO: optimization: faster blank removal. + } + + if (ignoreCase) + s = s.ToLower(); + + object aCode = h[s]; + if (aCode == null) + { + lastUsedCode++; + h[s] = lastUsedCode; + codes[i] = lastUsedCode; + } + else + { + codes[i] = (int)aCode; + } // if + } // for + return (codes); + } // DiffCodes + + + /// + /// This is the algorithm to find the Shortest Middle Snake (SMS). + /// + /// sequence A + /// lower bound of the actual range in DataA + /// upper bound of the actual range in DataA (exclusive) + /// sequence B + /// lower bound of the actual range in DataB + /// upper bound of the actual range in DataB (exclusive) + /// a vector for the (0,0) to (x,y) search. Passed as a parameter for speed reasons. + /// a vector for the (u,v) to (N,M) search. Passed as a parameter for speed reasons. + /// a MiddleSnakeData record containing x,y and u,v + private static Smsrd Sms(DiffData dataA, int lowerA, int upperA, DiffData dataB, int lowerB, int upperB, int[] downVector, int[] upVector) + { + int max = dataA.Length + dataB.Length + 1; + + int downK = lowerA - lowerB; // the k-line to start the forward search + int upK = upperA - upperB; // the k-line to start the reverse search + + int delta = (upperA - lowerA) - (upperB - lowerB); + bool oddDelta = (delta & 1) != 0; + + // The vectors in the publication accepts negative indexes. the vectors implemented here are 0-based + // and are access using a specific offset: UpOffset UpVector and DownOffset for DownVektor + int downOffset = max - downK; + int upOffset = max - upK; + + int maxD = ((upperA - lowerA + upperB - lowerB) / 2) + 1; + + // Debug.Write(2, "SMS", String.Format("Search the box: A[{0}-{1}] to B[{2}-{3}]", LowerA, UpperA, LowerB, UpperB)); + + // init vectors + downVector[downOffset + downK + 1] = lowerA; + upVector[upOffset + upK - 1] = upperA; + + for (int d = 0; d <= maxD; d++) + { + + // Extend the forward path. + Smsrd ret; + for (int k = downK - d; k <= downK + d; k += 2) + { + // Debug.Write(0, "SMS", "extend forward path " + k.ToString()); + + // find the only or better starting point + int x, y; + if (k == downK - d) + { + x = downVector[downOffset + k + 1]; // down + } + else + { + x = downVector[downOffset + k - 1] + 1; // a step to the right + if ((k < downK + d) && (downVector[downOffset + k + 1] >= x)) + x = downVector[downOffset + k + 1]; // down + } + y = x - k; + + // find the end of the furthest reaching forward D-path in diagonal k. + while ((x < upperA) && (y < upperB) && (dataA.Data[x] == dataB.Data[y])) + { + x++; y++; + } + downVector[downOffset + k] = x; + + // overlap ? + if (oddDelta && (upK - d < k) && (k < upK + d)) + { + if (upVector[upOffset + k] <= downVector[downOffset + k]) + { + ret.X = downVector[downOffset + k]; + ret.Y = downVector[downOffset + k] - k; + // ret.u = UpVector[UpOffset + k]; // 2002.09.20: no need for 2 points + // ret.v = UpVector[UpOffset + k] - k; + return (ret); + } // if + } // if + + } // for k + + // Extend the reverse path. + for (int k = upK - d; k <= upK + d; k += 2) + { + // Debug.Write(0, "SMS", "extend reverse path " + k.ToString()); + + // find the only or better starting point + int x, y; + if (k == upK + d) + { + x = upVector[upOffset + k - 1]; // up + } + else + { + x = upVector[upOffset + k + 1] - 1; // left + if ((k > upK - d) && (upVector[upOffset + k - 1] < x)) + x = upVector[upOffset + k - 1]; // up + } // if + y = x - k; + + while ((x > lowerA) && (y > lowerB) && (dataA.Data[x - 1] == dataB.Data[y - 1])) + { + x--; y--; // diagonal + } + upVector[upOffset + k] = x; + + // overlap ? + if (!oddDelta && (downK - d <= k) && (k <= downK + d)) + { + if (upVector[upOffset + k] <= downVector[downOffset + k]) + { + ret.X = downVector[downOffset + k]; + ret.Y = downVector[downOffset + k] - k; + // ret.u = UpVector[UpOffset + k]; // 2002.09.20: no need for 2 points + // ret.v = UpVector[UpOffset + k] - k; + return (ret); + } // if + } // if + + } // for k + + } // for D + + throw new ApplicationException("the algorithm should never come here."); + } // SMS + + + /// + /// This is the divide-and-conquer implementation of the longes common-subsequence (LCS) + /// algorithm. + /// The published algorithm passes recursively parts of the A and B sequences. + /// To avoid copying these arrays the lower and upper bounds are passed while the sequences stay constant. + /// + /// sequence A + /// lower bound of the actual range in DataA + /// upper bound of the actual range in DataA (exclusive) + /// sequence B + /// lower bound of the actual range in DataB + /// upper bound of the actual range in DataB (exclusive) + /// a vector for the (0,0) to (x,y) search. Passed as a parameter for speed reasons. + /// a vector for the (u,v) to (N,M) search. Passed as a parameter for speed reasons. + private static void Lcs(DiffData dataA, int lowerA, int upperA, DiffData dataB, int lowerB, int upperB, int[] downVector, int[] upVector) + { + // Debug.Write(2, "LCS", String.Format("Analyse the box: A[{0}-{1}] to B[{2}-{3}]", LowerA, UpperA, LowerB, UpperB)); + + // Fast walkthrough equal lines at the start + while (lowerA < upperA && lowerB < upperB && dataA.Data[lowerA] == dataB.Data[lowerB]) + { + lowerA++; lowerB++; + } + + // Fast walkthrough equal lines at the end + while (lowerA < upperA && lowerB < upperB && dataA.Data[upperA - 1] == dataB.Data[upperB - 1]) + { + --upperA; --upperB; + } + + if (lowerA == upperA) + { + // mark as inserted lines. + while (lowerB < upperB) + dataB.Modified[lowerB++] = true; + + } + else if (lowerB == upperB) + { + // mark as deleted lines. + while (lowerA < upperA) + dataA.Modified[lowerA++] = true; + + } + else + { + // Find the middle snakea and length of an optimal path for A and B + Smsrd smsrd = Sms(dataA, lowerA, upperA, dataB, lowerB, upperB, downVector, upVector); + // Debug.Write(2, "MiddleSnakeData", String.Format("{0},{1}", smsrd.x, smsrd.y)); + + // The path is from LowerX to (x,y) and (x,y) to UpperX + Lcs(dataA, lowerA, smsrd.X, dataB, lowerB, smsrd.Y, downVector, upVector); + Lcs(dataA, smsrd.X, upperA, dataB, smsrd.Y, upperB, downVector, upVector); // 2002.09.20: no need for 2 points + } + } // LCS() + + + /// Scan the tables of which lines are inserted and deleted, + /// producing an edit script in forward order. + /// + /// dynamic array + private static Item[] CreateDiffs(DiffData dataA, DiffData dataB) + { + ArrayList a = new ArrayList(); + Item aItem; + Item[] result; + + int lineA = 0; + int lineB = 0; + while (lineA < dataA.Length || lineB < dataB.Length) + { + if ((lineA < dataA.Length) && (!dataA.Modified[lineA]) + && (lineB < dataB.Length) && (!dataB.Modified[lineB])) + { + // equal lines + lineA++; + lineB++; + + } + else + { + // maybe deleted and/or inserted lines + int startA = lineA; + int startB = lineB; + + while (lineA < dataA.Length && (lineB >= dataB.Length || dataA.Modified[lineA])) + // while (LineA < DataA.Length && DataA.modified[LineA]) + lineA++; + + while (lineB < dataB.Length && (lineA >= dataA.Length || dataB.Modified[lineB])) + // while (LineB < DataB.Length && DataB.modified[LineB]) + lineB++; + + if ((startA < lineA) || (startB < lineB)) + { + // store a new difference-item + aItem = new Item(); + aItem.StartA = startA; + aItem.StartB = startB; + aItem.DeletedA = lineA - startA; + aItem.InsertedB = lineB - startB; + a.Add(aItem); + } // if + } // if + } // while + + result = new Item[a.Count]; + a.CopyTo(result); + + return (result); + } + + } // class Diff + + +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index ad49b46b54..45170dfc51 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1011,6 +1011,7 @@ + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index eaa279c342..f97ff2ca1e 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -115,15 +115,15 @@ False ..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.Net4.dll - + False ..\packages\ClientDependency.1.7.1.1\lib\ClientDependency.Core.dll - + False ..\packages\ClientDependency-Mvc.1.7.0.4\lib\ClientDependency.Core.Mvc.dll - + False ..\packages\Examine.0.1.52.2941\lib\Examine.dll diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 94e3ea315b..b829f6cc7a 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -102,7 +102,7 @@ ..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.Net4.dll - + False ..\packages\ClientDependency.1.7.1.1\lib\ClientDependency.Core.dll @@ -110,7 +110,7 @@ False ..\packages\xmlrpcnet.2.5.0\lib\net20\CookComputing.XmlRpcV2.dll - + False ..\packages\Examine.0.1.52.2941\lib\Examine.dll diff --git a/src/UmbracoExamine.Azure/UmbracoExamine.Azure.csproj b/src/UmbracoExamine.Azure/UmbracoExamine.Azure.csproj index db7b95bd7a..22792d6aa0 100644 --- a/src/UmbracoExamine.Azure/UmbracoExamine.Azure.csproj +++ b/src/UmbracoExamine.Azure/UmbracoExamine.Azure.csproj @@ -40,11 +40,11 @@ ..\packages\AzureDirectory.1.0.5\lib\AzureDirectory.dll - + False ..\packages\Examine.0.1.52.2941\lib\Examine.dll - + False ..\packages\Examine.Azure.0.1.51.2941\lib\Examine.Azure.dll diff --git a/src/UmbracoExamine.PDF.Azure/UmbracoExamine.PDF.Azure.csproj b/src/UmbracoExamine.PDF.Azure/UmbracoExamine.PDF.Azure.csproj index d17a4c4921..203fc935dc 100644 --- a/src/UmbracoExamine.PDF.Azure/UmbracoExamine.PDF.Azure.csproj +++ b/src/UmbracoExamine.PDF.Azure/UmbracoExamine.PDF.Azure.csproj @@ -40,11 +40,11 @@ ..\packages\AzureDirectory.1.0.5\lib\AzureDirectory.dll - + False ..\packages\Examine.0.1.52.2941\lib\Examine.dll - + False ..\packages\Examine.Azure.0.1.51.2941\lib\Examine.Azure.dll diff --git a/src/UmbracoExamine.PDF/UmbracoExamine.PDF.csproj b/src/UmbracoExamine.PDF/UmbracoExamine.PDF.csproj index 6bdc3d3666..aed13c7c13 100644 --- a/src/UmbracoExamine.PDF/UmbracoExamine.PDF.csproj +++ b/src/UmbracoExamine.PDF/UmbracoExamine.PDF.csproj @@ -46,7 +46,7 @@ false - + False ..\packages\Examine.0.1.52.2941\lib\Examine.dll diff --git a/src/UmbracoExamine/UmbracoExamine.csproj b/src/UmbracoExamine/UmbracoExamine.csproj index c180189a64..7bcb270bce 100644 --- a/src/UmbracoExamine/UmbracoExamine.csproj +++ b/src/UmbracoExamine/UmbracoExamine.csproj @@ -82,7 +82,7 @@ ..\Solution Items\TheFARM-Public.snk - + False ..\packages\Examine.0.1.52.2941\lib\Examine.dll diff --git a/src/umbraco.cms/businesslogic/utilities/Diff.cs b/src/umbraco.cms/businesslogic/utilities/Diff.cs index 62397f2495..3aa159de7e 100644 --- a/src/umbraco.cms/businesslogic/utilities/Diff.cs +++ b/src/umbraco.cms/businesslogic/utilities/Diff.cs @@ -3,6 +3,8 @@ using System.Collections; using System.Text; using System.Text.RegularExpressions; +//TODO: We've alraedy moved most of this logic to Core.Strings - need to review this as it has slightly more functionality but should be moved to core and obsoleted! + namespace umbraco.cms.businesslogic.utilities { /// /// This Class implements the Difference Algorithm published in diff --git a/src/umbraco.cms/businesslogic/workflow/Diff.cs b/src/umbraco.cms/businesslogic/workflow/Diff.cs index e4e4d505a9..39cbc1d2c3 100644 --- a/src/umbraco.cms/businesslogic/workflow/Diff.cs +++ b/src/umbraco.cms/businesslogic/workflow/Diff.cs @@ -18,7 +18,7 @@ namespace umbraco.cms.businesslogic.workflow /// Copyright (c) by Matthias Hertel, http://www.mathertel.de /// This work is licensed under a BSD style license. See http://www.mathertel.de/License.aspx /// - + [Obsolete("This class will be removed from the codebase in the future")] public class Diff { @@ -489,6 +489,7 @@ namespace umbraco.cms.businesslogic.workflow /// Data on one input file being compared. /// + [Obsolete("This class will be removed from the codebase in the future, logic has moved to the Core project")] internal class DiffData { diff --git a/src/umbraco.cms/businesslogic/workflow/Notification.cs b/src/umbraco.cms/businesslogic/workflow/Notification.cs index b272491362..4a0afbb38d 100644 --- a/src/umbraco.cms/businesslogic/workflow/Notification.cs +++ b/src/umbraco.cms/businesslogic/workflow/Notification.cs @@ -72,138 +72,19 @@ namespace umbraco.cms.businesslogic.workflow } } - ///TODO: Include update with html mail notification and document contents + //TODO: Include update with html mail notification and document contents private static void SendNotification(User performingUser, User mailingUser, Document documentObject, IAction action) { - // retrieve previous version of the document - DocumentVersionList[] versions = documentObject.GetVersions(); - int versionCount = (versions.Length > 1) ? (versions.Length - 2) : (versions.Length - 1); - var oldDoc = new Document(documentObject.Id, versions[versionCount].Version); + var nService = ApplicationContext.Current.Services.NotificationService; + var pUser = ApplicationContext.Current.Services.UserService.GetById(performingUser.Id); - // build summary - var summary = new StringBuilder(); - var props = documentObject.GenericProperties; - foreach (Property p in props) - { - // check if something was changed and display the changes otherwise display the fields - Property oldProperty = oldDoc.getProperty(p.PropertyType.Alias); - string oldText = oldProperty.Value != null ? oldProperty.Value.ToString() : ""; - string newText = p.Value != null ? p.Value.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 PPH: Had to change this, as a reference to the editorcontrols is not allowed, so a string comparison is the only way, this should be a DIFF or something instead.. - if (p.PropertyType.DataTypeDefinition.DataType.ToString() == - "umbraco.editorControls.tinymce.TinyMCEDataType" && - 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 " + - p.PropertyType.Name + ""); - summary.Append("" + - ReplaceLinks(CompareText(oldText, newText, true, false, - "", string.Empty)) + - ""); - summary.Append(""); - summary.Append(""); - summary.Append(" Old " + - oldProperty.PropertyType.Name + ""); - summary.Append("" + - ReplaceLinks(CompareText(newText, oldText, true, false, - "", string.Empty)) + - ""); - summary.Append(""); - } - else - { - summary.Append(""); - summary.Append("" + - p.PropertyType.Name + ""); - summary.Append("" + newText + ""); - summary.Append(""); - } - summary.Append( - " "); - } - - string protocol = GlobalSettings.UseSSL ? "https" : "http"; - - - string[] subjectVars = { - HttpContext.Current.Request.ServerVariables["SERVER_NAME"] + ":" + - HttpContext.Current.Request.Url.Port + - IOHelper.ResolveUrl(SystemDirectories.Umbraco), ui.Text(action.Alias) - , - documentObject.Text - }; - string[] bodyVars = { - mailingUser.Name, ui.Text(action.Alias), documentObject.Text, performingUser.Name, - HttpContext.Current.Request.ServerVariables["SERVER_NAME"] + ":" + - HttpContext.Current.Request.Url.Port + - IOHelper.ResolveUrl(SystemDirectories.Umbraco), - documentObject.Id.ToString(), summary.ToString(), - String.Format("{2}://{0}/{1}", - HttpContext.Current.Request.ServerVariables["SERVER_NAME"] + ":" + - HttpContext.Current.Request.Url.Port, - /*umbraco.library.NiceUrl(documentObject.Id))*/ - documentObject.Id + ".aspx", - protocol) - //TODO: PPH removed the niceURL reference... cms.dll cannot reference the presentation project... - //TODO: This should be moved somewhere else.. - }; - - // create the mail message - var mail = new MailMessage(UmbracoConfig.For.UmbracoSettings().Content.NotificationEmailAddress, mailingUser.Email); - - // populate the message - mail.Subject = ui.Text("notifications", "mailSubject", subjectVars, mailingUser); - if (UmbracoConfig.For.UmbracoSettings().Content.DisableHtmlEmail) - { - mail.IsBodyHtml = false; - mail.Body = ui.Text("notifications", "mailBody", bodyVars, mailingUser); - } - else - { - mail.IsBodyHtml = true; - mail.Body = - @" - - -" + - ui.Text("notifications", "mailBodyHtml", bodyVars, mailingUser) + ""; - } - - // 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.UseSSL && string.IsNullOrEmpty(mail.Body) == false) - { - string serverName = HttpContext.Current.Request.ServerVariables["SERVER_NAME"]; - mail.Body = mail.Body.Replace( - string.Format("http://{0}", serverName), - string.Format("https://{0}", serverName)); - } - - // send it - var sender = new SmtpClient(); - sender.Send(mail); - } - - private static string ReplaceLinks(string text) - { - string domain = GlobalSettings.UseSSL ? "https://" : "http://"; - domain += HttpContext.Current.Request.ServerVariables["SERVER_NAME"] + ":" + - HttpContext.Current.Request.Url.Port + "/"; - text = text.Replace("href=\"/", "href=\"" + domain); - text = text.Replace("src=\"/", "src=\"" + domain); - return text; + nService.SendNotifications( + pUser, documentObject.Content, action.Letter.ToString(CultureInfo.InvariantCulture), ui.Text(action.Alias), + new HttpContextWrapper(HttpContext.Current), + (user, strings) => ui.Text("notifications", "mailSubject", strings, mailingUser), + (user, strings) => UmbracoSettings.NotificationDisableHtmlEmail + ? ui.Text("notifications", "mailBody", strings, mailingUser) + : ui.Text("notifications", "mailBodyHtml", strings, mailingUser)); } /// @@ -328,80 +209,5 @@ namespace umbraco.cms.businesslogic.workflow MakeNew(user, node, c); } - /// - /// 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(); - Diff.Item[] diffs = Diff.DiffText1(oldText, newText); - - int pos = 0; - for (int n = 0; n < diffs.Length; n++) - { - Diff.Item 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 (int 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(); - } } } \ No newline at end of file diff --git a/src/umbraco.cms/umbraco.cms.csproj b/src/umbraco.cms/umbraco.cms.csproj index 595e07b40d..ea9437e8a4 100644 --- a/src/umbraco.cms/umbraco.cms.csproj +++ b/src/umbraco.cms/umbraco.cms.csproj @@ -106,7 +106,7 @@ false - + False ..\packages\ClientDependency.1.7.1.1\lib\ClientDependency.Core.dll diff --git a/src/umbraco.controls/umbraco.controls.csproj b/src/umbraco.controls/umbraco.controls.csproj index 9c3c80c1ac..abd0f53dce 100644 --- a/src/umbraco.controls/umbraco.controls.csproj +++ b/src/umbraco.controls/umbraco.controls.csproj @@ -68,7 +68,7 @@ false - + False ..\packages\ClientDependency.1.7.1.1\lib\ClientDependency.Core.dll diff --git a/src/umbraco.editorControls/umbraco.editorControls.csproj b/src/umbraco.editorControls/umbraco.editorControls.csproj index 50aabbe197..865b574024 100644 --- a/src/umbraco.editorControls/umbraco.editorControls.csproj +++ b/src/umbraco.editorControls/umbraco.editorControls.csproj @@ -114,7 +114,7 @@ {651E1350-91B6-44B7-BD60-7207006D7003} Umbraco.Web - + False ..\packages\ClientDependency.1.7.1.1\lib\ClientDependency.Core.dll