diff --git a/src/Umbraco.Core/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs index db3f47ba13..c670a75108 100644 --- a/src/Umbraco.Core/Models/DictionaryItem.cs +++ b/src/Umbraco.Core/Models/DictionaryItem.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Models { diff --git a/src/Umbraco.Core/Models/Language.cs b/src/Umbraco.Core/Models/Language.cs index 6986fae027..ef8ebbf931 100644 --- a/src/Umbraco.Core/Models/Language.cs +++ b/src/Umbraco.Core/Models/Language.cs @@ -3,7 +3,6 @@ using System.Globalization; using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Models { diff --git a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs index 0b456cda91..eee020eee7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs @@ -180,7 +180,7 @@ namespace Umbraco.Core.Persistence.Repositories } else { - translation.Id = Convert.ToInt32(Database.Insert(dto)); + translation.Id = Convert.ToInt32(Database.Insert(textDto)); translation.Key = entity.Key; } } diff --git a/src/Umbraco.Core/Services/ILocalizationService.cs b/src/Umbraco.Core/Services/ILocalizationService.cs index 82a9319884..5c3bc965ec 100644 --- a/src/Umbraco.Core/Services/ILocalizationService.cs +++ b/src/Umbraco.Core/Services/ILocalizationService.cs @@ -81,9 +81,16 @@ namespace Umbraco.Core.Services /// /// Gets a by its culture code /// - /// Culture Code + /// Culture Code - also refered to as the Friendly name /// - ILanguage GetLanguageByCultureCode(string culture); + ILanguage GetLanguageByCultureCode(string cultureName); + + /// + /// Gets a by its iso code + /// + /// Iso Code of the language (ie. en-US) + /// + ILanguage GetLanguageByIsoCode(string isoCode); /// /// Gets all available languages 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/IPackagingService.cs b/src/Umbraco.Core/Services/IPackagingService.cs index 9f318988d7..2f711eb8af 100644 --- a/src/Umbraco.Core/Services/IPackagingService.cs +++ b/src/Umbraco.Core/Services/IPackagingService.cs @@ -44,6 +44,22 @@ namespace Umbraco.Core.Services /// An enumrable list of generated DataTypeDefinitions IEnumerable ImportDataTypeDefinitions(XElement element, int userId = 0, bool raiseEvents = true); + /// + /// Imports and saves the 'DictionaryItems' part of the package xml as a list of + /// + /// Xml to import + /// Optional parameter indicating whether or not to raise events + /// An enumerable list of dictionary items + IEnumerable ImportDictionaryItems(XElement dictionaryItemElementList, bool raiseEvents = true); + + /// + /// Imports and saves the 'Languages' part of a package xml as a list of + /// + /// Xml to import + /// Optional parameter indicating whether or not to raise events + /// An enumerable list of generated languages + IEnumerable ImportLanguages(XElement languageElementList, bool raiseEvents = true); + /// /// Imports and saves package xml as /// @@ -78,5 +94,55 @@ namespace Umbraco.Core.Services /// Optional parameter indicating whether or not to raise events /// containing the xml representation of the Media object XElement Export(IMedia media, bool deep = false, bool raiseEvents = true); + + /// + /// Exports a list of items to xml as an + /// + /// List of Languages to export + /// Optional parameter indicating whether or not to raise events + /// containing the xml representation of the Language object + XElement Export(IEnumerable languages, bool raiseEvents = true); + + /// + /// Exports a single item to xml as an + /// + /// Language to export + /// Optional parameter indicating whether or not to raise events + /// containing the xml representation of the Language object + XElement Export(ILanguage language, bool raiseEvents = true); + + /// + /// Exports a list of items to xml as an + /// + /// List of dictionary items to export + /// Optional boolean indicating whether or not to include children + /// Optional parameter indicating whether or not to raise events + /// containing the xml representation of the IDictionaryItem objects + XElement Export(IEnumerable dictionaryItem, bool includeChildren = true, bool raiseEvents = true); + + /// + /// Exports a single item to xml as an + /// + /// Dictionary Item to export + /// Optional boolean indicating whether or not to include children + /// Optional parameter indicating whether or not to raise events + /// containing the xml representation of the IDictionaryItem object + XElement Export(IDictionaryItem dictionaryItem, bool includeChildren, bool raiseEvents = true); + + /// + /// Exports a list of Data Types + /// + /// List of data types to export + /// Optional parameter indicating whether or not to raise events + /// containing the xml representation of the IDataTypeDefinition objects + XElement Export(IEnumerable dataTypeDefinitions, bool raiseEvents = true); + + /// + /// Exports a single Data Type + /// + /// Data type to export + /// Optional parameter indicating whether or not to raise events + /// containing the xml representation of the IDataTypeDefinition object + XElement Export(IDataTypeDefinition dataTypeDefinition, bool raiseEvents = true); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/LocalizationService.cs b/src/Umbraco.Core/Services/LocalizationService.cs index 47284eb1ad..b7c0cb3b55 100644 --- a/src/Umbraco.Core/Services/LocalizationService.cs +++ b/src/Umbraco.Core/Services/LocalizationService.cs @@ -146,10 +146,10 @@ namespace Umbraco.Core.Services { repository.AddOrUpdate(dictionaryItem); uow.Commit(); - - SavedDictionaryItem.RaiseEvent(new SaveEventArgs(dictionaryItem, false), this); } + SavedDictionaryItem.RaiseEvent(new SaveEventArgs(dictionaryItem, false), this); + Audit.Add(AuditTypes.Save, "Save DictionaryItem performed by user", userId, dictionaryItem.Id); } @@ -170,10 +170,10 @@ namespace Umbraco.Core.Services //NOTE: The recursive delete is done in the repository repository.Delete(dictionaryItem); uow.Commit(); - - DeletedDictionaryItem.RaiseEvent(new DeleteEventArgs(dictionaryItem, false), this); } + DeletedDictionaryItem.RaiseEvent(new DeleteEventArgs(dictionaryItem, false), this); + Audit.Add(AuditTypes.Delete, "Delete DictionaryItem performed by user", userId, dictionaryItem.Id); } @@ -193,13 +193,29 @@ namespace Umbraco.Core.Services /// /// Gets a by its culture code /// - /// Culture Code + /// Culture Name - also refered to as the Friendly name /// - public ILanguage GetLanguageByCultureCode(string culture) + public ILanguage GetLanguageByCultureCode(string cultureName) { using (var repository = _repositoryFactory.CreateLanguageRepository(_uowProvider.GetUnitOfWork())) { - var query = Query.Builder.Where(x => x.CultureName == culture); + var query = Query.Builder.Where(x => x.CultureName == cultureName); + var items = repository.GetByQuery(query); + + return items.FirstOrDefault(); + } + } + + /// + /// Gets a by its iso code + /// + /// Iso Code of the language (ie. en-US) + /// + public ILanguage GetLanguageByIsoCode(string isoCode) + { + using (var repository = _repositoryFactory.CreateLanguageRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.IsoCode == isoCode); var items = repository.GetByQuery(query); return items.FirstOrDefault(); @@ -234,10 +250,10 @@ namespace Umbraco.Core.Services { repository.AddOrUpdate(language); uow.Commit(); - - SavedLanguage.RaiseEvent(new SaveEventArgs(language, false), this); } + SavedLanguage.RaiseEvent(new SaveEventArgs(language, false), this); + Audit.Add(AuditTypes.Save, "Save Language performed by user", userId, language.Id); } @@ -257,10 +273,10 @@ namespace Umbraco.Core.Services //NOTE: There isn't any constraints in the db, so possible references aren't deleted repository.Delete(language); uow.Commit(); - - DeletedLanguage.RaiseEvent(new DeleteEventArgs(language, false), this); } + DeletedLanguage.RaiseEvent(new DeleteEventArgs(language, false), this); + Audit.Add(AuditTypes.Delete, "Delete Language performed by user", userId, language.Id); } diff --git a/src/Umbraco.Core/Services/NotificationService.cs b/src/Umbraco.Core/Services/NotificationService.cs index 31c534470b..a750cd77fc 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,254 @@ 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.PropertyEditorAlias == Constants.PropertyEditors.TinyMCEAlias) + && 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(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 = + @" + + +" + 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.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 + { + using (mail) + { + using (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 970a278c3b..3dc7d8b61e 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; 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; @@ -54,49 +56,7 @@ namespace Umbraco.Core.Services _importedContentTypes = new Dictionary(); } - - #region Generic export methods - - internal void ExportToFile(string absoluteFilePath, string nodeType, int id) - { - XElement xml = null; - - if (nodeType.Equals("content", StringComparison.InvariantCultureIgnoreCase)) - { - var content = _contentService.GetById(id); - xml = Export(content); - } - - if (nodeType.Equals("media", StringComparison.InvariantCultureIgnoreCase)) - { - var media = _mediaService.GetById(id); - xml = Export(media); - } - - if (nodeType.Equals("contenttype", StringComparison.InvariantCultureIgnoreCase)) - { - var contentType = _contentTypeService.GetContentType(id); - xml = Export(contentType); - } - - if (nodeType.Equals("mediatype", StringComparison.InvariantCultureIgnoreCase)) - { - var mediaType = _contentTypeService.GetMediaType(id); - xml = Export(mediaType); - } - - if (nodeType.Equals("datatype", StringComparison.InvariantCultureIgnoreCase)) - { - var dataType = _dataTypeService.GetDataTypeDefinitionById(id); - xml = Export(dataType); - } - - if (xml != null) - xml.Save(absoluteFilePath); - } - - #endregion - + #region Content /// @@ -790,22 +750,35 @@ namespace Umbraco.Core.Services #region DataTypes /// - /// Export a list of data types + /// Exports a list of Data Types /// - /// - /// - internal XElement Export(IEnumerable dataTypeDefinitions) + /// List of data types to export + /// Optional parameter indicating whether or not to raise events + /// containing the xml representation of the IDataTypeDefinition objects + public XElement Export(IEnumerable dataTypeDefinitions, bool raiseEvents = true) { var container = new XElement("DataTypes"); - foreach (var d in dataTypeDefinitions) + foreach (var dataTypeDefinition in dataTypeDefinitions) { - container.Add(Export(d)); + container.Add(Export(dataTypeDefinition, raiseEvents)); } return container; } - internal XElement Export(IDataTypeDefinition dataTypeDefinition) + /// + /// Exports a single Data Type + /// + /// Data type to export + /// Optional parameter indicating whether or not to raise events + /// containing the xml representation of the IDataTypeDefinition object + public XElement Export(IDataTypeDefinition dataTypeDefinition, bool raiseEvents = true) { + if (raiseEvents) + { + if (ExportingDataType.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinition), this)) + return default(XElement); + } + var prevalues = new XElement("PreValues"); var prevalueList = _dataTypeService.GetPreValuesCollectionByDataTypeId(dataTypeDefinition.Id) @@ -830,6 +803,9 @@ namespace Umbraco.Core.Services xml.Add(new XAttribute("Definition", dataTypeDefinition.Key)); xml.Add(new XAttribute("DatabaseType", dataTypeDefinition.DatabaseType.ToString())); + if (raiseEvents) + DataTypeExported.RaiseEvent(new SaveEventArgs(xml, false), this); + return xml; } @@ -837,7 +813,7 @@ namespace Umbraco.Core.Services /// Imports and saves package xml as /// /// Xml to import - /// + /// Optional id of the user /// Optional parameter indicating whether or not to raise events /// An enumrable list of generated DataTypeDefinitions public IEnumerable ImportDataTypeDefinitions(XElement element, int userId = 0, bool raiseEvents = true) @@ -946,12 +922,232 @@ namespace Umbraco.Core.Services #endregion #region Dictionary Items + + /// + /// Exports a list of items to xml as an + /// + /// List of dictionary items to export + /// Optional boolean indicating whether or not to include children + /// Optional parameter indicating whether or not to raise events + /// containing the xml representation of the IDictionaryItem objects + public XElement Export(IEnumerable dictionaryItem, bool includeChildren = true, bool raiseEvents = true) + { + var xml = new XElement("DictionaryItems"); + foreach (var item in dictionaryItem) + { + xml.Add(Export(item, includeChildren, raiseEvents)); + } + return xml; + } + + /// + /// Exports a single item to xml as an + /// + /// Dictionary Item to export + /// Optional boolean indicating whether or not to include children + /// Optional parameter indicating whether or not to raise events + /// containing the xml representation of the IDictionaryItem object + public XElement Export(IDictionaryItem dictionaryItem, bool includeChildren, bool raiseEvents = true) + { + if (raiseEvents) + { + if (ExportingDictionaryItem.IsRaisedEventCancelled(new SaveEventArgs(dictionaryItem), this)) + return default(XElement); + } + + var xml = new XElement("DictionaryItem", new XAttribute("Key", dictionaryItem.ItemKey)); + foreach (var translation in dictionaryItem.Translations) + { + xml.Add(new XElement("Value", + new XAttribute("LanguageId", translation.Language.Id), + new XAttribute("LanguageCultureAlias", translation.Language.IsoCode), + new XCData(translation.Value))); + } + + if (includeChildren) + { + var children = _localizationService.GetDictionaryItemChildren(dictionaryItem.Key); + foreach (var child in children) + { + xml.Add(Export(child, true)); + } + } + + if (raiseEvents) + DictionaryItemExported.RaiseEvent(new SaveEventArgs(xml, false), this); + + return xml; + } + + /// + /// Imports and saves the 'DictionaryItems' part of the package xml as a list of + /// + /// Xml to import + /// Optional parameter indicating whether or not to raise events + /// An enumerable list of dictionary items + public IEnumerable ImportDictionaryItems(XElement dictionaryItemElementList, bool raiseEvents = true) + { + if (raiseEvents) + { + if (ImportingDictionaryItem.IsRaisedEventCancelled(new SaveEventArgs(dictionaryItemElementList), this)) + return Enumerable.Empty(); + } + + var languages = _localizationService.GetAllLanguages().ToList(); + return ImportDictionaryItems(dictionaryItemElementList, languages, raiseEvents); + } + + private IEnumerable ImportDictionaryItems(XElement dictionaryItemElementList, List languages, bool raiseEvents) + { + var items = new List(); + foreach (var dictionaryItemElement in dictionaryItemElementList.Elements("DictionaryItem")) + items.AddRange(ImportDictionaryItem(dictionaryItemElement, languages)); + + if (raiseEvents) + DictionaryItemImported.RaiseEvent(new SaveEventArgs(items, false), this); + + return items; + } + + private IEnumerable ImportDictionaryItem(XElement dictionaryItemElement, List languages) + { + var items = new List(); + + IDictionaryItem dictionaryItem; + var key = dictionaryItemElement.Attribute("Key").Value; + if (_localizationService.DictionaryItemExists(key)) + dictionaryItem = GetAndUpdateDictionaryItem(key, dictionaryItemElement, languages); + else + dictionaryItem = CreateNewDictionaryItem(key, dictionaryItemElement, languages); + _localizationService.Save(dictionaryItem); + items.Add(dictionaryItem); + items.AddRange(ImportDictionaryItems(dictionaryItemElement, languages, true)); + return items; + } + + private IDictionaryItem GetAndUpdateDictionaryItem(string key, XElement dictionaryItemElement, List languages) + { + var dictionaryItem = _localizationService.GetDictionaryItemByKey(key); + var translations = dictionaryItem.Translations.ToList(); + foreach (var valueElement in dictionaryItemElement.Elements("Value").Where(v => DictionaryValueIsNew(translations, v))) + AddDictionaryTranslation(translations, valueElement, languages); + dictionaryItem.Translations = translations; + return dictionaryItem; + } + + private static DictionaryItem CreateNewDictionaryItem(string key, XElement dictionaryItemElement, List languages) + { + var dictionaryItem = new DictionaryItem(key); + var translations = new List(); + + foreach (var valueElement in dictionaryItemElement.Elements("Value")) + AddDictionaryTranslation(translations, valueElement, languages); + + dictionaryItem.Translations = translations; + return dictionaryItem; + } + + private static bool DictionaryValueIsNew(IEnumerable translations, XElement valueElement) + { + return translations.All(t => + String.Compare(t.Language.IsoCode, valueElement.Attribute("LanguageCultureAlias").Value, StringComparison.InvariantCultureIgnoreCase) != 0 + ); + } + + private static void AddDictionaryTranslation(ICollection translations, XElement valueElement, IEnumerable languages) + { + var languageId = valueElement.Attribute("LanguageCultureAlias").Value; + var language = languages.SingleOrDefault(l => l.IsoCode == languageId); + if (language == null) + return; + var translation = new DictionaryTranslation(language, valueElement.Value); + translations.Add(translation); + } + #endregion #region Files #endregion #region Languages + + /// + /// Exports a list of items to xml as an + /// + /// List of Languages to export + /// Optional parameter indicating whether or not to raise events + /// containing the xml representation of the ILanguage objects + public XElement Export(IEnumerable languages, bool raiseEvents = true) + { + var xml = new XElement("Languages"); + foreach (var language in languages) + { + xml.Add(Export(language, raiseEvents)); + } + return xml; + } + + /// + /// Exports a single item to xml as an + /// + /// Language to export + /// Optional parameter indicating whether or not to raise events + /// containing the xml representation of the ILanguage object + public XElement Export(ILanguage language, bool raiseEvents = true) + { + if (raiseEvents) + { + if (ExportingLanguage.IsRaisedEventCancelled(new SaveEventArgs(language), this)) + return default(XElement); + } + + var xml = new XElement("Language", + new XAttribute("Id", language.Id), + new XAttribute("CultureAlias", language.IsoCode), + new XAttribute("FriendlyName", language.CultureName)); + + if (raiseEvents) + LanguageExported.RaiseEvent(new SaveEventArgs(xml, false), this); + + return xml; + } + + /// + /// Imports and saves the 'Languages' part of a package xml as a list of + /// + /// Xml to import + /// Optional parameter indicating whether or not to raise events + /// An enumerable list of generated languages + public IEnumerable ImportLanguages(XElement languageElementList, bool raiseEvents = true) + { + if (raiseEvents) + { + if (ImportingLanguage.IsRaisedEventCancelled(new SaveEventArgs(languageElementList), this)) + return Enumerable.Empty(); + } + + var list = new List(); + foreach (var languageElement in languageElementList.Elements("Language")) + { + var isoCode = languageElement.Attribute("CultureAlias").Value; + var existingLanguage = _localizationService.GetLanguageByIsoCode(isoCode); + if (existingLanguage == null) + { + var langauge = new Language(isoCode) + { + CultureName = languageElement.Attribute("FriendlyName").Value + }; + _localizationService.Save(langauge); + list.Add(langauge); + } + } + + if (raiseEvents) + LanguageImported.RaiseEvent(new SaveEventArgs(list, false), this); + + return list; + } + #endregion #region Macros @@ -1222,6 +1418,9 @@ namespace Umbraco.Core.Services #endregion + #region Stylesheets + #endregion + #region Event Handlers /// /// Occurs before Importing Content @@ -1283,6 +1482,56 @@ namespace Umbraco.Core.Services /// public static event TypedEventHandler> DataTypeImported; + /// + /// Occurs before Exporting DataType + /// + public static event TypedEventHandler> ExportingDataType; + + /// + /// Occurs after DataType is Exported to Xml + /// + public static event TypedEventHandler> DataTypeExported; + + /// + /// Occurs before Importing DictionaryItem + /// + public static event TypedEventHandler> ImportingDictionaryItem; + + /// + /// Occurs after DictionaryItem is Imported and Saved + /// + public static event TypedEventHandler> DictionaryItemImported; + + /// + /// Occurs before Exporting DictionaryItem + /// + public static event TypedEventHandler> ExportingDictionaryItem; + + /// + /// Occurs after DictionaryItem is Exported to Xml + /// + public static event TypedEventHandler> DictionaryItemExported; + + /// + /// Occurs before Importing Language + /// + public static event TypedEventHandler> ImportingLanguage; + + /// + /// Occurs after Language is Imported and Saved + /// + public static event TypedEventHandler> LanguageImported; + + /// + /// Occurs before Exporting Language + /// + public static event TypedEventHandler> ExportingLanguage; + + /// + /// Occurs after Language is Exported to Xml + /// + public static event TypedEventHandler> LanguageExported; + /// /// Occurs before Importing Template /// 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.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs index 554c0f7ec4..e8e25068db 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs @@ -180,7 +180,6 @@ namespace Umbraco.Tests.Persistence.Repositories } } - [NUnit.Framework.Ignore] [Test] public void Can_Perform_Update_On_DictionaryRepository() { @@ -209,6 +208,35 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Can_Perform_Update_WithNewTranslation_On_DictionaryRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + var languageRepository = new LanguageRepository(unitOfWork); + var repository = new DictionaryRepository(unitOfWork, languageRepository); + + var languageNo = new Language("nb-NO") { CultureName = "nb-NO" }; + ServiceContext.LocalizationService.Save(languageNo); + + // Act + var item = repository.Get(1); + var translations = item.Translations.ToList(); + translations.Add(new DictionaryTranslation(languageNo, "Les mer")); + item.Translations = translations; + + repository.AddOrUpdate(item); + unitOfWork.Commit(); + + var dictionaryItem = repository.Get(1); + + // Assert + Assert.That(dictionaryItem, Is.Not.Null); + Assert.That(dictionaryItem.Translations.Count(), Is.EqualTo(3)); + Assert.That(dictionaryItem.Translations.Single(t => t.Language.IsoCode == "nb-NO").Value, Is.EqualTo("Les mer")); + } + [Test] public void Can_Perform_Delete_On_DictionaryRepository() { diff --git a/src/Umbraco.Tests/Services/Importing/Dictionary-Package.xml b/src/Umbraco.Tests/Services/Importing/Dictionary-Package.xml new file mode 100644 index 0000000000..6fa5a3d15f --- /dev/null +++ b/src/Umbraco.Tests/Services/Importing/Dictionary-Package.xml @@ -0,0 +1,36 @@ + + + + + + Dictionary-Package + 1.0 + MIT license + http://not.available + + 3 + 0 + 0 + + + + Test + http://not.available + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/Importing/ImportResources.Designer.cs b/src/Umbraco.Tests/Services/Importing/ImportResources.Designer.cs index 1d06f2c885..c840f08842 100644 --- a/src/Umbraco.Tests/Services/Importing/ImportResources.Designer.cs +++ b/src/Umbraco.Tests/Services/Importing/ImportResources.Designer.cs @@ -88,6 +88,32 @@ namespace Umbraco.Tests.Services.Importing { } } + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8" ?> + ///<umbPackage> + /// <files /> + /// <info> + /// <package> + /// <name>Dictionary-Package</name> + /// <version>1.0</version> + /// <license url="http://www.opensource.org/licenses/mit-license.php">MIT license</license> + /// <url>http://not.available</url> + /// <requirements> + /// <major>3</major> + /// <minor>0</minor> + /// <patch>0</patch> + /// </requirements> + /// </package> + /// <author> + /// <name>Test</name> + /// <website>http://not.available</w [rest of string was truncated]";. + /// + internal static string Dictionary_Package { + get { + return ResourceManager.GetString("Dictionary_Package", resourceCulture); + } + } + /// /// Looks up a localized string similar to <?xml version="1.0" encoding="UTF-8" standalone="no"?> ///<umbPackage> diff --git a/src/Umbraco.Tests/Services/Importing/ImportResources.resx b/src/Umbraco.Tests/Services/Importing/ImportResources.resx index 180b74be79..dd47151b73 100644 --- a/src/Umbraco.Tests/Services/Importing/ImportResources.resx +++ b/src/Umbraco.Tests/Services/Importing/ImportResources.resx @@ -145,4 +145,7 @@ checkboxlist-content-package-legacyids.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + dictionary-package.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs b/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs index e56abb548f..a53edac3a4 100644 --- a/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs +++ b/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs @@ -2,9 +2,9 @@ using System.Linq; using System.Xml.Linq; using NUnit.Framework; +using Umbraco.Core.Models; using Umbraco.Core; using Umbraco.Core.Models.Rdbms; -using umbraco.editorControls.MultiNodeTreePicker; namespace Umbraco.Tests.Services.Importing { @@ -388,5 +388,148 @@ namespace Umbraco.Tests.Services.Importing Assert.That(allTemplates.Count(), Is.EqualTo(numberOfTemplates)); Assert.That(allTemplates.First(x => x.Alias == "umbHomepage").Content, Contains.Substring("THIS HAS BEEN UPDATED!")); } + + [Test] + public void PackagingService_Can_Import_DictionaryItems() + { + // Arrange + const string expectedEnglishParentValue = "ParentValue"; + const string expectedNorwegianParentValue = "ForelderVerdi"; + const string expectedEnglishChildValue = "ChildValue"; + const string expectedNorwegianChildValue = "BarnVerdi"; + + var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); + var dictionaryItemsElement = newPackageXml.Elements("DictionaryItems").First(); + + AddLanguages(); + + // Act + ServiceContext.PackagingService.ImportDictionaryItems(dictionaryItemsElement); + + // Assert + AssertDictionaryItem("Parent", expectedEnglishParentValue, "en-GB"); + AssertDictionaryItem("Parent", expectedNorwegianParentValue, "nb-NO"); + AssertDictionaryItem("Child", expectedEnglishChildValue, "en-GB"); + AssertDictionaryItem("Child", expectedNorwegianChildValue, "nb-NO"); + } + + [Test] + public void PackagingService_WhenExistingDictionaryKey_ImportsNewChildren() + { + // Arrange + const string expectedEnglishParentValue = "ExistingParentValue"; + const string expectedNorwegianParentValue = "EksisterendeForelderVerdi"; + const string expectedEnglishChildValue = "ChildValue"; + const string expectedNorwegianChildValue = "BarnVerdi"; + + var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); + var dictionaryItemsElement = newPackageXml.Elements("DictionaryItems").First(); + + AddLanguages(); + AddExistingEnglishAndNorwegianParentDictionaryItem(expectedEnglishParentValue, expectedNorwegianParentValue); + + // Act + ServiceContext.PackagingService.ImportDictionaryItems(dictionaryItemsElement); + + // Assert + AssertDictionaryItem("Parent", expectedEnglishParentValue, "en-GB"); + AssertDictionaryItem("Parent", expectedNorwegianParentValue, "nb-NO"); + AssertDictionaryItem("Child", expectedEnglishChildValue, "en-GB"); + AssertDictionaryItem("Child", expectedNorwegianChildValue, "nb-NO"); + } + + [Test] + public void PackagingService_WhenExistingDictionaryKey_OnlyAddsNewLanguages() + { + // Arrange + const string expectedEnglishParentValue = "ExistingParentValue"; + const string expectedNorwegianParentValue = "ForelderVerdi"; + const string expectedEnglishChildValue = "ChildValue"; + const string expectedNorwegianChildValue = "BarnVerdi"; + + var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); + var dictionaryItemsElement = newPackageXml.Elements("DictionaryItems").First(); + + AddLanguages(); + AddExistingEnglishParentDictionaryItem(expectedEnglishParentValue); + + // Act + ServiceContext.PackagingService.ImportDictionaryItems(dictionaryItemsElement); + + // Assert + AssertDictionaryItem("Parent", expectedEnglishParentValue, "en-GB"); + AssertDictionaryItem("Parent", expectedNorwegianParentValue, "nb-NO"); + AssertDictionaryItem("Child", expectedEnglishChildValue, "en-GB"); + AssertDictionaryItem("Child", expectedNorwegianChildValue, "nb-NO"); + } + + [Test] + public void PackagingService_Can_Import_Languages() + { + // Arrange + var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); + var LanguageItemsElement = newPackageXml.Elements("Languages").First(); + + // Act + var languages = ServiceContext.PackagingService.ImportLanguages(LanguageItemsElement); + var allLanguages = ServiceContext.LocalizationService.GetAllLanguages(); + + // Assert + Assert.That(languages.Any(x => x.HasIdentity == false), Is.False); + foreach (var language in languages) + { + Assert.That(allLanguages.Any(x => x.IsoCode == language.IsoCode), Is.True); + } + } + + private void AddLanguages() + { + var norwegian = new Core.Models.Language("nb-NO"); + var english = new Core.Models.Language("en-GB"); + ServiceContext.LocalizationService.Save(norwegian, 0); + ServiceContext.LocalizationService.Save(english, 0); + } + + private void AssertDictionaryItem(string key, string expectedValue, string cultureCode) + { + Assert.That(ServiceContext.LocalizationService.DictionaryItemExists(key), "DictionaryItem key does not exist"); + var dictionaryItem = ServiceContext.LocalizationService.GetDictionaryItemByKey(key); + var translation = dictionaryItem.Translations.SingleOrDefault(i => i.Language.IsoCode == cultureCode); + Assert.IsNotNull(translation, "Translation to {0} was not added", cultureCode); + var value = translation.Value; + Assert.That(value, Is.EqualTo(expectedValue), "Translation value was not set"); + } + + private void AddExistingEnglishParentDictionaryItem(string expectedEnglishParentValue) + { + var languages = ServiceContext.LocalizationService.GetAllLanguages().ToList(); + var englishLanguage = languages.Single(l => l.IsoCode == "en-GB"); + ServiceContext.LocalizationService.Save( + new DictionaryItem("Parent") + { + Translations = new List + { + new DictionaryTranslation(englishLanguage, expectedEnglishParentValue), + } + } + ); + } + + private void AddExistingEnglishAndNorwegianParentDictionaryItem(string expectedEnglishParentValue, string expectedNorwegianParentValue) + { + var languages = ServiceContext.LocalizationService.GetAllLanguages().ToList(); + var englishLanguage = languages.Single(l => l.IsoCode == "en-GB"); + var norwegianLanguage = languages.Single(l => l.IsoCode == "nb-NO"); + ServiceContext.LocalizationService.Save( + new DictionaryItem("Parent") + { + Translations = new List + { + new DictionaryTranslation(englishLanguage, expectedEnglishParentValue), + new DictionaryTranslation(norwegianLanguage, expectedNorwegianParentValue), + } + } + ); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/PackagingServiceTests.cs b/src/Umbraco.Tests/Services/PackagingServiceTests.cs index 1ed7807403..0124b4ca1a 100644 --- a/src/Umbraco.Tests/Services/PackagingServiceTests.cs +++ b/src/Umbraco.Tests/Services/PackagingServiceTests.cs @@ -1,41 +1,89 @@ -using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; using NUnit.Framework; -using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Tests.TestHelpers.Entities; +using Umbraco.Tests.Services.Importing; namespace Umbraco.Tests.Services { - //[TestFixture] - //public class PackagingServiceTests : BaseServiceTest - //{ - // [Test] - // public void Export_Content() - // { - // var yesNo = DataTypesResolver.Current.GetById(new Guid(Constants.PropertyEditors.TrueFalse)); - // var txtField = DataTypesResolver.Current.GetById(new Guid(Constants.PropertyEditors.Textbox)); + [TestFixture] + public class PackagingServiceTests : BaseServiceTest + { + [SetUp] + public override void Initialize() + { + base.Initialize(); + } - // var contentWithDataType = MockedContentTypes.CreateSimpleContentType( - // "test", - // "Test", - // new PropertyTypeCollection( - // new PropertyType[] - // { - // new PropertyType(new DataTypeDefinition(-1, txtField.Id) - // { - // Name = "Testing Textfield", DatabaseType = DataTypeDatabaseType.Ntext - // }), - // new PropertyType(new DataTypeDefinition(-1, yesNo.Id) - // { - // Name = "Testing intfield", DatabaseType = DataTypeDatabaseType.Integer - // }) - // })); + [TearDown] + public override void TearDown() + { + base.TearDown(); + } - // var content = MockedContent.CreateSimpleContent(contentWithDataType); - // content.Name = "Test"; + [Test] + public void PackagingService_Can_Export_DictionaryItems() + { + // Arrange + CreateDictionaryData(); + var dictionaryItem = ServiceContext.LocalizationService.GetDictionaryItemByKey("Parent"); - // var exported = ServiceContext.PackagingService.Export(content); + var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); + var dictionaryItemsElement = newPackageXml.Elements("DictionaryItems").First(); - // } - //} + // Act + var xml = ServiceContext.PackagingService.Export(new []{dictionaryItem}); + + // Assert + Assert.That(xml.ToString(), Is.EqualTo(dictionaryItemsElement.ToString())); + } + + [Test] + public void PackagingService_Can_Export_Languages() + { + // Arrange + var languageNbNo = new Language("nb-NO") { CultureName = "Norwegian" }; + ServiceContext.LocalizationService.Save(languageNbNo); + + var languageEnGb = new Language("en-GB") { CultureName = "English (United Kingdom)" }; + ServiceContext.LocalizationService.Save(languageEnGb); + + var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); + var languageItemsElement = newPackageXml.Elements("Languages").First(); + + // Act + var xml = ServiceContext.PackagingService.Export(new[] { languageNbNo, languageEnGb }); + + // Assert + Assert.That(xml.ToString(), Is.EqualTo(languageItemsElement.ToString())); + } + + private void CreateDictionaryData() + { + var languageNbNo = new Language("nb-NO") { CultureName = "nb-NO" }; + ServiceContext.LocalizationService.Save(languageNbNo); + + var languageEnGb = new Language("en-GB") { CultureName = "en-GB" }; + ServiceContext.LocalizationService.Save(languageEnGb); + + var parentItem = new DictionaryItem("Parent"); + var parentTranslations = new List + { + new DictionaryTranslation(languageNbNo, "ForelderVerdi"), + new DictionaryTranslation(languageEnGb, "ParentValue") + }; + parentItem.Translations = parentTranslations; + ServiceContext.LocalizationService.Save(parentItem); + + var childItem = new DictionaryItem(parentItem.Key, "Child"); + var childTranslations = new List + { + new DictionaryTranslation(languageNbNo, "BarnVerdi"), + new DictionaryTranslation(languageEnGb, "ChildValue") + }; + childItem.Translations = childTranslations; + ServiceContext.LocalizationService.Save(childItem); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index c5eb46bb1e..955992e644 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -638,6 +638,7 @@ Designer + 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.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 1c9c21cdbe..c4b85332fa 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -467,20 +467,21 @@ Rediger dine notificeringer for %0% - + - Hej %0%

Dette er en automatisk mail for at informere dig om at opgaven '%1%' er blevet udførtpå siden '%2%' af brugeren '%3%'

Opdateringssammendrag:

%6%

Hav en fortsat god dag!

De bedste hilsner fra umbraco robotten

]]>
+ Hej %0%

Dette er en automatisk mail for at informere dig om at opgaven '%1%' er blevet udførtpå siden '%2%' af brugeren '%3%'

Opdateringssammendrag:

%6%

Hav en fortsat god dag!

De bedste hilsner fra umbraco robotten

]]>
[%0%] Notificering om %1% udført på %2% Notificeringer diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml index 3721090de9..c2c16cccec 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml @@ -497,15 +497,15 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Bearbeiten Sie Ihre Benachrichtigungseinstellungen für '%0%' -Hallo %0%, + Hallo %0%, -die Aufgabe '%1%' (von Benutzer '%3%') an der Seite '%2%' wurde ausgeführt. + die Aufgabe '%1%' (von Benutzer '%3%') an der Seite '%2%' wurde ausgeführt. -Zum Bearbeiten verwenden Sie bitte diesen Link: http://%4%/umbraco/actions/editContent.aspx?id=%5% + Zum Bearbeiten verwenden Sie bitte diesen Link: http://%4%/#/content/content/edit/%5% -Einen schönen Tag wünscht -Ihr freundlicher Umbraco-Robot - + Einen schönen Tag wünscht + Ihr freundlicher Umbraco-Robot + <p>Hallo %0%,</p> diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index f417282ac2..1c04ba81e1 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -584,7 +584,7 @@ To manage your website, simply open the umbraco back office and start adding con has been performed on the page '%2%' by the user '%3%' - Go to http://%4%/actions/editContent.aspx?id=%5% to edit. + Go to http://%4%/#/content/content/edit/%5% to edit. Have a nice day! @@ -593,14 +593,12 @@ To manage your website, simply open the umbraco back office and start adding con Hi %0%

This is an automated mail to inform you that the task '%1%' - has been performed on the page '%2%' + has been performed on the page '%2%' by the user '%3%'

@@ -612,9 +610,7 @@ To manage your website, simply open the umbraco back office and start adding con

diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index b4266e8fd2..4311d1f04c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -582,7 +582,7 @@ To manage your website, simply open the umbraco back office and start adding con has been performed on the page '%2%' by the user '%3%' - Go to http://%4%/actions/editContent.aspx?id=%5% to edit. + Go to http://%4%/#/content/content/edit/%5% to edit. Have a nice day! @@ -598,9 +598,7 @@ To manage your website, simply open the umbraco back office and start adding con

@@ -612,9 +610,7 @@ To manage your website, simply open the umbraco back office and start adding con

diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml index 30bc95d93c..bc45128d7a 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml @@ -433,8 +433,8 @@ However, Runway offers an easy foundation based on best practices to get you sta Edite su notificación para %0% - Hola %0% Esto es un e-mail automático para informarle que la tarea '%1%' ha sido realizada sobre la página '%2%' por el usuario '%3%' Vaya a http://%4%/actions/editContent.aspx?id=%5% para editarla. ¡Espero que tenga un buen día! Saludos del robot de umbraco - Hola %0%

Esto es un e-mail generado automáticamente para informarle que la tarea '%1%' ha sido realizada sobre la página '%2%' por el usuario '%3%'

Resumen de actualización:

%6%

¡Espero que tenga un buen día!

Saludos del robot umbraco.

]]>
+ Hola %0% Esto es un e-mail automático para informarle que la tarea '%1%' ha sido realizada sobre la página '%2%' por el usuario '%3%' Vaya a http://%4%/#/content/content/edit/%5% para editarla. ¡Espero que tenga un buen día! Saludos del robot de umbraco + Hola %0%

Esto es un e-mail generado automáticamente para informarle que la tarea '%1%' ha sido realizada sobre la página '%2%' por el usuario '%3%'

Resumen de actualización:

%6%

¡Espero que tenga un buen día!

Saludos del robot umbraco.

]]>
[%0%] Notificación acerca de %1% realizado en %2% Notificaciones diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml index 0f10085ab9..7524dd527c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml @@ -534,7 +534,7 @@ Pour gerer votre site, accéder simplement au back office et commencez à contri Ceci est un email automatique pour vous informer que la tâche '%1%' a été activée sur la page '%2%' par l'utilisateur '%3%' - Allez à http://%4%/actions/editContent.aspx?id=%5% pour éditer. + Allez à http://%4%/#/content/content/edit/%5% pour éditer. Bonne journée! @@ -549,10 +549,8 @@ Pour gerer votre site, accéder simplement au back office et commencez à contri par l'utilisateur '%3%'

@@ -564,9 +562,7 @@ Pour gerer votre site, accéder simplement au back office et commencez à contri

diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/he.xml b/src/Umbraco.Web.UI/umbraco/config/lang/he.xml index 201001ffbc..d22a0474e1 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/he.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/he.xml @@ -509,9 +509,7 @@ To manage your website, simply open the umbraco back office and start adding con

@@ -523,9 +521,7 @@ To manage your website, simply open the umbraco back office and start adding con

diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/it.xml b/src/Umbraco.Web.UI/umbraco/config/lang/it.xml index d925460d57..f75a1d73be 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/it.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/it.xml @@ -458,7 +458,7 @@ Per gestire il tuo sito web, è sufficiente aprire il back office di Umbraco e i è stata eseguita sulla pagina '%2%' dall'utente '%3%' - Vai al link http://%4%/actions/editContent.aspx?id=%5% per effettuare le modifiche. + Vai al link http://%4%/#/content/content/edit/%5% per effettuare le modifiche. Buona giornata! @@ -473,9 +473,7 @@ Per gestire il tuo sito web, è sufficiente aprire il back office di Umbraco e i

@@ -487,9 +485,7 @@ Per gestire il tuo sito web, è sufficiente aprire il back office di Umbraco e i

diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml index 2cea7d7838..bf60d41574 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml @@ -496,7 +496,7 @@ Runwayをインストールして作られた新しいウェブサイトがど ページ '%2%' 上のタスク'%1%'から 自動的にメールします。 - 編集はこちらから: http://%4%/actions/editContent.aspx?id=%5% + 編集はこちらから: http://%4%/#/content/content/edit/%5% 早々 @@ -507,9 +507,7 @@ Runwayをインストールして作られた新しいウェブサイトがど

ユーザー '%3%' によりページ '%2%' 上のタスク '%1%' から自動的にメールします。

@@ -521,9 +519,7 @@ Runwayをインストールして作られた新しいウェブサイトがど

diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml index 0a7ba87cff..2ec2088a01 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml @@ -475,7 +475,7 @@ 사용자 '%3%' 가 작업 '%1%' 를 페이지 '%2%' 에서 진행했음을 알리는 자동 발송 메일입니다. - 편집하시려면 http://%4%/actions/editContent.aspx?id=%5% 로 이동하세요 + 편집하시려면 http://%4%/#/content/content/edit/%5% 로 이동하세요 좋은 하루 되세요! @@ -488,9 +488,7 @@

@@ -502,9 +500,7 @@

diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml index 39c7f53647..02f41b141f 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml @@ -437,7 +437,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je is uitgevoerd op pagina '%2%' door gebruiker '%3%' - Ga naar http://%4%/actions/editContent.aspx?id=%5% om te bewerken. + Ga naar http://%4%/#/content/content/edit/%5% om te bewerken. Een prettige dag! @@ -451,10 +451,8 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je door gebruiker '%3%'

@@ -466,9 +464,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je

diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/no.xml b/src/Umbraco.Web.UI/umbraco/config/lang/no.xml index 4e76541b2e..ed024af119 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/no.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/no.xml @@ -459,9 +459,7 @@ Vennlig hilsen umbraco roboten </p> <div style="margin: 8px 0; padding: 8px; display: block;"> <br /> - <a style="color: white; font-weight: bold; background-color: #66cc66; text-decoration : none; margin-right: 20px; border: 8px solid #66cc66; width: 150px;" href="http://%4%/umbraco/actions/publish.aspx?id=%5%">&nbsp;&nbsp;PUBLISER&nbsp;&nbsp;</a> &nbsp; <a style="color: white; font-weight: bold; background-color: #5372c3; text-decoration : none; margin-right: 20px; border: 8px solid #5372c3; width: 150px;" href="http://%4%/umbraco/actions/editContent.aspx?id=%5%">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;REDIGER&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</a> &nbsp; - <a style="color: white; font-weight: bold; background-color: #ca4a4a; text-decoration : none; margin-right: 20px; border: 8px solid #ca4a4a; width: 150px;" href="http://%4%/umbraco/actions/delete.aspx?id=%5%">&nbsp;&nbsp;&nbsp;&nbsp;SLETT&nbsp;&nbsp;&nbsp;&nbsp;</a> <br /> </div> <p> @@ -473,9 +471,7 @@ Vennlig hilsen umbraco roboten <div style="margin: 8px 0; padding: 8px; display: block;"> <br /> - <a style="color: white; font-weight: bold; background-color: #66cc66; text-decoration : none; margin-right: 20px; border: 8px solid #66cc66; width: 150px;" href="http://%4%/umbraco/actions/publish.aspx?id=%5%">&nbsp;&nbsp;PUBLISER&nbsp;&nbsp;</a> &nbsp; <a style="color: white; font-weight: bold; background-color: #5372c3; text-decoration : none; margin-right: 20px; border: 8px solid #5372c3; width: 150px;" href="http://%4%/umbraco/actions/editContent.aspx?id=%5%">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;REDIGER&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</a> &nbsp; - <a style="color: white; font-weight: bold; background-color: #ca4a4a; text-decoration : none; margin-right: 20px; border: 8px solid #ca4a4a; width: 150px;" href="http://%4%/umbraco/actions/delete.aspx?id=%5%">&nbsp;&nbsp;&nbsp;&nbsp;SLETT&nbsp;&nbsp;&nbsp;&nbsp;</a> <br /> </div> diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml index 6db3d9e2a5..a9d419c9ab 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml @@ -426,10 +426,10 @@ Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]>
Edytuj powiadomienie dla %0% - Witaj %0%

To jest automatyczny e-mail, wysłany aby poinformować Cię, że polecenie '%1%' zostało wykonane na stronie '%2%' przez użytkownika '%3%'

Podsumowanie zmian:

%6%

Miłego dnia!

Pozdrowienia od robota Umbraco

]]>
+ Witaj %0%

To jest automatyczny e-mail, wysłany aby poinformować Cię, że polecenie '%1%' zostało wykonane na stronie '%2%' przez użytkownika '%3%'

Podsumowanie zmian:

%6%

Miłego dnia!

Pozdrowienia od robota Umbraco

]]>
[%0%] Powiadomienie o %1% wykonane na %2% Powiadomienie diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml b/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml index ec10a7bdce..bbb0e7e5e2 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml @@ -467,7 +467,7 @@ Pressione "próximo" para iniciar o assistente.]]>
Esta é uma mensagem automatizada de email para informar que a tarefa '%1%' foi realizada na página '%2%' pelo usuário '%3%' -Vá até http://%4%/actions/editContent.aspx?id=%5% para editar. +Vá até http://%4%/#/content/content/edit/%5% para editar. Tenha um bom dia! @@ -480,9 +480,7 @@ Vá até http://%4%/actions/editContent.aspx?id=%5% para editar.

@@ -494,9 +492,7 @@ Vá até http://%4%/actions/editContent.aspx?id=%5% para editar.

diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index d21847c9f8..667dcfe4a9 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -515,7 +515,7 @@ Операция '%1%' была произведена на странице '%2%' пользователем '%3%'. - Вы можете увидеть изменения и отредактировать, перейдя по ссылке http://%4%/actions/editContent.aspx?id=%5%. + Вы можете увидеть изменения и отредактировать, перейдя по ссылке http://%4%/#/content/content/edit/%5%. Удачи! @@ -529,9 +529,7 @@ @@ -544,9 +542,7 @@ diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml index bb7f487072..3db191537b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml @@ -496,7 +496,7 @@ Inställningar för notifieringar gällande {0} Hej {0} Detta mail skickas till dig automatiskt för att meddela att '{1}' har utförts på sidan '{2}' av användaren '{3}' Gå till http://{4}/actions/editContent.aspx?id={5} för att redigera. - Hej {0}

Detta mail skickas till dig automatiskt för att meddela att '{1}' har utförts på sidan '{2}' av användaren '{3}'

Sammanfattning av uppdateringen:

{6}

]]>
+ Hej {0}

Detta mail skickas till dig automatiskt för att meddela att '{1}' har utförts på sidan '{2}' av användaren '{3}'

Sammanfattning av uppdateringen:

{6}

]]>
[{0}] Meddelande för att informera om att {1} har utförts på {2} Notifieringar diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml index 49a39399bd..e868feb5b5 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml @@ -530,7 +530,7 @@ 您好!这是一封自动邮件,提醒您用户'%3%'执行'%1%'任务已经在完成'%2%'。 - 转到 http://%4%/actions/editContent.aspx?id=%5% 进行编辑 + 转到 http://%4%/#/content/content/edit/%5% 进行编辑 ]]> %0%:

@@ -538,9 +538,7 @@

您好!这是一封自动发送的邮件,告诉您任务'%1%'已在'%2%'被用户'%3%'执行

@@ -552,9 +550,7 @@

diff --git a/src/Umbraco.Web/NotificationServiceExtensions.cs b/src/Umbraco.Web/NotificationServiceExtensions.cs new file mode 100644 index 0000000000..8404a99a0e --- /dev/null +++ b/src/Umbraco.Web/NotificationServiceExtensions.cs @@ -0,0 +1,45 @@ +using System; +using System.Globalization; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Services; +using umbraco; +using umbraco.BusinessLogic.Actions; +using umbraco.interfaces; + +namespace Umbraco.Web +{ + internal static class NotificationServiceExtensions + { + internal static void SendNotification(this INotificationService service, IUmbracoEntity entity, IAction action, ApplicationContext applicationContext) + { + if (global::Umbraco.Web.UmbracoContext.Current == null) return; + service.SendNotification(entity, action, global::Umbraco.Web.UmbracoContext.Current); + } + + internal static void SendNotification(this INotificationService service, IUmbracoEntity entity, IAction action, UmbracoContext umbracoContext) + { + if (umbracoContext == null) return; + service.SendNotification(entity, action, umbracoContext, umbracoContext.Application); + } + + internal static void SendNotification(this INotificationService service, IUmbracoEntity entity, IAction action, UmbracoContext umbracoContext, ApplicationContext applicationContext) + { + if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); + if (applicationContext == null) throw new ArgumentNullException("applicationContext"); + + var user = umbracoContext.Security.CurrentUser; + applicationContext.Services.NotificationService.SendNotifications( + user, + entity, + action.Letter.ToString(CultureInfo.InvariantCulture), + ui.Text("actions", action.Alias), + umbracoContext.HttpContext, + (mailingUser, strings) => ui.Text("notifications", "mailSubject", strings, mailingUser), + (mailingUser, strings) => UmbracoConfig.For.UmbracoSettings().Content.DisableHtmlEmail + ? ui.Text("notifications", "mailBody", strings, mailingUser) + : ui.Text("notifications", "mailBodyHtml", strings, mailingUser)); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/NotificationsHandler.cs b/src/Umbraco.Web/Strategies/NotificationsHandler.cs new file mode 100644 index 0000000000..dc77a7b745 --- /dev/null +++ b/src/Umbraco.Web/Strategies/NotificationsHandler.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Services; +using umbraco; +using umbraco.BusinessLogic.Actions; + +namespace Umbraco.Web.Strategies +{ + /// + /// Subscribes to the relavent events in order to send out notifications + /// + public sealed class NotificationsHandler : ApplicationEventHandler + { + + protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + base.ApplicationStarted(umbracoApplication, applicationContext); + + //Send notifications for the published action + ContentService.Published += (sender, args) => + args.PublishedEntities.ForEach( + content => + applicationContext.Services.NotificationService.SendNotification( + content, ActionPublish.Instance, applicationContext)); + + //Send notifications for the update and created actions + ContentService.Saved += (sender, args) => + { + //need to determine if this is updating or if it is new + foreach (var entity in args.SavedEntities) + { + var dirty = (IRememberBeingDirty) entity; + if (dirty.WasPropertyDirty("Id")) + { + //it's new + applicationContext.Services.NotificationService.SendNotification( + entity, ActionNew.Instance, applicationContext); + } + else + { + //it's updating + applicationContext.Services.NotificationService.SendNotification( + entity, ActionUpdate.Instance, applicationContext); + } + } + }; + + //Send notifications for the delete action + ContentService.Deleted += (sender, args) => + args.DeletedEntities.ForEach( + content => + applicationContext.Services.NotificationService.SendNotification( + content, ActionDelete.Instance, applicationContext)); + + //Send notifications for the unpublish action + ContentService.UnPublished += (sender, args) => + args.PublishedEntities.ForEach( + content => + applicationContext.Services.NotificationService.SendNotification( + content, ActionUnPublish.Instance, applicationContext)); + + } + + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 94e3ea315b..c0e1e010a6 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 @@ -338,6 +338,7 @@ + @@ -407,6 +408,7 @@ + diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx.cs index 208b2a9780..78c4549a55 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx.cs @@ -8,6 +8,7 @@ using System.Web.SessionState; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; +using Umbraco.Web; using umbraco.BusinessLogic; using umbraco.BusinessLogic.Actions; using umbraco.cms.businesslogic.web; @@ -32,9 +33,9 @@ namespace umbraco.dialogs int docId; if (int.TryParse(Request.QueryString["id"], out docId)) { - - //TODO Send to publish!! - + //send notifications! TODO: This should be put somewhere centralized instead of hard coded directly here + ApplicationContext.Services.NotificationService.SendNotification( + ApplicationContext.Services.ContentService.GetById(docId), ActionToPublish.Instance, ApplicationContext); } } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/translation/default.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/translation/default.aspx.cs index 00d1cd76f4..2e5749c4f9 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/translation/default.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/translation/default.aspx.cs @@ -1,9 +1,13 @@ using System; using System.Data; +using System.Globalization; using System.IO; using System.Text; using System.Xml; +using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Web; using umbraco.BasePages; using umbraco.BusinessLogic; using umbraco.BusinessLogic.Actions; @@ -191,22 +195,15 @@ namespace umbraco.presentation.translation if (t != null) { //user auth and content node validation - if (t.Node.Id == int.Parse(taskNode.Attributes.GetNamedItem("id").Value) && (t.User.Id == base.getUser().Id || t.ParentUser.Id == base.getUser().Id)) + if (t.Node.Id == int.Parse(taskNode.Attributes.GetNamedItem("id").Value) && (t.User.Id == UmbracoUser.Id || t.ParentUser.Id == UmbracoUser.Id)) { // update node contents var d = new Document(t.Node.Id); - Document.Import(d.ParentId, getUser(), (XmlElement)taskNode); + Document.Import(d.ParentId, UmbracoUser, (XmlElement)taskNode); - /* d.Text = taskNode.Attributes.GetNamedItem("nodeName").Value.Trim(); - - // update data elements - foreach (XmlNode data in taskNode.SelectNodes("data")) - if (data.FirstChild != null) - d.getProperty(data.Attributes.GetNamedItem("alias").Value).Value = data.FirstChild.Value; - else - d.getProperty(data.Attributes.GetNamedItem("alias").Value).Value = ""; - */ + //send notifications! TODO: This should be put somewhere centralized instead of hard coded directly here + ApplicationContext.Services.NotificationService.SendNotification(d.Content, ActionTranslate.Instance, ApplicationContext); t.Closed = true; t.Save(); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs index a6e2d7e402..1dfa3362da 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs @@ -7,8 +7,12 @@ using System.Linq; using System.Web.Script.Services; using System.Web.Services; using System.Xml; +using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Web; using Umbraco.Web.WebServices; using umbraco.BasePages; using umbraco.BusinessLogic; @@ -160,12 +164,16 @@ namespace umbraco.presentation.webservices if (parentNode != null) content.SortNodes(ref parentNode); + //send notifications! TODO: This should be put somewhere centralized instead of hard coded directly here + ApplicationContext.Services.NotificationService.SendNotification(contentService.GetById(parentId), ActionSort.Instance, UmbracoContext, ApplicationContext); + } catch (Exception ex) { LogHelper.Error("Could not update content sort order", ex); } } + } [Serializable] 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/Packager/Installer.cs b/src/umbraco.cms/businesslogic/Packager/Installer.cs index ef9b77e16b..10e6176877 100644 --- a/src/umbraco.cms/businesslogic/Packager/Installer.cs +++ b/src/umbraco.cms/businesslogic/Packager/Installer.cs @@ -298,51 +298,27 @@ namespace umbraco.cms.businesslogic.packager foreach (var dataTypeDefinition in dataTypeDefinitions) { insPack.Data.DataTypes.Add(dataTypeDefinition.Id.ToString(CultureInfo.InvariantCulture)); - //saveNeeded = true; } } - /*foreach (XmlNode n in _packageConfig.DocumentElement.SelectNodes("//DataType")) - { - cms.businesslogic.datatype.DataTypeDefinition newDtd = cms.businesslogic.datatype.DataTypeDefinition.Import(n); - - if (newDtd != null) - { - insPack.Data.DataTypes.Add(newDtd.Id.ToString()); - saveNeeded = true; - } - }*/ - - //if (saveNeeded) { insPack.Save(); saveNeeded = false; } #endregion #region Languages - foreach (XmlNode n in Config.DocumentElement.SelectNodes("//Language")) + var languageItemsElement = rootElement.Descendants("Languages").FirstOrDefault(); + if (languageItemsElement != null) { - language.Language newLang = language.Language.Import(n); - - if (newLang != null) - { - insPack.Data.Languages.Add(newLang.id.ToString(CultureInfo.InvariantCulture)); - //saveNeeded = true; - } + var insertedLanguages = packagingService.ImportLanguages(languageItemsElement); + insPack.Data.Languages.AddRange(insertedLanguages.Select(l => l.Id.ToString())); } - - //if (saveNeeded) { insPack.Save(); saveNeeded = false; } + #endregion #region Dictionary items - foreach (XmlNode n in Config.DocumentElement.SelectNodes("./DictionaryItems/DictionaryItem")) + var dictionaryItemsElement = rootElement.Descendants("DictionaryItems").FirstOrDefault(); + if (dictionaryItemsElement != null) { - Dictionary.DictionaryItem newDi = Dictionary.DictionaryItem.Import(n); - - if (newDi != null) - { - insPack.Data.DictionaryItems.Add(newDi.id.ToString()); - //saveNeeded = true; - } + var insertedDictionaryItems = packagingService.ImportDictionaryItems(dictionaryItemsElement); + insPack.Data.DictionaryItems.AddRange(insertedDictionaryItems.Select(d => d.Id.ToString())); } - - //if (saveNeeded) { insPack.Save(); saveNeeded = false; } #endregion #region Macros @@ -368,56 +344,8 @@ namespace umbraco.cms.businesslogic.packager foreach (var template in templates) { insPack.Data.Templates.Add(template.Id.ToString(CultureInfo.InvariantCulture)); - //saveNeeded = true; } } - - //if (saveNeeded) { insPack.Save(); saveNeeded = false; } - - /*foreach (XmlNode n in _packageConfig.DocumentElement.SelectNodes("Templates/Template")) - { - var t = Template.Import(n, currentUser); - - insPack.Data.Templates.Add(t.Id.ToString()); - - saveNeeded = true; - } - - if (saveNeeded) { insPack.Save(); saveNeeded = false; } - - - //NOTE: SD: I'm pretty sure the only thing the below script does is ensure that the Master template Id is set - // in the database, but this is also duplicating the saving of the design content since the above Template.Import - // already does this. I've left this for now because I'm not sure the reprocussions of removing it but seems there - // is a lot of excess database calls happening here. - foreach (XmlNode n in _packageConfig.DocumentElement.SelectNodes("Templates/Template")) - { - string master = XmlHelper.GetNodeValue(n.SelectSingleNode("Master")); - Template t = Template.GetByAlias(XmlHelper.GetNodeValue(n.SelectSingleNode("Alias"))); - if (master.Trim() != "") - { - var masterTemplate = Template.GetByAlias(master); - if (masterTemplate != null) - { - t.MasterTemplate = Template.GetByAlias(master).Id; - //SD: This appears to always just save an empty template because the design isn't set yet - // this fixes an issue now that we have MVC because if there is an empty template and MVC is - // the default, it will create a View not a master page and then the system will try to route via - // MVC which means that the package will not work anymore. - // The code below that imports the templates should suffice because it's actually importing - // template data not just blank data. - - //if (UmbracoConfiguration.Current.UmbracoSettings.Templates.UseAspNetMasterPages) - // t.SaveMasterPageFile(t.Design); - } - } - // Master templates can only be generated when their master is known - if (UmbracoConfiguration.Current.UmbracoSettings.Templates.UseAspNetMasterPages) - { - t.ImportDesign(XmlHelper.GetNodeValue(n.SelectSingleNode("Design"))); - t.SaveMasterPageFile(t.Design); - } - }*/ #endregion #region DocumentTypes @@ -436,44 +364,6 @@ namespace umbraco.cms.businesslogic.packager //saveNeeded = true; } } - - //if (saveNeeded) { insPack.Save(); saveNeeded = false; } - - /*foreach (XmlNode n in _packageConfig.DocumentElement.SelectNodes("DocumentTypes/DocumentType")) - { - ImportDocumentType(n, currentUser, false); - saveNeeded = true; - } - - if (saveNeeded) { insPack.Save(); saveNeeded = false; } - - - // Add documenttype structure - foreach (XmlNode n in _packageConfig.DocumentElement.SelectNodes("DocumentTypes/DocumentType")) - { - DocumentType dt = DocumentType.GetByAlias(XmlHelper.GetNodeValue(n.SelectSingleNode("Info/Alias"))); - if (dt != null) - { - ArrayList allowed = new ArrayList(); - foreach (XmlNode structure in n.SelectNodes("Structure/DocumentType")) - { - DocumentType dtt = DocumentType.GetByAlias(XmlHelper.GetNodeValue(structure)); - if (dtt != null) - allowed.Add(dtt.Id); - } - - int[] adt = new int[allowed.Count]; - for (int i = 0; i < allowed.Count; i++) - adt[i] = (int)allowed[i]; - dt.AllowedChildContentTypeIDs = adt; - dt.Save(); - //PPH we log the document type install here. - insPack.Data.Documenttypes.Add(dt.Id.ToString()); - saveNeeded = true; - } - } - - if (saveNeeded) { insPack.Save(); saveNeeded = false; }*/ #endregion #region Stylesheets @@ -496,10 +386,6 @@ namespace umbraco.cms.businesslogic.packager var firstContentItem = content.First(); insPack.Data.ContentNodeId = firstContentItem.Id.ToString(CultureInfo.InvariantCulture); } - /*foreach (XmlElement n in _packageConfig.DocumentElement.SelectNodes("Documents/DocumentSet [@importMode = 'root']/*")) - { - insPack.Data.ContentNodeId = cms.businesslogic.web.Document.Import(-1, currentUser, n).ToString(); - }*/ #endregion #region Package Actions 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