diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 3e5becf021..b048998691 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -467,6 +467,11 @@ namespace Umbraco.Core.Models { base.ResetDirtyProperties(rememberDirty); + if (Template != null) + Template.ResetDirtyProperties(rememberDirty); + if (ContentType != null) + ContentType.ResetDirtyProperties(rememberDirty); + // take care of the published state _publishedState = _published ? PublishedState.Published : PublishedState.Unpublished; @@ -499,28 +504,23 @@ namespace Umbraco.Core.Models return clone; } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (Content) base.DeepClone(); + base.PerformDeepClone(clone); - //turn off change tracking - clone.DisableChangeTracking(); + var clonedContent = (Content)clone; //need to manually clone this since it's not settable - clone._contentType = (IContentType) ContentType.DeepClone(); + clonedContent._contentType = (IContentType) ContentType.DeepClone(); //if culture infos exist then deal with event bindings - if (clone._publishInfos != null) + if (clonedContent._publishInfos != null) { - clone._publishInfos.CollectionChanged -= PublishNamesCollectionChanged; //clear this event handler if any - clone._publishInfos = (ContentCultureInfosCollection) _publishInfos.DeepClone(); //manually deep clone - clone._publishInfos.CollectionChanged += clone.PublishNamesCollectionChanged; //re-assign correct event handler + clonedContent._publishInfos.CollectionChanged -= PublishNamesCollectionChanged; //clear this event handler if any + clonedContent._publishInfos = (ContentCultureInfosCollection) _publishInfos.DeepClone(); //manually deep clone + clonedContent._publishInfos.CollectionChanged += clonedContent.PublishNamesCollectionChanged; //re-assign correct event handler } - - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; + } } } diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index 863374726d..7e70238d2f 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -475,33 +475,28 @@ namespace Umbraco.Core.Models /// /// Overriden to deal with specific object instances /// - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (ContentBase) base.DeepClone(); + base.PerformDeepClone(clone); - //turn off change tracking - clone.DisableChangeTracking(); + var clonedContent = (ContentBase)clone; //if culture infos exist then deal with event bindings - if (clone._cultureInfos != null) + if (clonedContent._cultureInfos != null) { - clone._cultureInfos.CollectionChanged -= CultureInfosCollectionChanged; //clear this event handler if any - clone._cultureInfos = (ContentCultureInfosCollection) _cultureInfos.DeepClone(); //manually deep clone - clone._cultureInfos.CollectionChanged += clone.CultureInfosCollectionChanged; //re-assign correct event handler + clonedContent._cultureInfos.CollectionChanged -= CultureInfosCollectionChanged; //clear this event handler if any + clonedContent._cultureInfos = (ContentCultureInfosCollection) _cultureInfos.DeepClone(); //manually deep clone + clonedContent._cultureInfos.CollectionChanged += clonedContent.CultureInfosCollectionChanged; //re-assign correct event handler } //if properties exist then deal with event bindings - if (clone._properties != null) + if (clonedContent._properties != null) { - clone._properties.CollectionChanged -= PropertiesChanged; //clear this event handler if any - clone._properties = (PropertyCollection) _properties.DeepClone(); //manually deep clone - clone._properties.CollectionChanged += clone.PropertiesChanged; //re-assign correct event handler + clonedContent._properties.CollectionChanged -= PropertiesChanged; //clear this event handler if any + clonedContent._properties = (PropertyCollection) _properties.DeepClone(); //manually deep clone + clonedContent._properties.CollectionChanged += clonedContent.PropertiesChanged; //re-assign correct event handler } - - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; + } } } diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 9f848c6d14..caa63d7526 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -467,35 +467,29 @@ namespace Umbraco.Core.Models } } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (ContentTypeBase) base.DeepClone(); + base.PerformDeepClone(clone); - //turn off change tracking - clone.DisableChangeTracking(); + var clonedEntity = (ContentTypeBase) clone; - if (clone._noGroupPropertyTypes != null) + if (clonedEntity._noGroupPropertyTypes != null) { //need to manually wire up the event handlers for the property type collections - we've ensured // its ignored from the auto-clone process because its return values are unions, not raw and // we end up with duplicates, see: http://issues.umbraco.org/issue/U4-4842 - clone._noGroupPropertyTypes.CollectionChanged -= PropertyTypesChanged; //clear this event handler if any - clone._noGroupPropertyTypes = (PropertyTypeCollection) _noGroupPropertyTypes.DeepClone(); //manually deep clone - clone._noGroupPropertyTypes.CollectionChanged += clone.PropertyTypesChanged; //re-assign correct event handler + clonedEntity._noGroupPropertyTypes.CollectionChanged -= PropertyTypesChanged; //clear this event handler if any + clonedEntity._noGroupPropertyTypes = (PropertyTypeCollection) _noGroupPropertyTypes.DeepClone(); //manually deep clone + clonedEntity._noGroupPropertyTypes.CollectionChanged += clonedEntity.PropertyTypesChanged; //re-assign correct event handler } - if (clone._propertyGroups != null) + if (clonedEntity._propertyGroups != null) { - clone._propertyGroups.CollectionChanged -= PropertyGroupsChanged; //clear this event handler if any - clone._propertyGroups = (PropertyGroupCollection) _propertyGroups.DeepClone(); //manually deep clone - clone._propertyGroups.CollectionChanged += clone.PropertyGroupsChanged; //re-assign correct event handler + clonedEntity._propertyGroups.CollectionChanged -= PropertyGroupsChanged; //clear this event handler if any + clonedEntity._propertyGroups = (PropertyGroupCollection) _propertyGroups.DeepClone(); //manually deep clone + clonedEntity._propertyGroups.CollectionChanged += clonedEntity.PropertyGroupsChanged; //re-assign correct event handler } - - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; } public IContentType DeepCloneWithResetIdentities(string alias) diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs index 2f455083c0..08b9f74802 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -311,20 +311,15 @@ namespace Umbraco.Core.Models .Union(ContentTypeComposition.SelectMany(x => x.CompositionIds())); } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (ContentTypeCompositionBase)base.DeepClone(); - //turn off change tracking - clone.DisableChangeTracking(); - //need to manually assign since this is an internal field and will not be automatically mapped - clone.RemovedContentTypeKeyTracker = new List(); - clone._contentTypeComposition = ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList(); - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); + base.PerformDeepClone(clone); - return clone; + var clonedEntity = (ContentTypeCompositionBase)clone; + + //need to manually assign since this is an internal field and will not be automatically mapped + clonedEntity.RemovedContentTypeKeyTracker = new List(); + clonedEntity._contentTypeComposition = ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList(); } } } diff --git a/src/Umbraco.Core/Models/DictionaryTranslation.cs b/src/Umbraco.Core/Models/DictionaryTranslation.cs index 2105e8057c..c3b5a8a3b2 100644 --- a/src/Umbraco.Core/Models/DictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/DictionaryTranslation.cs @@ -104,23 +104,14 @@ namespace Umbraco.Core.Models set { SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector); } } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (DictionaryTranslation)base.DeepClone(); + base.PerformDeepClone(clone); + + var clonedEntity = (DictionaryTranslation)clone; // clear fields that were memberwise-cloned and that we don't want to clone - clone._language = null; - - // turn off change tracking - clone.DisableChangeTracking(); - - // this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - - // re-enable tracking - clone.EnableChangeTracking(); - - return clone; + clonedEntity._language = null; } } } diff --git a/src/Umbraco.Core/Models/Entities/EntityBase.cs b/src/Umbraco.Core/Models/Entities/EntityBase.cs index 0b69586abf..5c6f943c60 100644 --- a/src/Umbraco.Core/Models/Entities/EntityBase.cs +++ b/src/Umbraco.Core/Models/Entities/EntityBase.cs @@ -159,7 +159,7 @@ namespace Umbraco.Core.Models.Entities } } - public virtual object DeepClone() + public object DeepClone() { // memberwise-clone (ie shallow clone) the entity var unused = Key; // ensure that 'this' has a key, before cloning @@ -169,20 +169,29 @@ namespace Umbraco.Core.Models.Entities clone.InstanceId = Guid.NewGuid(); #endif - // clear changes (ensures the clone has its own dictionaries) - // then disable change tracking - clone.ResetDirtyProperties(false); + //disable change tracking while we deep clone IDeepCloneable properties clone.DisableChangeTracking(); // deep clone ref properties that are IDeepCloneable DeepCloneHelper.DeepCloneRefProperties(this, clone); - // clear changes again (just to be sure, because we were not tracking) - // then enable change tracking + PerformDeepClone(clone); + + // clear changes (ensures the clone has its own dictionaries) clone.ResetDirtyProperties(false); + + //re-enable change tracking clone.EnableChangeTracking(); return clone; } + + /// + /// Used by inheritors to modify the DeepCloning logic + /// + /// + protected virtual void PerformDeepClone(object clone) + { + } } } diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index 2e85b13261..2f8e021f4c 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -156,26 +156,17 @@ namespace Umbraco.Core.Models clone._alias = Alias; } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (File) base.DeepClone(); + base.PerformDeepClone(clone); + + var clonedFile = (File)clone; // clear fields that were memberwise-cloned and that we don't want to clone - clone._content = null; - - // turn off change tracking - clone.DisableChangeTracking(); + clonedFile._content = null; // ... - DeepCloneNameAndAlias(clone); - - // this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - - // re-enable tracking - clone.EnableChangeTracking(); - - return clone; + DeepCloneNameAndAlias(clonedFile); } } } diff --git a/src/Umbraco.Core/Models/Macro.cs b/src/Umbraco.Core/Models/Macro.cs index 6e68bda439..5ef49305ac 100644 --- a/src/Umbraco.Core/Models/Macro.cs +++ b/src/Umbraco.Core/Models/Macro.cs @@ -284,22 +284,18 @@ namespace Umbraco.Core.Models get { return _properties; } } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (Macro)base.DeepClone(); - //turn off change tracking - clone.DisableChangeTracking(); - clone._addedProperties = new List(); - clone._removedProperties = new List(); - clone._properties = (MacroPropertyCollection)Properties.DeepClone(); - //re-assign the event handler - clone._properties.CollectionChanged += clone.PropertiesChanged; - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); + base.PerformDeepClone(clone); - return clone; + var clonedEntity = (Macro)clone; + + clonedEntity._addedProperties = new List(); + clonedEntity._removedProperties = new List(); + clonedEntity._properties = (MacroPropertyCollection)Properties.DeepClone(); + //re-assign the event handler + clonedEntity._properties.CollectionChanged += clonedEntity.PropertiesChanged; + } } } diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 7576f01ce0..38927898cf 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -598,20 +598,15 @@ namespace Umbraco.Core.Models return true; } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (Member)base.DeepClone(); - //turn off change tracking - clone.DisableChangeTracking(); + base.PerformDeepClone(clone); + + var clonedEntity = (Member)clone; + //need to manually clone this since it's not settable - clone._contentType = (IMemberType)ContentType.DeepClone(); - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; - + clonedEntity._contentType = (IMemberType)ContentType.DeepClone(); + } /// diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 9066674193..0694194996 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -448,18 +448,19 @@ namespace Umbraco.Core.Models.Membership [DoNotClone] internal object AdditionalDataLock { get { return _additionalDataLock; } } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (User)base.DeepClone(); - //turn off change tracking - clone.DisableChangeTracking(); + base.PerformDeepClone(clone); + + var clonedEntity = (User)clone; + //manually clone the start node props - clone._startContentIds = _startContentIds.ToArray(); - clone._startMediaIds = _startMediaIds.ToArray(); + clonedEntity._startContentIds = _startContentIds.ToArray(); + clonedEntity._startMediaIds = _startMediaIds.ToArray(); // this value has been cloned and points to the same object // which obviously is bad - needs to point to a new object - clone._additionalDataLock = new object(); + clonedEntity._additionalDataLock = new object(); if (_additionalData != null) { @@ -467,7 +468,7 @@ namespace Umbraco.Core.Models.Membership // changing one clone impacts all of them - so we need to reset it with a fresh // dictionary that will contain the same values - and, if some values are deep // cloneable, they should be deep-cloned too - var cloneAdditionalData = clone._additionalData = new Dictionary(); + var cloneAdditionalData = clonedEntity._additionalData = new Dictionary(); lock (_additionalDataLock) { @@ -480,15 +481,9 @@ namespace Umbraco.Core.Models.Membership } //need to create new collections otherwise they'll get copied by ref - clone._userGroups = new HashSet(_userGroups); - clone._allowedSections = _allowedSections != null ? new List(_allowedSections) : null; - //re-create the event handler - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; + clonedEntity._userGroups = new HashSet(_userGroups); + clonedEntity._allowedSections = _allowedSections != null ? new List(_allowedSections) : null; + } /// diff --git a/src/Umbraco.Core/Models/NotificationEmailBodyParams.cs b/src/Umbraco.Core/Models/NotificationEmailBodyParams.cs new file mode 100644 index 0000000000..e85284fe5a --- /dev/null +++ b/src/Umbraco.Core/Models/NotificationEmailBodyParams.cs @@ -0,0 +1,32 @@ +using System; + +namespace Umbraco.Core.Models +{ + public class NotificationEmailBodyParams + { + public NotificationEmailBodyParams(string recipientName, string action, string itemName, string itemId, string itemUrl, string editedUser, string siteUrl, string summary) + { + RecipientName = recipientName ?? throw new ArgumentNullException(nameof(recipientName)); + Action = action ?? throw new ArgumentNullException(nameof(action)); + ItemName = itemName ?? throw new ArgumentNullException(nameof(itemName)); + ItemId = itemId ?? throw new ArgumentNullException(nameof(itemId)); + ItemUrl = itemUrl ?? throw new ArgumentNullException(nameof(itemUrl)); + Summary = summary ?? throw new ArgumentNullException(nameof(summary)); + EditedUser = editedUser ?? throw new ArgumentNullException(nameof(editedUser)); + SiteUrl = siteUrl ?? throw new ArgumentNullException(nameof(siteUrl)); + } + + public string RecipientName { get; } + public string Action { get; } + public string ItemName { get; } + public string ItemId { get; } + public string ItemUrl { get; } + + /// + /// This will either be an HTML or text based summary depending on the email type being sent + /// + public string Summary { get; } + public string EditedUser { get; } + public string SiteUrl { get; } + } +} diff --git a/src/Umbraco.Core/Models/NotificationEmailSubjectParams.cs b/src/Umbraco.Core/Models/NotificationEmailSubjectParams.cs new file mode 100644 index 0000000000..07b26dbcc1 --- /dev/null +++ b/src/Umbraco.Core/Models/NotificationEmailSubjectParams.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core.Models +{ + + public class NotificationEmailSubjectParams + { + public NotificationEmailSubjectParams(string siteUrl, string action, string itemName) + { + SiteUrl = siteUrl ?? throw new ArgumentNullException(nameof(siteUrl)); + Action = action ?? throw new ArgumentNullException(nameof(action)); + ItemName = itemName ?? throw new ArgumentNullException(nameof(itemName)); + } + + public string SiteUrl { get; } + public string Action { get; } + public string ItemName { get; } + } +} diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index 8a97dc2cfc..0c71544111 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -392,21 +392,14 @@ namespace Umbraco.Core.Models return PropertyType.IsPropertyValueValid(value); } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (Property) base.DeepClone(); + base.PerformDeepClone(clone); - //turn off change tracking - clone.DisableChangeTracking(); + var clonedEntity = (Property)clone; //need to manually assign since this is a readonly property - clone.PropertyType = (PropertyType) PropertyType.DeepClone(); - - //re-enable tracking - clone.ResetDirtyProperties(false); // not needed really, since we're not tracking - clone.EnableChangeTracking(); - - return clone; + clonedEntity.PropertyType = (PropertyType) PropertyType.DeepClone(); } } } diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs index 6c1f2e5c61..1d0b949932 100644 --- a/src/Umbraco.Core/Models/PropertyGroup.cs +++ b/src/Umbraco.Core/Models/PropertyGroup.cs @@ -100,24 +100,18 @@ namespace Umbraco.Core.Models return baseHash ^ nameHash; } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (PropertyGroup)base.DeepClone(); + base.PerformDeepClone(clone); - //turn off change tracking - clone.DisableChangeTracking(); + var clonedEntity = (PropertyGroup)clone; - if (clone._propertyTypes != null) + if (clonedEntity._propertyTypes != null) { - clone._propertyTypes.CollectionChanged -= PropertyTypesChanged; //clear this event handler if any - clone._propertyTypes = (PropertyTypeCollection) _propertyTypes.DeepClone(); //manually deep clone - clone._propertyTypes.CollectionChanged += clone.PropertyTypesChanged; //re-assign correct event handler + clonedEntity._propertyTypes.CollectionChanged -= PropertyTypesChanged; //clear this event handler if any + clonedEntity._propertyTypes = (PropertyTypeCollection) _propertyTypes.DeepClone(); //manually deep clone + clonedEntity._propertyTypes.CollectionChanged += clonedEntity.PropertyTypesChanged; //re-assign correct event handler } - - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; } } } diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index a34fdb04ed..d44e7d464f 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -424,22 +424,17 @@ namespace Umbraco.Core.Models } /// - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (PropertyType)base.DeepClone(); - //turn off change tracking - clone.DisableChangeTracking(); + base.PerformDeepClone(clone); + + var clonedEntity = (PropertyType)clone; + //need to manually assign the Lazy value as it will not be automatically mapped if (PropertyGroupId != null) { - clone._propertyGroupId = new Lazy(() => PropertyGroupId.Value); + clonedEntity._propertyGroupId = new Lazy(() => PropertyGroupId.Value); } - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; } } } diff --git a/src/Umbraco.Core/Models/PublicAccessEntry.cs b/src/Umbraco.Core/Models/PublicAccessEntry.cs index 7fd0849e27..e93dc56e35 100644 --- a/src/Umbraco.Core/Models/PublicAccessEntry.cs +++ b/src/Umbraco.Core/Models/PublicAccessEntry.cs @@ -153,23 +153,17 @@ namespace Umbraco.Core.Models } } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (PublicAccessEntry) base.DeepClone(); + base.PerformDeepClone(clone); - //turn off change tracking - clone.DisableChangeTracking(); + var cloneEntity = (PublicAccessEntry)clone; - if (clone._ruleCollection != null) + if (cloneEntity._ruleCollection != null) { - clone._ruleCollection.CollectionChanged -= _ruleCollection_CollectionChanged; //clear this event handler if any - clone._ruleCollection.CollectionChanged += clone._ruleCollection_CollectionChanged; //re-assign correct event handler + cloneEntity._ruleCollection.CollectionChanged -= _ruleCollection_CollectionChanged; //clear this event handler if any + cloneEntity._ruleCollection.CollectionChanged += cloneEntity._ruleCollection_CollectionChanged; //re-assign correct event handler } - - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; } } } diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 6cfc923ebe..275957e453 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -312,12 +312,12 @@ namespace Umbraco.Core.Services /// /// Sorts documents. /// - bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true); + OperationResult Sort(IEnumerable items, int userId = 0, bool raiseEvents = true); /// /// Sorts documents. /// - bool Sort(IEnumerable ids, int userId = 0, bool raiseEvents = true); + OperationResult Sort(IEnumerable ids, int userId = 0, bool raiseEvents = true); #endregion diff --git a/src/Umbraco.Core/Services/INotificationService.cs b/src/Umbraco.Core/Services/INotificationService.cs index 3af603a31c..a990b1e0ff 100644 --- a/src/Umbraco.Core/Services/INotificationService.cs +++ b/src/Umbraco.Core/Services/INotificationService.cs @@ -3,7 +3,6 @@ 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.Entities; using Umbraco.Core.Models.Membership; @@ -13,20 +12,6 @@ namespace Umbraco.Core.Services { public interface INotificationService : IService { - /// - /// Sends the notifications for the specified user regarding the specified node and action. - /// - /// - /// - /// - /// - /// - /// - /// - void SendNotifications(IUser operatingUser, IUmbracoEntity entity, string action, string actionName, HttpContextBase http, - Func createSubject, - Func createBody); - /// /// Sends the notifications for the specified user regarding the specified nodes and action. /// @@ -37,9 +22,9 @@ namespace Umbraco.Core.Services /// /// /// - void SendNotifications(IUser operatingUser, IEnumerable entities, string action, string actionName, HttpContextBase http, - Func createSubject, - Func createBody); + void SendNotifications(IUser operatingUser, IEnumerable entities, string action, string actionName, Uri siteUri, + Func<(IUser user, NotificationEmailSubjectParams subject), string> createSubject, + Func<(IUser user, NotificationEmailBodyParams body, bool isHtml), string> createBody); /// /// Gets the notifications for the user diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 386c76db88..2b585cebad 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -1906,17 +1906,19 @@ namespace Umbraco.Core.Services.Implement /// /// /// - /// True if sorting succeeded, otherwise False - public bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true) + /// Result indicating what action was taken when handling the command. + public OperationResult Sort(IEnumerable items, int userId = 0, bool raiseEvents = true) { + var evtMsgs = EventMessagesFactory.Get(); + var itemsA = items.ToArray(); - if (itemsA.Length == 0) return true; + if (itemsA.Length == 0) return new OperationResult(OperationResultType.NoOperation, evtMsgs); using (var scope = ScopeProvider.CreateScope()) { scope.WriteLock(Constants.Locks.ContentTree); - var ret = Sort(scope, itemsA, userId, raiseEvents); + var ret = Sort(scope, itemsA, userId, evtMsgs, raiseEvents); scope.Complete(); return ret; } @@ -1933,28 +1935,38 @@ namespace Umbraco.Core.Services.Implement /// /// /// - /// True if sorting succeeded, otherwise False - public bool Sort(IEnumerable ids, int userId = 0, bool raiseEvents = true) + /// Result indicating what action was taken when handling the command. + public OperationResult Sort(IEnumerable ids, int userId = 0, bool raiseEvents = true) { + var evtMsgs = EventMessagesFactory.Get(); + var idsA = ids.ToArray(); - if (idsA.Length == 0) return true; + if (idsA.Length == 0) return new OperationResult(OperationResultType.NoOperation, evtMsgs); using (var scope = ScopeProvider.CreateScope()) { scope.WriteLock(Constants.Locks.ContentTree); var itemsA = GetByIds(idsA).ToArray(); - var ret = Sort(scope, itemsA, userId, raiseEvents); + var ret = Sort(scope, itemsA, userId, evtMsgs, raiseEvents); scope.Complete(); return ret; } } - private bool Sort(IScope scope, IContent[] itemsA, int userId, bool raiseEvents) + private OperationResult Sort(IScope scope, IContent[] itemsA, int userId, EventMessages evtMsgs, bool raiseEvents) { var saveEventArgs = new SaveEventArgs(itemsA); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) - return false; + if (raiseEvents) + { + //raise cancelable sorting event + if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Sorting))) + return OperationResult.Cancel(evtMsgs); + + //raise saving event (this one cannot be canceled) + saveEventArgs.CanCancel = false; + scope.Events.Dispatch(Saving, this, saveEventArgs, nameof(Saving)); + } var published = new List(); var saved = new List(); @@ -1986,8 +1998,9 @@ namespace Umbraco.Core.Services.Implement if (raiseEvents) { - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); + //first saved, then sorted + scope.Events.Dispatch(Saved, this, saveEventArgs, nameof(Saved)); + scope.Events.Dispatch(Sorted, this, saveEventArgs, nameof(Sorted)); } scope.Events.Dispatch(TreeChanged, this, saved.Select(x => new TreeChange(x, TreeChangeTypes.RefreshNode)).ToEventArgs()); @@ -1996,7 +2009,7 @@ namespace Umbraco.Core.Services.Implement scope.Events.Dispatch(Published, this, new PublishEventArgs(published, false, false), "Published"); Audit(AuditType.Sort, userId, 0, "Sorting content performed by user"); - return true; + return OperationResult.Succeed(evtMsgs); } #endregion @@ -2071,6 +2084,16 @@ namespace Umbraco.Core.Services.Implement /// public static event TypedEventHandler DeletedVersions; + /// + /// Occurs before Sorting + /// + public static event TypedEventHandler> Sorting; + + /// + /// Occurs after Sorting + /// + public static event TypedEventHandler> Sorted; + /// /// Occurs before Save /// diff --git a/src/Umbraco.Core/Services/Implement/NotificationService.cs b/src/Umbraco.Core/Services/Implement/NotificationService.cs index cc76374715..ef2bfafcf6 100644 --- a/src/Umbraco.Core/Services/Implement/NotificationService.cs +++ b/src/Umbraco.Core/Services/Implement/NotificationService.cs @@ -6,8 +6,8 @@ using System.Linq; using System.Net.Mail; using System.Text; using System.Threading; -using System.Web; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -24,91 +24,25 @@ namespace Umbraco.Core.Services.Implement private readonly IScopeProvider _uowProvider; private readonly IUserService _userService; private readonly IContentService _contentService; + private readonly ILocalizationService _localizationService; private readonly INotificationsRepository _notificationsRepository; private readonly IGlobalSettings _globalSettings; + private readonly IContentSection _contentSection; private readonly ILogger _logger; - public NotificationService(IScopeProvider provider, IUserService userService, IContentService contentService, ILogger logger, - INotificationsRepository notificationsRepository, IGlobalSettings globalSettings) + public NotificationService(IScopeProvider provider, IUserService userService, IContentService contentService, ILocalizationService localizationService, + ILogger logger, INotificationsRepository notificationsRepository, IGlobalSettings globalSettings, IContentSection contentSection) { _notificationsRepository = notificationsRepository; _globalSettings = globalSettings; + _contentSection = contentSection; _uowProvider = provider ?? throw new ArgumentNullException(nameof(provider)); _userService = userService ?? throw new ArgumentNullException(nameof(userService)); _contentService = contentService ?? throw new ArgumentNullException(nameof(contentService)); + _localizationService = localizationService; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - /// - /// Sends the notifications for the specified user regarding the specified node and 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) - { - if (entity is IContent == false) - throw new NotSupportedException(); - - var content = (IContent) entity; - - // lazily get previous version - IContentBase prevVersion = null; - - // do not load *all* users in memory at once - // do not load notifications *per user* (N+1 select) - // cannot load users & notifications in 1 query (combination btw User2AppDto and User2NodeNotifyDto) - // => get batches of users, get all their notifications in 1 query - // re. users: - // users being (dis)approved = not an issue, filtered in memory not in SQL - // users being modified or created = not an issue, ordering by ID, as long as we don't *insert* low IDs - // users being deleted = not an issue for GetNextUsers - var id = Constants.Security.SuperUserId; - var nodeIds = content.Path.Split(',').Select(int.Parse).ToArray(); - const int pagesz = 400; // load batches of 400 users - do - { - // users are returned ordered by id, notifications are returned ordered by user id - var users = ((UserService) _userService).GetNextUsers(id, pagesz).Where(x => x.IsApproved).ToList(); - var notifications = GetUsersNotifications(users.Select(x => x.Id), action, nodeIds, Constants.ObjectTypes.Document).ToList(); - if (notifications.Count == 0) break; - - var i = 0; - foreach (var user in users) - { - // continue if there's no notification for this user - if (notifications[i].UserId != user.Id) continue; // next user - - // lazy load prev version - if (prevVersion == null) - { - prevVersion = GetPreviousVersion(entity.Id); - } - - // queue notification - var req = CreateNotificationRequest(operatingUser, user, content, prevVersion, actionName, http, createSubject, createBody); - Enqueue(req); - - // skip other notifications for this user - while (i < notifications.Count && notifications[i++].UserId == user.Id) ; - if (i >= notifications.Count) break; // break if no more notifications - } - - // load more users if any - id = users.Count == pagesz ? users.Last().Id + 1 : -1; - - } while (id > 0); - } - /// /// Gets the previous version to the latest version of the content item if there is one /// @@ -131,20 +65,14 @@ namespace Umbraco.Core.Services.Implement /// /// /// - /// + /// /// /// - /// - /// Currently this will only work for Content entities! - /// - public void SendNotifications(IUser operatingUser, IEnumerable entities, string action, string actionName, HttpContextBase http, - Func createSubject, - Func createBody) + public void SendNotifications(IUser operatingUser, IEnumerable entities, string action, string actionName, Uri siteUri, + Func<(IUser user, NotificationEmailSubjectParams subject), string> createSubject, + Func<(IUser user, NotificationEmailBodyParams body, bool isHtml), string> createBody) { - if (entities is IEnumerable == false) - throw new NotSupportedException(); - - var entitiesL = entities as List ?? entities.Cast().ToList(); + var entitiesL = entities.ToList(); //exit if there are no entities if (entitiesL.Count == 0) return; @@ -156,7 +84,7 @@ namespace Umbraco.Core.Services.Implement var prevVersionDictionary = new Dictionary(); // see notes above - var id = 0; + var id = Constants.Security.SuperUserId; const int pagesz = 400; // load batches of 400 users do { @@ -185,7 +113,7 @@ namespace Umbraco.Core.Services.Implement } // queue notification - var req = CreateNotificationRequest(operatingUser, user, content, prevVersionDictionary[content.Id], actionName, http, createSubject, createBody); + var req = CreateNotificationRequest(operatingUser, user, content, prevVersionDictionary[content.Id], actionName, siteUri, createSubject, createBody); Enqueue(req); } @@ -350,118 +278,141 @@ namespace Umbraco.Core.Services.Implement /// /// /// 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 NotificationRequest CreateNotificationRequest(IUser performingUser, IUser mailingUser, IContentBase content, IContentBase oldDoc, - string actionName, HttpContextBase http, - Func createSubject, - Func createBody) + private NotificationRequest CreateNotificationRequest(IUser performingUser, IUser mailingUser, IContent content, IContentBase oldDoc, + string actionName, + Uri siteUri, + Func<(IUser user, NotificationEmailSubjectParams subject), string> createSubject, + Func<(IUser user, NotificationEmailBodyParams body, bool isHtml), string> createBody) { if (performingUser == null) throw new ArgumentNullException("performingUser"); if (mailingUser == null) throw new ArgumentNullException("mailingUser"); if (content == null) throw new ArgumentNullException("content"); - if (http == null) throw new ArgumentNullException("http"); + if (siteUri == null) throw new ArgumentNullException("siteUri"); if (createSubject == null) throw new ArgumentNullException("createSubject"); if (createBody == null) throw new ArgumentNullException("createBody"); // build summary var summary = new StringBuilder(); - var props = content.Properties.ToArray(); - foreach (var p in props) + + if (content.ContentType.VariesByNothing()) { - //fixme doesn't take into account variants - - var newText = p.GetValue() != null ? p.GetValue().ToString() : ""; - var oldText = newText; - - // check if something was changed and display the changes otherwise display the fields - if (oldDoc.Properties.Contains(p.PropertyType.Alias)) + if (!_contentSection.DisableHtmlEmail) { - var oldProperty = oldDoc.Properties[p.PropertyType.Alias]; - oldText = oldProperty.GetValue() != null ? oldProperty.GetValue().ToString() : ""; + //create the html summary for invariant content - // replace html with char equivalent - ReplaceHtmlSymbols(ref oldText); - ReplaceHtmlSymbols(ref newText); + //list all of the property values like we used to + summary.Append(""); + foreach (var p in content.Properties) + { + //fixme doesn't take into account variants + + var newText = p.GetValue() != null ? p.GetValue().ToString() : ""; + var oldText = newText; + + // check if something was changed and display the changes otherwise display the fields + if (oldDoc.Properties.Contains(p.PropertyType.Alias)) + { + var oldProperty = oldDoc.Properties[p.PropertyType.Alias]; + oldText = oldProperty.GetValue() != null ? oldProperty.GetValue().ToString() : ""; + + // replace html with char equivalent + ReplaceHtmlSymbols(ref oldText); + ReplaceHtmlSymbols(ref newText); + } + + //show the values + summary.Append(""); + summary.Append(""); + summary.Append(""); + summary.Append(""); + } + summary.Append("
"); + summary.Append(p.PropertyType.Name); + summary.Append(""); + summary.Append(newText); + summary.Append("
"); } + + } + else if (content.ContentType.VariesByCulture()) + { + //it's variant, so detect what cultures have changed - - // make sure to only highlight changes done using TinyMCE editor... other changes will be displayed using default summary - // TODO: We should probably allow more than just tinymce?? - if ((p.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.TinyMce) - && string.CompareOrdinal(oldText, newText) != 0) + if (!_contentSection.DisableHtmlEmail) { - summary.Append(""); - summary.Append(" Note: "); - summary.Append( - " Red for deleted characters Yellow for inserted characters"); - summary.Append(""); - summary.Append(""); - summary.Append(" New "); - summary.Append(p.PropertyType.Name); - summary.Append(""); - summary.Append(""); - summary.Append(ReplaceLinks(CompareText(oldText, newText, true, false, "", string.Empty), http.Request)); - summary.Append(""); - summary.Append(""); - summary.Append(""); - summary.Append(" Old "); - summary.Append(p.PropertyType.Name); - summary.Append(""); - summary.Append(""); - summary.Append(ReplaceLinks(CompareText(newText, oldText, true, false, "", string.Empty), http.Request)); - summary.Append(""); - summary.Append(""); + //Create the html based summary (ul of culture names) + + var culturesChanged = content.CultureInfos.Where(x => x.Value.WasDirty()) + .Select(x => x.Key) + .Select(_localizationService.GetLanguageByIsoCode) + .WhereNotNull() + .Select(x => x.CultureName); + summary.Append("
    "); + foreach (var culture in culturesChanged) + { + summary.Append("
  • "); + summary.Append(culture); + summary.Append("
  • "); + } + summary.Append("
"); } else { - summary.Append(""); - summary.Append(""); - summary.Append(p.PropertyType.Name); - summary.Append(""); - summary.Append(""); - summary.Append(newText); - summary.Append(""); - summary.Append(""); + //Create the text based summary (csv of culture names) + + var culturesChanged = string.Join(", ", content.CultureInfos.Where(x => x.Value.WasDirty()) + .Select(x => x.Key) + .Select(_localizationService.GetLanguageByIsoCode) + .WhereNotNull() + .Select(x => x.CultureName)); + + summary.Append("'"); + summary.Append(culturesChanged); + summary.Append("'"); } - summary.Append( - " "); + } + else + { + //not supported yet... + throw new NotSupportedException(); } - string protocol = _globalSettings.UseHttps ? "https" : "http"; + var protocol = _globalSettings.UseHttps ? "https" : "http"; + var subjectVars = new NotificationEmailSubjectParams( + string.Concat(siteUri.Authority, IOHelper.ResolveUrl(SystemDirectories.Umbraco)), + actionName, + content.Name); - string[] subjectVars = { - string.Concat(http.Request.ServerVariables["SERVER_NAME"], ":", http.Request.Url.Port, IOHelper.ResolveUrl(SystemDirectories.Umbraco)), - actionName, - content.Name - }; - string[] bodyVars = { - mailingUser.Name, - actionName, - content.Name, - performingUser.Name, - string.Concat(http.Request.ServerVariables["SERVER_NAME"], ":", http.Request.Url.Port, IOHelper.ResolveUrl(SystemDirectories.Umbraco)), - content.Id.ToString(CultureInfo.InvariantCulture), summary.ToString(), - string.Format("{2}://{0}/{1}", - string.Concat(http.Request.ServerVariables["SERVER_NAME"], ":", http.Request.Url.Port), - //TODO: RE-enable this so we can have a nice url - /*umbraco.library.NiceUrl(documentObject.Id))*/ - string.Concat(content.Id, ".aspx"), - protocol) - - }; + var bodyVars = new NotificationEmailBodyParams( + mailingUser.Name, + actionName, + content.Name, + content.Id.ToString(CultureInfo.InvariantCulture), + string.Format("{2}://{0}/{1}", + string.Concat(siteUri.Authority), + //TODO: RE-enable this so we can have a nice url + /*umbraco.library.NiceUrl(documentObject.Id))*/ + string.Concat(content.Id, ".aspx"), + protocol), + performingUser.Name, + string.Concat(siteUri.Authority, IOHelper.ResolveUrl(SystemDirectories.Umbraco)), + summary.ToString()); // create the mail message - var mail = new MailMessage(UmbracoConfig.For.UmbracoSettings().Content.NotificationEmailAddress, mailingUser.Email); + var mail = new MailMessage(_contentSection.NotificationEmailAddress, mailingUser.Email); // populate the message - mail.Subject = createSubject(mailingUser, subjectVars); - if (UmbracoConfig.For.UmbracoSettings().Content.DisableHtmlEmail) + + + mail.Subject = createSubject((mailingUser, subjectVars)); + if (_contentSection.DisableHtmlEmail) { mail.IsBodyHtml = false; - mail.Body = createBody(mailingUser, bodyVars); + mail.Body = createBody((user: mailingUser, body: bodyVars, false)); } else { @@ -470,14 +421,14 @@ namespace Umbraco.Core.Services.Implement string.Concat(@" -", createBody(mailingUser, bodyVars)); +", createBody((user: mailingUser, body: bodyVars, true))); } // nh, issue 30724. Due to hardcoded http strings in resource files, we need to check for https replacements here // adding the server name to make sure we don't replace external links if (_globalSettings.UseHttps && string.IsNullOrEmpty(mail.Body) == false) { - string serverName = http.Request.ServerVariables["SERVER_NAME"]; + string serverName = siteUri.Host; mail.Body = mail.Body.Replace( string.Format("http://{0}", serverName), string.Format("https://{0}", serverName)); @@ -486,12 +437,10 @@ namespace Umbraco.Core.Services.Implement return new NotificationRequest(mail, actionName, mailingUser.Name, mailingUser.Email); } - private string ReplaceLinks(string text, HttpRequestBase request) + private string ReplaceLinks(string text, Uri siteUri) { var sb = new StringBuilder(_globalSettings.UseHttps ? "https://" : "http://"); - sb.Append(request.ServerVariables["SERVER_NAME"]); - sb.Append(":"); - sb.Append(request.Url.Port); + sb.Append(siteUri.Authority); sb.Append("/"); var domain = sb.ToString(); text = text.Replace("href=\"/", "href=\"" + domain); @@ -505,6 +454,7 @@ namespace Umbraco.Core.Services.Implement /// The old string. private static void ReplaceHtmlSymbols(ref string oldString) { + if (oldString.IsNullOrWhiteSpace()) return; oldString = oldString.Replace(" ", " "); oldString = oldString.Replace("’", "'"); oldString = oldString.Replace("&", "&"); @@ -512,69 +462,7 @@ namespace Umbraco.Core.Services.Implement oldString = oldString.Replace("”", "”"); oldString = oldString.Replace(""", "\""); } - - /// - /// Compares the text. - /// - /// The old text. - /// The new text. - /// if set to true [display inserted text]. - /// if set to true [display deleted text]. - /// The inserted style. - /// The deleted style. - /// - private static string CompareText(string oldText, string newText, bool displayInsertedText, - bool displayDeletedText, string insertedStyle, string deletedStyle) - { - var sb = new StringBuilder(); - var diffs = Diff.DiffText1(oldText, newText); - - int pos = 0; - for (var n = 0; n < diffs.Length; n++) - { - var it = diffs[n]; - - // write unchanged chars - while ((pos < it.StartB) && (pos < newText.Length)) - { - sb.Append(newText[pos]); - pos++; - } // while - - // write deleted chars - if (displayDeletedText && it.DeletedA > 0) - { - sb.Append(deletedStyle); - for (var m = 0; m < it.DeletedA; m++) - { - sb.Append(oldText[it.StartA + m]); - } // for - sb.Append("
"); - } - - // write inserted chars - if (displayInsertedText && pos < it.StartB + it.InsertedB) - { - sb.Append(insertedStyle); - while (pos < it.StartB + it.InsertedB) - { - sb.Append(newText[pos]); - pos++; - } // while - sb.Append("
"); - } // if - } // while - - // write rest of unchanged chars - while (pos < newText.Length) - { - sb.Append(newText[pos]); - pos++; - } // while - - return sb.ToString(); - } - + // manage notifications // ideally, would need to use IBackgroundTasks - but they are not part of Core! diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index acda9ef589..ac115e843b 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -395,6 +395,8 @@ + + diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index 807231730b..31110d7196 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -271,8 +271,13 @@ namespace Umbraco.Tests.Models // Arrange var contentType = MockedContentTypes.CreateTextpageContentType(); contentType.Id = 99; + contentType.Variations = ContentVariation.Culture; var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); + content.SetCultureName("Hello", "en-US"); + content.SetCultureName("World", "es-ES"); + content.PublishCulture("en-US"); + // should not try to clone something that's not Published or Unpublished // (and in fact it will not work) // but we cannot directly set the state to Published - hence this trick @@ -301,6 +306,8 @@ namespace Umbraco.Tests.Models content.UpdateDate = DateTime.Now; content.WriterId = 23; + + // Act var clone = (Content)content.DeepClone(); @@ -349,6 +356,22 @@ namespace Umbraco.Tests.Models Assert.AreEqual(clone.Properties[index], content.Properties[index]); } + Assert.AreNotSame(clone.PublishCultureInfos, content.PublishCultureInfos); + Assert.AreEqual(clone.PublishCultureInfos.Count, content.PublishCultureInfos.Count); + foreach (var key in content.PublishCultureInfos.Keys) + { + Assert.AreNotSame(clone.PublishCultureInfos[key], content.PublishCultureInfos[key]); + Assert.AreEqual(clone.PublishCultureInfos[key], content.PublishCultureInfos[key]); + } + + Assert.AreNotSame(clone.CultureInfos, content.CultureInfos); + Assert.AreEqual(clone.CultureInfos.Count, content.CultureInfos.Count); + foreach (var key in content.CultureInfos.Keys) + { + Assert.AreNotSame(clone.CultureInfos[key], content.CultureInfos[key]); + Assert.AreEqual(clone.CultureInfos[key], content.CultureInfos[key]); + } + //This double verifies by reflection var allProps = clone.GetType().GetProperties(); foreach (var propertyInfo in allProps) @@ -369,6 +392,87 @@ namespace Umbraco.Tests.Models Assert.IsTrue(asDirty.IsPropertyDirty("Properties")); } + [Test] + public void Remember_Dirty_Properties() + { + // Arrange + var contentType = MockedContentTypes.CreateTextpageContentType(); + contentType.Id = 99; + contentType.Variations = ContentVariation.Culture; + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); + + content.SetCultureName("Hello", "en-US"); + content.SetCultureName("World", "es-ES"); + content.PublishCulture("en-US"); + + var i = 200; + foreach (var property in content.Properties) + { + property.Id = ++i; + } + content.Id = 10; + content.CreateDate = DateTime.Now; + content.CreatorId = 22; + content.ExpireDate = DateTime.Now; + content.Key = Guid.NewGuid(); + content.Level = 3; + content.Path = "-1,4,10"; + content.ReleaseDate = DateTime.Now; + content.SortOrder = 5; + content.Template = new Template((string)"Test Template", (string)"testTemplate") + { + Id = 88 + }; + + content.Trashed = true; + content.UpdateDate = DateTime.Now; + content.WriterId = 23; + + content.Template.UpdateDate = DateTime.Now; //update a child object + content.ContentType.UpdateDate = DateTime.Now; //update a child object + + // Act + content.ResetDirtyProperties(); + + // Assert + Assert.IsTrue(content.WasDirty()); + Assert.IsTrue(content.WasPropertyDirty("Id")); + Assert.IsTrue(content.WasPropertyDirty("CreateDate")); + Assert.IsTrue(content.WasPropertyDirty("CreatorId")); + Assert.IsTrue(content.WasPropertyDirty("ExpireDate")); + Assert.IsTrue(content.WasPropertyDirty("Key")); + Assert.IsTrue(content.WasPropertyDirty("Level")); + Assert.IsTrue(content.WasPropertyDirty("Path")); + Assert.IsTrue(content.WasPropertyDirty("ReleaseDate")); + Assert.IsTrue(content.WasPropertyDirty("SortOrder")); + Assert.IsTrue(content.WasPropertyDirty("Template")); + Assert.IsTrue(content.WasPropertyDirty("Trashed")); + Assert.IsTrue(content.WasPropertyDirty("UpdateDate")); + Assert.IsTrue(content.WasPropertyDirty("WriterId")); + foreach (var prop in content.Properties) + { + Assert.IsTrue(prop.WasDirty()); + Assert.IsTrue(prop.WasPropertyDirty("Id")); + } + Assert.IsTrue(content.WasPropertyDirty("CultureInfos")); + foreach(var culture in content.CultureInfos) + { + Assert.IsTrue(culture.Value.WasDirty()); + Assert.IsTrue(culture.Value.WasPropertyDirty("Name")); + Assert.IsTrue(culture.Value.WasPropertyDirty("Date")); + } + Assert.IsTrue(content.WasPropertyDirty("PublishCultureInfos")); + foreach (var culture in content.PublishCultureInfos) + { + Assert.IsTrue(culture.Value.WasDirty()); + Assert.IsTrue(culture.Value.WasPropertyDirty("Name")); + Assert.IsTrue(culture.Value.WasPropertyDirty("Date")); + } + //verify child objects were reset too + Assert.IsTrue(content.Template.WasPropertyDirty("UpdateDate")); + Assert.IsTrue(content.ContentType.WasPropertyDirty("UpdateDate")); + } + [Test] public void Can_Serialize_Without_Error() { diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 6d58a49f04..4529c4f1ef 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -10,6 +10,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -106,6 +107,7 @@ namespace Umbraco.Tests.TestHelpers CacheHelper cache, ILogger logger, IGlobalSettings globalSettings, + IUmbracoSettingsSection umbracoSettings, IEventMessagesFactory eventMessagesFactory, IEnumerable urlSegmentProviders, TypeLoader typeLoader, @@ -159,10 +161,11 @@ namespace Umbraco.Tests.TestHelpers var runtimeState = Mock.Of(); var idkMap = new IdkMap(scopeProvider); + var localizationService = GetLazyService(container, c => new LocalizationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c))); var userService = GetLazyService(container, c => new UserService(scopeProvider, logger, eventMessagesFactory, runtimeState, GetRepo(c), GetRepo(c),globalSettings)); var dataTypeService = GetLazyService(container, c => new DataTypeService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); var contentService = GetLazyService(container, c => new ContentService(scopeProvider, logger, eventMessagesFactory, mediaFileSystem, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var notificationService = GetLazyService(container, c => new NotificationService(scopeProvider, userService.Value, contentService.Value, logger, GetRepo(c),globalSettings)); + var notificationService = GetLazyService(container, c => new NotificationService(scopeProvider, userService.Value, contentService.Value, localizationService.Value, logger, GetRepo(c), globalSettings, umbracoSettings.Content)); var serverRegistrationService = GetLazyService(container, c => new ServerRegistrationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var memberGroupService = GetLazyService(container, c => new MemberGroupService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var memberService = GetLazyService(container, c => new MemberService(scopeProvider, logger, eventMessagesFactory, memberGroupService.Value, mediaFileSystem, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); @@ -170,7 +173,6 @@ namespace Umbraco.Tests.TestHelpers var contentTypeService = GetLazyService(container, c => new ContentTypeService(scopeProvider, logger, eventMessagesFactory, contentService.Value, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); var mediaTypeService = GetLazyService(container, c => new MediaTypeService(scopeProvider, logger, eventMessagesFactory, mediaService.Value, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); var fileService = GetLazyService(container, c => new FileService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var localizationService = GetLazyService(container, c => new LocalizationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c))); var memberTypeService = GetLazyService(container, c => new MemberTypeService(scopeProvider, logger, eventMessagesFactory, memberService.Value, GetRepo(c), GetRepo(c), GetRepo(c))); var entityService = GetLazyService(container, c => new EntityService( diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js index 097602fe20..d5a21e0ba6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js @@ -5,74 +5,78 @@ * @description Used to show validation warnings for a editor sub view to indicate that the section content has validation errors in its data. * In order for this directive to work, the valFormManager directive must be placed on the containing form. **/ -(function() { - 'use strict'; +(function () { + 'use strict'; - function valSubViewDirective() { + function valSubViewDirective() { - function controller($scope, $element) { - //expose api - return { - valStatusChanged: function(args) { - if (!args.form.$valid) { - var subViewContent = $element.find(".ng-invalid"); + function controller($scope, $element) { + //expose api + return { + valStatusChanged: function (args) { - if (subViewContent.length > 0) { - $scope.model.hasError = true; - $scope.model.errorClass = args.showValidation ? 'show-validation' : null; - } else { - $scope.model.hasError = false; - $scope.model.errorClass = null; + //TODO: Verify this is correct, does $scope.model ever exist? + if ($scope.model) { + if (!args.form.$valid) { + var subViewContent = $element.find(".ng-invalid"); + + if (subViewContent.length > 0) { + $scope.model.hasError = true; + $scope.model.errorClass = args.showValidation ? 'show-validation' : null; + } else { + $scope.model.hasError = false; + $scope.model.errorClass = null; + } + } + else { + $scope.model.hasError = false; + $scope.model.errorClass = null; + } + } + } } - } - else { - $scope.model.hasError = false; - $scope.model.errorClass = null; - } } - } + + function link(scope, el, attr, ctrl) { + + //if there are no containing form or valFormManager controllers, then we do nothing + if (!ctrl || !angular.isArray(ctrl) || ctrl.length !== 2 || !ctrl[0] || !ctrl[1]) { + return; + } + + var valFormManager = ctrl[1]; + scope.model.hasError = false; + + //listen for form validation changes + valFormManager.onValidationStatusChanged(function (evt, args) { + if (!args.form.$valid) { + + var subViewContent = el.find(".ng-invalid"); + + if (subViewContent.length > 0) { + scope.model.hasError = true; + } else { + scope.model.hasError = false; + } + + } + else { + scope.model.hasError = false; + } + }); + + } + + var directive = { + require: ['?^^form', '?^^valFormManager'], + restrict: "A", + link: link, + controller: controller + }; + + return directive; } - function link(scope, el, attr, ctrl) { - - //if there are no containing form or valFormManager controllers, then we do nothing - if (!ctrl || !angular.isArray(ctrl) || ctrl.length !== 2 || !ctrl[0] || !ctrl[1]) { - return; - } - - var valFormManager = ctrl[1]; - scope.model.hasError = false; - - //listen for form validation changes - valFormManager.onValidationStatusChanged(function (evt, args) { - if (!args.form.$valid) { - - var subViewContent = el.find(".ng-invalid"); - - if (subViewContent.length > 0) { - scope.model.hasError = true; - } else { - scope.model.hasError = false; - } - - } - else { - scope.model.hasError = false; - } - }); - - } - - var directive = { - require: ['?^^form', '?^^valFormManager'], - restrict: "A", - link: link, - controller: controller - }; - - return directive; - } - - angular.module('umbraco.directives').directive('valSubView', valSubViewDirective); + angular.module('umbraco.directives').directive('valSubView', valSubViewDirective); })(); diff --git a/src/Umbraco.Web.UI.Client/src/common/interceptors/security.interceptor.js b/src/Umbraco.Web.UI.Client/src/common/interceptors/security.interceptor.js index b636a0a51c..13d5fbc057 100644 --- a/src/Umbraco.Web.UI.Client/src/common/interceptors/security.interceptor.js +++ b/src/Umbraco.Web.UI.Client/src/common/interceptors/security.interceptor.js @@ -102,11 +102,8 @@ //It was decided to just put these messages into the normal status messages. - var msg = "Unauthorized access to URL:
" + rejection.config.url.split('?')[0] + ""; - if (rejection.config.data) { - msg += "
with data:
" + angular.toJson(rejection.config.data) + "
Contact your administrator for information."; - } - + var msg = "Unauthorized access to URL:
" + rejection.config.url.split('?')[0] + "
Contact your administrator for information."; + notificationsService.error("Authorization error", msg); } diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.sort.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.sort.controller.js index 4581b78c8c..53759d7d97 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.sort.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.sort.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function ContentSortController($scope, $filter, contentResource, navigationService) { + function ContentSortController($scope, $filter, $routeParams, contentResource, navigationService) { var vm = this; var parentId = $scope.currentNode.parentId ? $scope.currentNode.parentId : "-1"; @@ -30,7 +30,7 @@ function onInit() { vm.loading = true; - contentResource.getChildren(id) + contentResource.getChildren(id, { cultureName: $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture }) .then(function(data){ vm.children = data.items; vm.loading = false; @@ -79,4 +79,4 @@ } angular.module("umbraco").controller("Umbraco.Editors.Content.SortController", ContentSortController); -})(); \ No newline at end of file +})(); diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index d2867acfc9..c108cffcf4 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -342,7 +342,6 @@ - @@ -420,7 +419,6 @@ - @@ -430,10 +428,6 @@ Form - - - - Designer diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 11a32a9703..cb9c87c8d9 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -946,10 +946,13 @@ To manage your website, simply open the Umbraco back office and start adding con Go to http://%4%/#/content/content/edit/%5% to edit. + %6% + Have a nice day! Cheers from the Umbraco robot ]]> + The following languages have been modified %0% @@ -1005,9 +1008,7 @@ To manage your website, simply open the Umbraco back office and start adding con

Update summary:

- - %6% -
+ %6%

Have a nice day!

@@ -1028,6 +1029,9 @@ To manage your website, simply open the Umbraco back office and start adding con ]]> + The following languages have been modified:

+ %0% + ]]>
[%0%] Notification about %1% performed on %2% Notifications 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 6a29fc0c0c..a7a7c7d8e5 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -968,10 +968,13 @@ To manage your website, simply open the Umbraco back office and start adding con Go to http://%4%/#/content/content/edit/%5% to edit. + %6% + Have a nice day! Cheers from the Umbraco robot ]]> + The following languages have been modified %0% @@ -1027,9 +1030,7 @@ To manage your website, simply open the Umbraco back office and start adding con

Update summary:

- - %6% -
+ %6%

Have a nice day!

@@ -1050,6 +1051,9 @@ To manage your website, simply open the Umbraco back office and start adding con ]]> + The following languages have been modified:

+ %0% + ]]>
[%0%] Notification about %1% performed on %2% Notifications diff --git a/src/Umbraco.Web.UI/Umbraco/dialogs/SendPublish.aspx b/src/Umbraco.Web.UI/Umbraco/dialogs/SendPublish.aspx deleted file mode 100644 index 8b190d575c..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/dialogs/SendPublish.aspx +++ /dev/null @@ -1,13 +0,0 @@ -<%@ Page language="c#" Codebehind="SendPublish.aspx.cs" AutoEventWireup="True" Inherits="umbraco.dialogs.SendPublish" %> - - - - umbraco - <%=Services.TextService.Localize("editContentSendToPublish")%> - - - -

Republish <%=Services.TextService.Localize("editContentSendToPublishText")%>

-
- <%=Services.TextService.Localize("closewindow")%> - - diff --git a/src/Umbraco.Web.UI/Umbraco/webservices/CheckForUpgrade.asmx b/src/Umbraco.Web.UI/Umbraco/webservices/CheckForUpgrade.asmx deleted file mode 100644 index 226022e0fd..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/webservices/CheckForUpgrade.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="C#" CodeBehind="CheckForUpgrade.asmx.cs" Class="umbraco.presentation.webservices.CheckForUpgrade" %> diff --git a/src/Umbraco.Web.UI/Umbraco/webservices/Developer.asmx b/src/Umbraco.Web.UI/Umbraco/webservices/Developer.asmx deleted file mode 100644 index b98e33ef54..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/webservices/Developer.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="c#" Codebehind="Developer.asmx.cs" Class="umbraco.webservices.Developer" %> diff --git a/src/Umbraco.Web.UI/Umbraco/webservices/ajax.js b/src/Umbraco.Web.UI/Umbraco/webservices/ajax.js deleted file mode 100644 index 868ccf6650..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/webservices/ajax.js +++ /dev/null @@ -1,805 +0,0 @@ -// ajax.js -// Common Javascript methods and global objects -// Ajax framework for Internet Explorer (6.0, ...) and Firefox (1.0, ...) -// Copyright by Matthias Hertel, http://www.mathertel.de -// This work is licensed under a Creative Commons Attribution 2.0 Germany License. -// See http://creativecommons.org/licenses/by/2.0/de/ -// More information on: http://ajaxaspects.blogspot.com/ and http://ajaxaspekte.blogspot.com/ -// ----- -// 05.06.2005 created by Matthias Hertel. -// 19.06.2005 minor corrections to webservices. -// 25.06.2005 ajax action queue and timing. -// 02.07.2005 queue up actions fixed. -// 10.07.2005 ajax.timeout -// 10.07.2005 a option object that is passed from ajax.Start() to prepare() is also queued. -// 10.07.2005 a option object that is passed from ajax.Start() to prepare(), finish() -// and onException() is also queued. -// 12.07.2005 correct xml encoding when CallSoap() -// 20.07.2005 more datatypes and XML Documents -// 20.07.2005 more datatypes and XML Documents fixed -// 06.08.2005 caching implemented. -// 07.08.2005 bugs fixed, when queuing without a delay time. -// 04.09.2005 bugs fixed, when entering non-multiple actions. -// 07.09.2005 proxies.IsActive added -// 27.09.2005 fixed error in handling bool as a datatype -// 13.12.2005 WebServices with arrays on strings, ints, floats and booleans - still undocumented -// 27.12.2005 fixed: empty string return values enabled. -// 27.12.2005 enable the late binding of proxy methods. -// 21.01.2006 void return bug fixed. -// 18.02.2006 typo: Finsh -> Finish. -// 25.02.2006 better xmlhttp request object retrieval, see http://blogs.msdn.com/ie/archive/2006/01/23/516393.aspx -// 22.04.2006 progress indicator added. -// 28.01.2006 void return bug fixed again? -// 09.03.2006 enable late binding of prepare and finish methods by using an expression. -// 14.07.2006 Safari Browser Version 2.03/Mac OS X 10.4. compatibility: xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue -// 10.08.2006 date to xml format fixed by Kars Veling -// 16.09.2006 .postUrl - -// ----- global variable for the proxies to webservices. ----- - -/// The root object for the proxies to webservices. -var proxies = new Object(); - -proxies.current = null; // the current active webservice call. -proxies.xmlhttp = null; // The current active xmlhttp object. - - -// ----- global variable for the ajax engine. ----- - -/// The root object for the ajax engine. -var ajax = new Object(); - -ajax.current = null; /// The current active AJAX action. -ajax.option = null; /// The options for the current active AJAX action. - -ajax.queue = new Array(); /// The pending AJAX actions. -ajax.options = new Array(); /// The options for the pending AJAX actions. - -ajax.timer = null; /// The timer for delayed actions. - -ajax.progress = false; /// show a progress indicator -ajax.progressTimer = null; /// a timer-object that help displaying the progress indicator not too often. - -// ----- AJAX engine and actions implementation ----- - -///Start an AJAX action by entering it into the queue -ajax.Start = function (action, options) { - ajax.Add(action, options); - // check if the action should start - if ((ajax.current == null) && (ajax.timer == null)) - ajax._next(false); -} // ajax.Start - - -///Start an AJAX action by entering it into the queue -ajax.Add = function (action, options) { - if (action == null) { - alert("ajax.Start: Argument action must be set."); - return; - } // if - - // enable the late binding of the methods by using a string that is evaluated. - if (typeof(action.call) == "string") action.call = eval(action.call); - if (typeof(action.prepare) == "string") action.prepare = eval(action.prepare); - if (typeof(action.finish) == "string") action.finish = eval(action.finish); - - if ((action.queueClear != null) && (action.queueClear == true)) { - ajax.queue = new Array(); - ajax.options = new Array(); - - } else if ((ajax.queue.length > 0) && ((action.queueMultiple == null) || (action.queueMultiple == false))) { - // remove existing action entries from the queue and clear a running timer - if ((ajax.timer != null) && (ajax.queue[0] == action)) { - window.clearTimeout(ajax.timer); - ajax.timer = null; - } // if - - var n = 0; - while (n < ajax.queue.length) { - if (ajax.queue[n] == action) { - ajax.queue.splice(n, 1); - ajax.options.splice(n, 1); - } else { - n++; - } // if - } // while - } // if - - if ((action.queueTop == null) || (action.queueTop == false)) { - // to the end. - ajax.queue.push(action); - ajax.options.push(options); - - } else { - // to the top - ajax.queue.unshift(action); - ajax.options.unshift(options); - } // if -} // ajax.Add - - -///Check, if the next AJAX action can start. -///This is an internal method that should not be called from external. -///for private use only. -ajax._next = function (forceStart) { - var ca = null // current action - var co = null // current opptions - var data = null; - - if (ajax.current != null) - return; // a call is active: wait more time - - if (ajax.timer != null) - return; // a call is pendig: wait more time - - if (ajax.queue.length == 0) - return; // nothing to do. - - ca = ajax.queue[0]; - co = ajax.options[0]; - if ((forceStart == true) || (ca.delay == null) || (ca.delay == 0)) { - // start top action - ajax.current = ca; - ajax.queue.shift(); - ajax.option = co; - ajax.options.shift(); - - // get the data - if (ca.prepare != null) - try { - data = ca.prepare(co); - } catch (ex) { } - - if (ca.call != null) { - ajax.StartProgress(); - - // start the call - ca.call.func = ajax.Finish; - ca.call.onException = ajax.Exception; - ca.call(data); - // start timeout timer - if (ca.timeout != null) - ajax.timer = window.setTimeout(ajax.Cancel, ca.timeout * 1000); - - } else if (ca.postUrl != null) { - // post raw data to URL - - } else { - // no call - ajax.Finish(data); - } // if - - } else { - // start a timer and wait - ajax.timer = window.setTimeout(ajax.EndWait, ca.delay); - } // if -} // ajax._next - - -///The delay time of an action is over. -ajax.EndWait = function() { - ajax.timer = null; - ajax._next(true); -} // ajax.EndWait - - -///The current action timed out. -ajax.Cancel = function() { - proxies.cancel(false); // cancel the current webservice call. - ajax.timer = null; - ajax.current = null; - ajax.option = null; - ajax.EndProgress(); - window.setTimeout(ajax._next, 200); // give some to time to cancel the http connection. -} // ajax.Cancel - - -///Finish an AJAX Action the normal way -ajax.Finish = function (data) { - // clear timeout timer if set - if (ajax.timer != null) { - window.clearTimeout(ajax.timer); - ajax.timer = null; - } // if - - // use the data - try { - if ((ajax.current != null) && (ajax.current.finish != null)) - ajax.current.finish(data, ajax.option); - } catch (ex) { } - // reset the running action - ajax.current = null; - ajax.option = null; - ajax.EndProgress(); - ajax._next(false) -} // ajax.Finish - - -///Finish an AJAX Action with an exception -ajax.Exception = function (ex) { - // use the data - if (ajax.current.onException != null) - ajax.current.onException(ex, ajax.option); - - // reset the running action - ajax.current = null; - ajax.option = null; - ajax.EndProgress(); -} // ajax.Exception - - -///Clear the current and all pending AJAX actions. -ajax.CancelAll = function () { - ajax.Cancel(); - // clear all pending AJAX actions in the queue. - ajax.queue = new Array(); - ajax.options = new Array(); -} // ajax.CancelAll - - -// ----- show or hide a progress indicator ----- - -// show a progress indicator if it takes longer... -ajax.StartProgress = function() { - ajax.progress = true; - if (ajax.progressTimer != null) - window.clearTimeout(ajax.progressTimer); - ajax.progressTimer = window.setTimeout(ajax.ShowProgress, 220); -} // ajax.StartProgress - - -// hide any progress indicator soon. -ajax.EndProgress = function () { - ajax.progress = false; - if (ajax.progressTimer != null) - window.clearTimeout(ajax.progressTimer); - ajax.progressTimer = window.setTimeout(ajax.ShowProgress, 20); -} // ajax.EndProgress - - -// this function is called by a timer to show or hide a progress indicator -ajax.ShowProgress = function() { - ajax.progressTimer = null; - var a = document.getElementById("AjaxProgressIndicator"); - - if (ajax.progress && (a != null)) { - // just display the existing object - a.style.top = document.documentElement.scrollTop + 2 + "px"; - a.style.display = ""; - - } else if (ajax.progress) { - - // find a relative link to the ajaxcore folder containing ajax.js - var path = "../ajaxcore/" - for (var n in document.scripts) { - s = document.scripts[n].src; - if ((s != null) && (s.length >= 7) && (s.substr(s.length -7).toLowerCase() == "ajax.js")) - path = s.substr(0,s.length -7); - } // for - - // create new standard progress object - a = document.createElement("div"); - a.id = "AjaxProgressIndicator"; - a.style.position = "absolute"; - a.style.right = "2px"; - a.style.top = document.documentElement.scrollTop + 2 + "px"; - a.style.width = "98px"; - a.style.height = "16px" - a.style.padding = "2px"; - a.style.verticalAlign = "bottom"; - a.style.backgroundColor="#51c77d"; - - a.innerHTML = " please wait..."; - document.body.appendChild(a); - - } else if (a) { - a.style.display="none"; - } // if -} // ajax.ShowProgress - - -// ----- simple http-POST server call ----- - -ajax.postData = function (url, data, func) { - var x = proxies._getXHR(); - - // enable cookieless sessions: - var cs = document.location.href.match(/\/\(.*\)\//); - if (cs != null) { - url = url.split('/'); - url[3] += cs[0].substr(0, cs[0].length-1); - url = url.join('/'); - } // if - - x.open("POST", url, (func != null)); - - if (func != null) { - // async call with xmlhttp-object as parameter - x.onreadystatechange = func; - x.send(data); - - } else { - // sync call - x.send(soap); - return(x.responseText); - } // if -} // ajax.postData - - -///Execute a soap call. -///Build the xml for the call of a soap method of a webservice -///and post it to the server. -proxies.callSoap = function (args) { - var p = args.callee; - var x = null; - - // check for existing cache-entry - if (p._cache != null) { - if ((p.params.length == 1) && (args.length == 1) && (p._cache[args[0]] != null)) { - if (p.func != null) { - p.func(p._cache[args[0]]); - return(null); - } else { - return(p._cache[args[0]]); - } // if - } else { - p._cachekey = args[0]; - }// if - } // if - - proxies.current = p; - x = proxies._getXHR(); - proxies.xmlhttp = x; - - // envelope start - var soap = "" - + "" - + "" - + "<" + p.fname + " xmlns='" + p.service.ns + "'>"; - - // parameters - for (n = 0; (n < p.params.length) && (n < args.length); n++) { - var val = args[n]; - var typ = p.params[n].split(':'); - - if ((typ.length == 1) || (typ[1] == "string")) { - val = String(args[n]).replace(/&/g, "&").replace(//g, ">"); - - } else if (typ[1] == "int") { - val = parseInt(args[n]); - } else if (typ[1] == "float") { - val = parseFloat(args[n]); - - } else if ((typ[1] == "x") && (typeof(args[n]) == "string")) { - val = args[n]; - - } else if ((typ[1] == "x") && (typeof(XMLSerializer) != "undefined")) { - val = (new XMLSerializer()).serializeToString(args[n].firstChild); - - } else if (typ[1] == "x") { - val = args[n].xml; - - } else if ((typ[1] == "bool") && (typeof(args[n]) == "string")) { - val = args[n].toLowerCase(); - - } else if (typ[1] == "bool") { - val = String(args[n]).toLowerCase(); - - } else if (typ[1] == "date") { - // calculate the xml format for datetime objects from a javascript date object - var s, ret; - ret = String(val.getFullYear()); - ret += "-"; - s = String(val.getMonth() + 1); - ret += (s.length == 1 ? "0" + s : s); - ret += "-"; - s = String(val.getDate()); - ret += (s.length == 1 ? "0" + s : s); - ret += "T"; - s = String(val.getHours()); - ret += (s.length == 1 ? "0" + s : s); - ret += ":"; - s = String(val.getMinutes()); - ret += (s.length == 1 ? "0" + s : s); - ret += ":"; - s = String(val.getSeconds()); - ret += (s.length == 1 ? "0" + s : s); - val = ret; - - } else if (typ[1] == "s[]") { - val = "" + args[n].join("") + ""; - - } else if (typ[1] == "int[]") { - val = "" + args[n].join("") + ""; - - } else if (typ[1] == "float[]") { - val = "" + args[n].join("") + ""; - - } else if (typ[1] == "bool[]") { - val = "" + args[n].join("") + ""; - - } // if - soap += "<" + typ[0] + ">" + val + "" - } // for - - // envelope end - soap += "" - + "" - + ""; - - // enable cookieless sessions: - var u = p.service.url; - var cs = document.location.href.match(/\/\(.*\)\//); - if (cs != null) { - u = p.service.url.split('/'); - u[3] += cs[0].substr(0, cs[0].length-1); - u = u.join('/'); - } // if - - x.open("POST", u, (p.func != null)); - x.setRequestHeader("SOAPAction", p.action); - x.setRequestHeader("Content-Type", "text/xml; charset=utf-8"); - - if (p.corefunc != null) { - // async call with xmlhttp-object as parameter - x.onreadystatechange = p.corefunc; - x.send(soap); - - } else if (p.func != null) { - // async call - x.onreadystatechange = proxies._response; - x.send(soap); - - } else { - // sync call - x.send(soap); - return(proxies._response()); - } // if -} // proxies.callSoap - - -// cancel the running webservice call. -// raise: set raise to false to prevent raising an exception -proxies.cancel = function(raise) { - var cc = proxies.current; - var cx = proxies.xmlhttp; - - if (raise == null) raise == true; - - if (proxies.xmlhttp != null) { - proxies.xmlhttp.onreadystatechange = function() { }; - proxies.xmlhttp.abort(); - if (raise && (proxies.current.onException != null)) - proxies.current.onException("WebService call was canceled.") - proxies.current = null; - proxies.xmlhttp = null; - } // if -} // proxies.cancel - - -// px is a proxies.service.func object ! -proxies.EnableCache = function (px) { - // attach an empty _cache object. - px._cache = new Object(); -} // proxies.EnableCache - - -// check, if a call is currently waiting for a result -proxies.IsActive = function () { - return(proxies.xmlhttp != null); -} // proxies.IsActive - - -///Callback method for a webservice call that dispatches the response to servive.func or service.onException. -///for private use only. -proxies._response = function () { - var ret = null; - var x = proxies.xmlhttp; - var cc = proxies.current; - var rtype = null; - - if ((cc.rtype.length > 0) && (cc.rtype[0] != null)) - rtype = cc.rtype[0].split(':'); - - if ((x != null) && (x.readyState == 4)) { - if (x.status == 200) { - var xNode = null; - - if (rtype != null) - xNode = x.responseXML.getElementsByTagName(rtype[0])[0]; - - if (xNode == null) { - ret = null; - - } else if (xNode.firstChild == null) { // 27.12.2005: empty string return values - ret = ((rtype.length == 1) || (rtype[1] == "string") ? "" : null); - - } else if ((rtype.length == 1) || (rtype[1] == "string")) { - ret = xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue; - - } else if (rtype[1] == "bool") { - ret = xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue; - ret = (ret == "true"); - - } else if (rtype[1] == "int") { - ret = xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue; - ret = parseInt(ret); - - } else if (rtype[1] == "float") { - ret = xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue; - ret = parseFloat(ret); - - } else if ((rtype[1] == "x") && (typeof(XMLSerializer) != "undefined")) { - ret = (new XMLSerializer()).serializeToString(xNode.firstChild); - ret = ajax._getXMLDOM(ret); - - } else if ((rtype[1] == "ds") && (typeof(XMLSerializer) != "undefined")) { - // ret = (new XMLSerializer()).serializeToString(xNode.firstChild.nextSibling.firstChild); - ret = (new XMLSerializer()).serializeToString(xNode); - ret = ajax._getXMLDOM(ret); - - } else if (rtype[1] == "x") { - ret = xNode.firstChild.xml; - ret = ajax._getXMLDOM(ret); - - } else if (rtype[1] == "ds") { -// ret = xNode.firstChild.nextSibling.firstChild.xml; - ret = xNode.xml; - ret = ajax._getXMLDOM(ret); - - } else if (rtype[1] == "s[]") { - // Array of strings - ret = new Array(); - xNode = xNode.firstChild; - while (xNode != null) { - ret.push(xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue); - xNode = xNode.nextSibling; - } // while - - } else if (rtype[1] == "int[]") { - // Array of int - ret = new Array(); - xNode = xNode.firstChild; - while (xNode != null) { - ret.push(parseInt(xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue)); - xNode = xNode.nextSibling; - } // while - - } else if (rtype[1] == "float[]") { - // Array of float - ret = new Array(); - xNode = xNode.firstChild; - while (xNode != null) { - ret.push(parseFloat(xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue)); - xNode = xNode.nextSibling; - } // while - - } else if (rtype[1] == "bool[]") { - // Array of bool - ret = new Array(); - xNode = xNode.firstChild; - while (xNode != null) { - ret.push((xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue).toLowerCase() == "true"); - xNode = xNode.nextSibling; - } // while - - } else { - ret = xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue; - } // if - - // store to _cache - if ((cc._cache != null) && (cc._cachekey != null)) { - cc._cache[cc._cachekey] = ret; - cc._cachekey = null; - } // if - - proxies.xmlhttp = null; - proxies.current = null; - - if (cc.func == null) { - return(ret); // sync - } else { - cc.func(ret); // async - return(null); - } // if - - } else if (proxies.current.onException == null) { - // no exception - - } else { - // raise an exception - ret = new Error(); - - if (x.status == 404) { - ret.message = "The webservice could not be found."; - - } else if (x.status == 500) { - ret.name = "SoapException"; - var n = x.responseXML.documentElement.firstChild.firstChild.firstChild; - while (n != null) { - if (n.nodeName == "faultcode") ret.message = n.firstChild.nodeValue; - if (n.nodeName == "faultstring") ret.description = n.firstChild.nodeValue; - n = n.nextSibling; - } // while - - } else if ((x.status == 502) || (x.status == 12031)) { - ret.message = "The server could not be found."; - - } else { - // no classified response. - ret.message = "Result-Status:" + x.status + "\n" + x.responseText; - } // if - proxies.current.onException(ret); - } // if - - proxies.xmlhttp = null; - proxies.current = null; - } // if -} // proxies._response - - -///Callback method to show the result of a soap call in an alert box. -///To set up a debug output in an alert box use: -///proxies.service.method.corefunc = proxies.alertResult; -proxies.alertResult = function () { - var x = proxies.xmlhttp; - - if (x.readyState == 4) { - if (x.status == 200) { - if (x.responseXML.documentElement.firstChild.firstChild.firstChild == null) - alert("(no result)"); - else - alert(x.responseXML.documentElement.firstChild.firstChild.firstChild.firstChild.nodeValue); - - } else if (x.status == 404) { alert("Error!\n\nThe webservice could not be found."); - - } else if (x.status == 500) { - // a SoapException - var ex = new Error(); - ex.name = "SoapException"; - var n = x.responseXML.documentElement.firstChild.firstChild.firstChild; - while (n != null) { - if (n.nodeName == "faultcode") ex.message = n.firstChild.nodeValue; - if (n.nodeName == "faultstring") ex.description = n.firstChild.nodeValue; - n = n.nextSibling; - } // while - alert("The server threw an exception.\n\n" + ex.message + "\n\n" + ex.description); - - } else if (x.status == 502) { alert("Error!\n\nThe server could not be found."); - - } else { - // no classified response. - alert("Result-Status:" + x.status + "\n" + x.responseText); - } // if - - proxies.xmlhttp = null; - proxies.current = null; - } // if -} // proxies.alertResult - - -///Show all the details of the returned data of a webservice call. -///Use this method for debugging transmission problems. -///To set up a debug output in an alert box use: -///proxies.service.method.corefunc = proxies.alertResponseText; -proxies.alertResponseText = function () { - if (proxies.xmlhttp.readyState == 4) - alert("Status:" + proxies.xmlhttp.status + "\nRESULT:" + proxies.xmlhttp.responseText); -} // proxies.alertResponseText - - -///show the details about an exception. -proxies.alertException = function(ex) { - var s = "Exception:\n\n"; - - if (ex.constructor == String) { - s = ex; - } else { - if ((ex.name != null) && (ex.name != "")) - s += "Type: " + ex.name + "\n\n"; - - if ((ex.message != null) && (ex.message != "")) - s += "Message:\n" + ex.message + "\n\n"; - - if ((ex.description != null) && (ex.description != "") && (ex.message != ex.description)) - s += "Description:\n" + ex.description + "\n\n"; - } // if - alert(s); -} // proxies.alertException - - -///Get a browser specific implementation of the XMLHttpRequest object. -// from http://blogs.msdn.com/ie/archive/2006/01/23/516393.aspx -proxies._getXHR = function () { - var x = null; - if (window.XMLHttpRequest) { - // if IE7, Mozilla, Safari, etc: Use native object - x = new XMLHttpRequest() - - } else if (window.ActiveXObject) { - // ...otherwise, use the ActiveX control for IE5.x and IE6 - try { x = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { } - if (x == null) - try { x = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { } - } // if - return(x); -} // proxies._getXHR - - -///Get a browser specific implementation of the XMLDOM object, containing a XML document. -///the xml document as string. -ajax._getXMLDOM = function (xmlText) { - var obj = null; - - if ((document.implementation != null) && (typeof document.implementation.createDocument == "function")) { - // Gecko / Mozilla / Firefox - var parser = new DOMParser(); - obj = parser.parseFromString(xmlText, "text/xml"); - - } else { - // IE - try { - obj = new ActiveXObject("MSXML2.DOMDocument"); - } catch (e) { } - - if (obj == null) { - try { - obj = new ActiveXObject("Microsoft.XMLDOM"); - } catch (e) { } - } // if - - if (obj != null) { - obj.async = false; - obj.validateOnParse = false; - } // if - obj.loadXML(xmlText); - } // if - return(obj); -} // _getXMLDOM - - -///show the details of a javascript object. -///This helps a lot while developing and debugging. -function inspectObj(obj) { - var s = "InspectObj:"; - - if (obj == null) { - s = "(null)"; alert(s); return; - } else if (obj.constructor == String) { - s = "\"" + obj + "\""; - } else if (obj.constructor == Array) { - s += " _ARRAY"; - } else if (typeof(obj) == "function") { - s += " [function]" + obj; - - } else if ((typeof(XMLSerializer) != "undefined") && (obj.constructor == XMLDocument)) { - s = "[XMLDocument]:\n" + (new XMLSerializer()).serializeToString(obj.firstChild); - alert(s); return; - - } else if ((obj.constructor == null) && (typeof(obj) == "object") && (obj.xml != null)) { - s = "[XML]:\n" + obj.xml; - alert(s); return; - } - - for (p in obj) { - try { - if (obj[p] == null) { - s += "\n" + String(p) + " (...)"; - - } else if (typeof(obj[p]) == "function") { - s += "\n" + String(p) + " [function]"; - - } else if (obj[p].constructor == Array) { - s += "\n" + String(p) + " [ARRAY]: " + obj[p]; - for (n = 0; n < obj[p].length; n++) - s += "\n " + n + ": " + obj[p][n]; - - } else { - s += "\n" + String(p) + " [" + typeof(obj[p]) + "]: " + obj[p]; - } // if - } catch (e) { s+= e;} - } // for - alert(s); -} // inspectObj - -// ----- End ----- diff --git a/src/Umbraco.Web.UI/Umbraco/webservices/codeEditorSave.asmx b/src/Umbraco.Web.UI/Umbraco/webservices/codeEditorSave.asmx deleted file mode 100644 index 947a6fb889..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/webservices/codeEditorSave.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="C#" CodeBehind="codeEditorSave.asmx.cs" Class="umbraco.presentation.webservices.codeEditorSave" %> diff --git a/src/Umbraco.Web.UI/Umbraco/webservices/legacyAjaxCalls.asmx b/src/Umbraco.Web.UI/Umbraco/webservices/legacyAjaxCalls.asmx deleted file mode 100644 index 5acc6e6022..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/webservices/legacyAjaxCalls.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="C#" CodeBehind="legacyAjaxCalls.asmx.cs" Class="umbraco.presentation.webservices.legacyAjaxCalls" %> diff --git a/src/Umbraco.Web.UI/Umbraco/webservices/nodeSorter.asmx b/src/Umbraco.Web.UI/Umbraco/webservices/nodeSorter.asmx deleted file mode 100644 index ae09ffa463..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/webservices/nodeSorter.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="C#" CodeBehind="nodeSorter.asmx.cs" Class="umbraco.presentation.webservices.nodeSorter" %> diff --git a/src/Umbraco.Web.UI/Umbraco/webservices/templates.asmx b/src/Umbraco.Web.UI/Umbraco/webservices/templates.asmx deleted file mode 100644 index 4a6e4ee189..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/webservices/templates.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="c#" Codebehind="templates.asmx.cs" Class="umbraco.webservices.templates" %> diff --git a/src/Umbraco.Web/Components/NotificationsComponent.cs b/src/Umbraco.Web/Components/NotificationsComponent.cs index 79f5ee9f6d..38ec283cfe 100644 --- a/src/Umbraco.Web/Components/NotificationsComponent.cs +++ b/src/Umbraco.Web/Components/NotificationsComponent.cs @@ -1,68 +1,195 @@ using System.Collections.Generic; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Components; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.Web.Actions; - +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; +using System.Linq; +using Umbraco.Core.Models.Membership; +using System; +using System.Globalization; namespace Umbraco.Web.Components { [RuntimeLevel(MinLevel = RuntimeLevel.Run)] public sealed class NotificationsComponent : UmbracoComponentBase, IUmbracoCoreComponent { - public void Initialize(INotificationService notificationService, ActionCollection actions) + public override void Compose(Composition composition) { - ContentService.SentToPublish += (sender, args) => - notificationService.SendNotification(args.Entity, actions.GetAction()); + base.Compose(composition); + composition.Container.RegisterSingleton(); + } + + public void Initialize(INotificationService notificationService, Notifier notifier, ActionCollection actions) + { + //Send notifications for the send to publish action + ContentService.SentToPublish += (sender, args) => notifier.Notify(actions.GetAction(), args.Entity); //Send notifications for the published action - ContentService.Published += (sender, args) => - { - foreach (var content in args.PublishedEntities) - notificationService.SendNotification(content, actions.GetAction()); - }; + ContentService.Published += (sender, args) => notifier.Notify(actions.GetAction(), args.PublishedEntities.ToArray()); + + //Send notifications for the saved action + ContentService.Sorted += (sender, args) => ContentServiceSorted(notifier, sender, args, actions); //Send notifications for the update and created actions - ContentService.Saved += (sender, args) => - { - var newEntities = new List(); - var updatedEntities = new List(); - - //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 - newEntities.Add(entity); - } - else - { - //it's updating - updatedEntities.Add(entity); - } - } - notificationService.SendNotification(newEntities, actions.GetAction()); - notificationService.SendNotification(updatedEntities, actions.GetAction()); - }; + ContentService.Saved += (sender, args) => ContentServiceSaved(notifier, sender, args, actions); //Send notifications for the delete action - ContentService.Deleted += (sender, args) => - { - foreach (var content in args.DeletedEntities) - notificationService.SendNotification(content, actions.GetAction()); - }; - + ContentService.Deleted += (sender, args) => notifier.Notify(actions.GetAction(), args.DeletedEntities.ToArray()); + //Send notifications for the unpublish action - ContentService.Unpublished += (sender, args) => + ContentService.Unpublished += (sender, args) => notifier.Notify(actions.GetAction(), args.PublishedEntities.ToArray()); + } + + private void ContentServiceSorted(Notifier notifier, IContentService sender, Core.Events.SaveEventArgs args, ActionCollection actions) + { + var parentId = args.SavedEntities.Select(x => x.ParentId).Distinct().ToList(); + if (parentId.Count != 1) return; // this shouldn't happen, for sorting all entities will have the same parent id + + // in this case there's nothing to report since if the root is sorted we can't report on a fake entity. + // this is how it was in v7, we can't report on root changes because you can't subscribe to root changes. + if (parentId[0] <= 0) return; + + var parent = sender.GetById(parentId[0]); + if (parent == null) return; // this shouldn't happen + + notifier.Notify(actions.GetAction(), new[] { parent }); + } + + private void ContentServiceSaved(Notifier notifier, IContentService sender, Core.Events.SaveEventArgs args, ActionCollection actions) + { + var newEntities = new List(); + var updatedEntities = new List(); + + //need to determine if this is updating or if it is new + foreach (var entity in args.SavedEntities) { - foreach (var content in args.PublishedEntities) - notificationService.SendNotification(content, actions.GetAction()); - }; + var dirty = (IRememberBeingDirty)entity; + if (dirty.WasPropertyDirty("Id")) + { + //it's new + newEntities.Add(entity); + } + else + { + //it's updating + updatedEntities.Add(entity); + } + } + notifier.Notify(actions.GetAction(), newEntities.ToArray()); + notifier.Notify(actions.GetAction(), updatedEntities.ToArray()); + } + + /// + /// This class is used to send the notifications + /// + public sealed class Notifier + { + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IRuntimeState _runtimeState; + private readonly INotificationService _notificationService; + private readonly IUserService _userService; + private readonly ILocalizedTextService _textService; + private readonly IGlobalSettings _globalSettings; + private readonly IContentSection _contentConfig; + private readonly ILogger _logger; + + /// + /// Constructor + /// + /// + /// + /// + /// + /// + /// + /// + public Notifier(IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState, INotificationService notificationService, IUserService userService, ILocalizedTextService textService, IGlobalSettings globalSettings, IContentSection contentConfig, ILogger logger) + { + _umbracoContextAccessor = umbracoContextAccessor; + _runtimeState = runtimeState; + _notificationService = notificationService; + _userService = userService; + _textService = textService; + _globalSettings = globalSettings; + _contentConfig = contentConfig; + _logger = logger; + } + + public void Notify(IAction action, params IContent[] entities) + { + IUser user = null; + if (_umbracoContextAccessor.UmbracoContext != null) + { + user = _umbracoContextAccessor.UmbracoContext.Security.CurrentUser; + } + + //if there is no current user, then use the admin + if (user == null) + { + _logger.Debug(typeof(Notifier), "There is no current Umbraco user logged in, the notifications will be sent from the administrator"); + user = _userService.GetUserById(Constants.Security.SuperUserId); + if (user == null) + { + _logger.Warn(typeof(Notifier), "Notifications can not be sent, no admin user with id {SuperUserId} could be resolved", Constants.Security.SuperUserId); + return; + } + } + + SendNotification(user, entities, action, _runtimeState.ApplicationUrl); + } + + private void SendNotification(IUser sender, IEnumerable entities, IAction action, Uri siteUri) + { + if (sender == null) throw new ArgumentNullException(nameof(sender)); + if (siteUri == null) throw new ArgumentNullException(nameof(siteUri)); + + //group by the content type variation since the emails will be different + foreach(var contentVariantGroup in entities.GroupBy(x => x.ContentType.Variations)) + { + if (contentVariantGroup.Key == ContentVariation.CultureAndSegment || contentVariantGroup.Key == ContentVariation.Segment) + throw new NotSupportedException("Segments are not yet supported in Umbraco"); + + _notificationService.SendNotifications( + sender, + contentVariantGroup, + action.Letter.ToString(CultureInfo.InvariantCulture), + _textService.Localize("actions", action.Alias), + siteUri, + ((IUser user, NotificationEmailSubjectParams subject) x) + => _textService.Localize( + "notifications/mailSubject", + x.user.GetUserCulture(_textService, _globalSettings), + new[] { x.subject.SiteUrl, x.subject.Action, x.subject.ItemName }), + ((IUser user, NotificationEmailBodyParams body, bool isHtml) x) + => _textService.Localize( + x.isHtml ? "notifications/mailBodyHtml" : "notifications/mailBody", + x.user.GetUserCulture(_textService, _globalSettings), + new[] + { + x.body.RecipientName, + x.body.Action, + x.body.ItemName, + x.body.EditedUser, + x.body.SiteUrl, + x.body.ItemId, + //format the summary depending on if it's variant or not + contentVariantGroup.Key == ContentVariation.Culture + ? (x.isHtml ? _textService.Localize("notifications/mailBodyVariantHtmlSummary", new[]{ x.body.Summary }) : _textService.Localize("notifications/mailBodyVariantSummary", new []{ x.body.Summary })) + : x.body.Summary, + x.body.ItemUrl + })); + } + } + } } + + } diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 308cab644c..4ed9251929 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -41,6 +41,7 @@ namespace Umbraco.Web.Editors { private readonly ManifestParser _manifestParser; private readonly UmbracoFeatures _features; + private readonly IRuntimeState _runtimeState; private BackOfficeUserManager _userManager; private BackOfficeSignInManager _signInManager; @@ -48,10 +49,11 @@ namespace Umbraco.Web.Editors private const string TokenPasswordResetCode = "PasswordResetCode"; private static readonly string[] TempDataTokenNames = { TokenExternalSignInError, TokenPasswordResetCode }; - public BackOfficeController(ManifestParser manifestParser, UmbracoFeatures features) + public BackOfficeController(ManifestParser manifestParser, UmbracoFeatures features, IRuntimeState runtimeState) { _manifestParser = manifestParser; _features = features; + _runtimeState = runtimeState; } protected BackOfficeSignInManager SignInManager => _signInManager ?? (_signInManager = OwinContext.GetBackOfficeSignInManager()); @@ -250,7 +252,7 @@ namespace Umbraco.Web.Editors [MinifyJavaScriptResult(Order = 1)] public JavaScriptResult ServerVariables() { - var serverVars = new BackOfficeServerVariables(Url, Current.RuntimeState, _features, GlobalSettings); + var serverVars = new BackOfficeServerVariables(Url, _runtimeState, _features, GlobalSettings); //cache the result if debugging is disabled var result = HttpContext.IsDebuggingEnabled diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index fe5675b2aa..5fddac0c14 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1070,17 +1070,14 @@ namespace Umbraco.Web.Editors var contentService = Services.ContentService; // Save content with new sort order and update content xml in db accordingly - if (contentService.Sort(sorted.IdSortOrder, Security.CurrentUser.Id) == false) + var sortResult = contentService.Sort(sorted.IdSortOrder, Security.CurrentUser.Id); + if (!sortResult.Success) { Logger.Warn("Content sorting failed, this was probably caused by an event being cancelled"); + //TODO: Now you can cancel sorting, does the event messages bubble up automatically? return Request.CreateValidationErrorResponse("Content sorting failed, this was probably caused by an event being cancelled"); } - if (sorted.ParentId > 0) - { - Services.NotificationService.SendNotification(contentService.GetById(sorted.ParentId), Current.Actions.GetAction(), UmbracoContext, Services.TextService, GlobalSettings); - } - return Request.CreateResponse(HttpStatusCode.OK); } catch (Exception ex) diff --git a/src/Umbraco.Web/NotificationServiceExtensions.cs b/src/Umbraco.Web/NotificationServiceExtensions.cs deleted file mode 100644 index 4be7b4f091..0000000000 --- a/src/Umbraco.Web/NotificationServiceExtensions.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Globalization; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; -using Umbraco.Core.Models; - -using System.Collections.Generic; -using Umbraco.Core.Models.Entities; -using Umbraco.Web.Actions; -using Umbraco.Web.Composing; - -namespace Umbraco.Web -{ - // TODO: all of these require an UmbracoContext because currently to send the notifications we need an HttpContext, this is based on legacy code - // for which probably requires updating so that these can be sent outside of the http context. - - internal static class NotificationServiceExtensions - { - internal static void SendNotification(this INotificationService service, IUmbracoEntity entity, IAction action) - { - if (Current.UmbracoContext == null) - { - Current.Logger.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); - return; - } - service.SendNotification(entity, action, Current.UmbracoContext, Current.Services.TextService, UmbracoConfig.For.GlobalSettings()); - } - - internal static void SendNotification(this INotificationService service, IEnumerable entities, IAction action) - { - if (Current.UmbracoContext == null) - { - Current.Logger.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); - return; - } - service.SendNotification(entities, action, Current.UmbracoContext, Current.Services.TextService, UmbracoConfig.For.GlobalSettings()); - } - - //internal static void SendNotification(this INotificationService service, IUmbracoEntity entity, IAction action, UmbracoContext umbracoContext) - //{ - // if (umbracoContext == null) - // { - // Current.Logger.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); - // return; - // } - // service.SendNotification(entity, action, umbracoContext); - //} - - //internal static void SendNotification(this INotificationService service, IEnumerable entities, IAction action, UmbracoContext umbracoContext) - //{ - // if (umbracoContext == null) - // { - // Current.Logger.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); - // return; - // } - // service.SendNotification(entities, action, umbracoContext); - //} - - internal static void SendNotification(this INotificationService service, IUmbracoEntity entity, IAction action, UmbracoContext umbracoContext, ILocalizedTextService textService, IGlobalSettings globalSettings) - { - if (umbracoContext == null) - { - Current.Logger.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); - return; - } - - var user = umbracoContext.Security.CurrentUser; - var userService = Current.Services.UserService; // fixme inject - - //if there is no current user, then use the admin - if (user == null) - { - Current.Logger.Debug(typeof(NotificationServiceExtensions), "There is no current Umbraco user logged in, the notifications will be sent from the administrator"); - user = userService.GetUserById(Constants.Security.SuperUserId); - if (user == null) - { - Current.Logger.Warn(typeof(NotificationServiceExtensions), "Noticiations can not be sent, no admin user with id {SuperUserId} could be resolved", Constants.Security.SuperUserId); - return; - } - } - service.SendNotification(user, entity, action, umbracoContext, textService, globalSettings); - } - - internal static void SendNotification(this INotificationService service, IEnumerable entities, IAction action, UmbracoContext umbracoContext, ILocalizedTextService textService, IGlobalSettings globalSettings) - { - if (umbracoContext == null) - { - Current.Logger.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); - return; - } - - var user = umbracoContext.Security.CurrentUser; - var userService = Current.Services.UserService; // fixme inject - - //if there is no current user, then use the admin - if (user == null) - { - Current.Logger.Debug(typeof(NotificationServiceExtensions), "There is no current Umbraco user logged in, the notifications will be sent from the administrator"); - user = userService.GetUserById(Constants.Security.SuperUserId); - if (user == null) - { - Current.Logger.Warn(typeof(NotificationServiceExtensions), "Noticiations can not be sent, no admin user with id {SuperUserId} could be resolved", Constants.Security.SuperUserId); - return; - } - } - service.SendNotification(user, entities, action, umbracoContext, textService, globalSettings); - } - - internal static void SendNotification(this INotificationService service, IUser sender, IUmbracoEntity entity, IAction action, UmbracoContext umbracoContext, ILocalizedTextService textService, IGlobalSettings globalSettings) - { - if (sender == null) throw new ArgumentNullException(nameof(sender)); - if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); - - service.SendNotifications( - sender, - entity, - action.Letter.ToString(CultureInfo.InvariantCulture), - textService.Localize("actions", action.Alias), - umbracoContext.HttpContext, - (mailingUser, strings) => textService.Localize("notifications/mailSubject", mailingUser.GetUserCulture(textService, globalSettings), strings), - (mailingUser, strings) => UmbracoConfig.For.UmbracoSettings().Content.DisableHtmlEmail - ? textService.Localize("notifications/mailBody", mailingUser.GetUserCulture(textService, globalSettings), strings) - : textService.Localize("notifications/mailBodyHtml", mailingUser.GetUserCulture(textService, globalSettings), strings)); - } - - internal static void SendNotification(this INotificationService service, IUser sender, IEnumerable entities, IAction action, UmbracoContext umbracoContext, ILocalizedTextService textService, IGlobalSettings globalSettings) - { - if (sender == null) throw new ArgumentNullException(nameof(sender)); - if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); - - service.SendNotifications( - sender, - entities, - action.Letter.ToString(CultureInfo.InvariantCulture), - textService.Localize("actions", action.Alias), - umbracoContext.HttpContext, - (mailingUser, strings) => textService.Localize("notifications/mailSubject", mailingUser.GetUserCulture(textService, globalSettings), strings), - (mailingUser, strings) => UmbracoConfig.For.UmbracoSettings().Content.DisableHtmlEmail - ? textService.Localize("notifications/mailBody", mailingUser.GetUserCulture(textService, globalSettings), strings) - : textService.Localize("notifications/mailBodyHtml", mailingUser.GetUserCulture(textService, globalSettings), strings)); - } - } -} diff --git a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs index d0bfa360fa..85cebdf3f5 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs @@ -48,6 +48,7 @@ namespace Umbraco.Web.Trees internal static Attempt GetUrlAndTitleFromLegacyAction(IAction action, string nodeId, string nodeType, string nodeName, string currentSection) { switch (action) + { case ActionNew actionNew: return Attempt.Succeed( diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index f17f6daf72..78215e0d1d 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -762,7 +762,6 @@ - @@ -1268,13 +1267,6 @@ republish.aspx - - SendPublish.aspx - ASPXCodeBehind - - - SendPublish.aspx - editPackage.aspx ASPXCodeBehind @@ -1287,19 +1279,6 @@ - - - CheckForUpgrade.asmx - Component - - - legacyAjaxCalls.asmx - Component - - - nodeSorter.asmx - Component - @@ -1311,9 +1290,6 @@ - - Component - Component @@ -1340,10 +1316,7 @@ - - - ASPXCodeBehind @@ -1361,7 +1334,6 @@ ASPXCodeBehind - ASPXCodeBehind diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx deleted file mode 100644 index 8b190d575c..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx +++ /dev/null @@ -1,13 +0,0 @@ -<%@ Page language="c#" Codebehind="SendPublish.aspx.cs" AutoEventWireup="True" Inherits="umbraco.dialogs.SendPublish" %> - - - - umbraco - <%=Services.TextService.Localize("editContentSendToPublish")%> - - - -

Republish <%=Services.TextService.Localize("editContentSendToPublishText")%>

-
- <%=Services.TextService.Localize("closewindow")%> - - diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx.cs deleted file mode 100644 index 1f4ba277b5..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using Umbraco.Web; -using Umbraco.Core; -using Umbraco.Web.Actions; -using Umbraco.Web.Composing; - - -namespace umbraco.dialogs -{ - /// - /// Runs all action handlers for the ActionToPublish action for the document with - /// the corresponding document id passed in by query string - /// - public partial class SendPublish : Umbraco.Web.UI.Pages.UmbracoEnsuredPage - { - public SendPublish() - { - CurrentApp = Constants.Applications.Content.ToString(); - } - - protected void Page_Load(object sender, EventArgs e) - { - if (!string.IsNullOrEmpty(Request.QueryString["id"])) - { - int docId; - if (int.TryParse(Request.QueryString["id"], out docId)) - { - //send notifications! TODO: This should be put somewhere centralized instead of hard coded directly here - Services.NotificationService.SendNotification( - Services.ContentService.GetById(docId), Current.Actions.GetAction()); - } - - } - - } - - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx.designer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx.designer.cs deleted file mode 100644 index 36e42291f7..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx.designer.cs +++ /dev/null @@ -1,16 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:2.0.50727.4200 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace umbraco.dialogs { - - - public partial class SendPublish { - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CheckForUpgrade.asmx b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CheckForUpgrade.asmx deleted file mode 100644 index 226022e0fd..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CheckForUpgrade.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="C#" CodeBehind="CheckForUpgrade.asmx.cs" Class="umbraco.presentation.webservices.CheckForUpgrade" %> diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CheckForUpgrade.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CheckForUpgrade.asmx.cs deleted file mode 100644 index 9007f9c41e..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CheckForUpgrade.asmx.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web; -using System.Web.Services; -using System.Web.Script.Services; -using Umbraco.Core; -using Umbraco.Web.WebServices; -using Umbraco.Core.Configuration; -using Umbraco.Web; -using Umbraco.Web.Composing; -using Umbraco.Web.Install; - - -namespace umbraco.presentation.webservices -{ - /// - /// Summary description for CheckForUpgrade - /// - [WebService(Namespace = "http://umbraco.org/")] - [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] - [System.ComponentModel.ToolboxItem(false)] - [ScriptService] - public class CheckForUpgrade : UmbracoAuthorizedWebService - { - - [WebMethod] - [ScriptMethod] - public UpgradeResult CallUpgradeService() - { - if (!AuthorizeRequest()) return null; - - var check = new global::Umbraco.Web.org.umbraco.update.CheckForUpgrade(); - var result = check.CheckUpgrade(UmbracoVersion.Current.Major, - UmbracoVersion.Current.Minor, - UmbracoVersion.Current.Build, - UmbracoVersion.CurrentComment); - return new UpgradeResult(result.UpgradeType.ToString(), result.Comment, result.UpgradeUrl); - } - - [WebMethod] - [ScriptMethod] - public void InstallStatus(bool isCompleted, string userAgent, string errorMsg) - { - bool isUpgrade = false; - // if it's an upgrade, you'll need to be logged in before we allow this call - if (string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) == false) - { - isUpgrade = true; - try - { - AuthorizeRequest(true); - } - catch (Exception) - { - //we don't want to throw the exception back to JS - return; - } - } - - // Check for current install Id - Guid installId = Guid.NewGuid(); - var installCookie = Context.Request.GetCookieValue(Constants.Web.InstallerCookieName); - if (string.IsNullOrEmpty(installCookie) == false) - { - if (Guid.TryParse(installCookie, out installId)) - { - // check that it's a valid Guid - if (installId == Guid.Empty) - installId = Guid.NewGuid(); - - } - } - Context.Response.Cookies.Set(new HttpCookie(Constants.Web.InstallerCookieName, installId.ToString())); - - string dbProvider = string.Empty; - if (string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) == false) - dbProvider = InstallHelper.GetDbProviderString(Current.SqlContext); - - var check = new global::Umbraco.Web.org.umbraco.update.CheckForUpgrade(); - check.Install(installId, - isUpgrade, - isCompleted, - DateTime.Now, - UmbracoVersion.Current.Major, - UmbracoVersion.Current.Minor, - UmbracoVersion.Current.Build, - UmbracoVersion.CurrentComment, - errorMsg, - userAgent, - dbProvider); - } - } - - - public class UpgradeResult - { - public string UpgradeType { get; set; } - public string UpgradeComment { get; set; } - public string UpgradeUrl { get; set; } - - public UpgradeResult() { } - public UpgradeResult(string upgradeType, string upgradeComment, string upgradeUrl) - { - UpgradeType = upgradeType; - UpgradeComment = upgradeComment; - UpgradeUrl = upgradeUrl + "?version=" + HttpContext.Current.Server.UrlEncode(UmbracoVersion.Current.ToString(3)); - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/UmbracoAuthorizedWebService.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/UmbracoAuthorizedWebService.cs deleted file mode 100644 index 33c4831c40..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/UmbracoAuthorizedWebService.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Security; -using Umbraco.Web.Security; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; -using Umbraco.Web; -using Umbraco.Web.WebServices; - -namespace umbraco.presentation.webservices -{ - /// - /// An abstract web service class that has the methods and properties to correct validate an Umbraco user - /// - public abstract class UmbracoAuthorizedWebService : UmbracoWebService - { - private bool _hasValidated = false; - - /// - /// Checks if the umbraco context id is valid - /// - /// - /// - protected bool ValidateUserContextId(string currentUmbracoUserContextId) - { - return UmbracoContext.Security.ValidateCurrentUser(); - } - - /// - /// Checks if the username/password credentials are valid - /// - /// - /// - /// - protected bool ValidateCredentials(string username, string password) - { - return UmbracoContext.Security.ValidateBackOfficeCredentials(username, password); - } - - /// - /// Validates the user for access to a certain application - /// - /// The application alias. - /// true if an exception should be thrown if authorization fails - /// - protected bool AuthorizeRequest(string app, bool throwExceptions = false) - { - //ensure we have a valid user first! - if (!AuthorizeRequest(throwExceptions)) return false; - - //if it is empty, don't validate - if (app.IsNullOrWhiteSpace()) - { - return true; - } - var hasAccess = UserHasAppAccess(app, Security.CurrentUser); - if (!hasAccess && throwExceptions) - throw new SecurityException("The user does not have access to the required application"); - return hasAccess; - } - - /// - /// Checks if the specified user as access to the app - /// - /// - /// - /// - protected bool UserHasAppAccess(string app, IUser user) - { - return Security.UserHasSectionAccess(app, user); - } - - /// - /// Checks if the specified user by username as access to the app - /// - /// - /// - /// - protected bool UserHasAppAccess(string app, string username) - { - return Security.UserHasSectionAccess(app, username); - } - - /// - /// Returns true if there is a valid logged in user and that ssl is enabled if required - /// - /// true if an exception should be thrown if authorization fails - /// - protected bool AuthorizeRequest(bool throwExceptions = false) - { - var result = Security.AuthorizeRequest(throwExceptions); - return result == ValidateRequestAttempt.Success; - } - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/ajaxHelpers.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/ajaxHelpers.cs deleted file mode 100644 index 3e6ad7d221..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/ajaxHelpers.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Data; -using System.Configuration; -using System.Web; -using System.Web.Security; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.WebControls.WebParts; -using System.Web.UI.HtmlControls; -using Umbraco.Core.IO; - -namespace umbraco.presentation.webservices -{ - public class ajaxHelpers - { - public static void EnsureLegacyCalls(Page page) - { - var sm = ScriptManager.GetCurrent(page); - var legacyPath = new ServiceReference(SystemDirectories.WebServices + "/legacyAjaxCalls.asmx"); - - if (!sm.Services.Contains(legacyPath)) - sm.Services.Add(legacyPath); - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx deleted file mode 100644 index 5acc6e6022..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="C#" CodeBehind="legacyAjaxCalls.asmx.cs" Class="umbraco.presentation.webservices.legacyAjaxCalls" %> diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs deleted file mode 100644 index 28ba66709a..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System; -using System.Web; -using System.Web.Services; -using System.ComponentModel; -using System.Web.Script.Services; -using Umbraco.Core; -using Umbraco.Web; -using Umbraco.Web.WebServices; -using Umbraco.Core.Models.Membership; -using Umbraco.Web.Composing; -using Umbraco.Web._Legacy.UI; -using Umbraco.Core.Services; - -namespace umbraco.presentation.webservices -{ - /// - /// Summary description for legacyAjaxCalls - /// - [WebService(Namespace = "http://umbraco.org/webservices")] - [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] - [ToolboxItem(false)] - [ScriptService] - public class legacyAjaxCalls : UmbracoAuthorizedWebService - { - /// - /// method to accept a string value for the node id. Used for tree's such as python - /// and xslt since the file names are the node IDs - /// - /// - /// - /// - [WebMethod] - [ScriptMethod] - public void Delete(string nodeId, string alias, string nodeType) - { - if (!AuthorizeRequest()) - return; - - //U4-2686 - alias is html encoded, make sure to decode - alias = HttpUtility.HtmlDecode(alias); - - //check which parameters to pass depending on the types passed in - int intNodeId; - if (nodeType == "memberGroups") - { - LegacyDialogHandler.Delete( - new HttpContextWrapper(HttpContext.Current), - Security.CurrentUser, - nodeType, 0, nodeId); - } - else if (int.TryParse(nodeId, out intNodeId) && nodeType != "member") // Fix for #26965 - numeric member login gets parsed as nodeId - { - LegacyDialogHandler.Delete( - new HttpContextWrapper(HttpContext.Current), - Security.CurrentUser, - nodeType, intNodeId, alias); - } - else - { - LegacyDialogHandler.Delete( - new HttpContextWrapper(HttpContext.Current), - Security.CurrentUser, - nodeType, 0, nodeId); - } - } - - /// - /// Permanently deletes a document/media object. - /// Used to remove an item from the recycle bin. - /// - /// - /// - [WebMethod] - [ScriptMethod] - public void DeleteContentPermanently(string nodeId, string nodeType) - { - int intNodeId; - if (int.TryParse(nodeId, out intNodeId)) - { - switch (nodeType) - { - case "media": - case "mediaRecycleBin": - //ensure user has access to media - AuthorizeRequest(Constants.Applications.Media.ToString(), true); - var media = Current.Services.MediaService.GetById(intNodeId); - if (media != null) - Current.Services.MediaService.Delete(media); - break; - case "content": - case "contentRecycleBin": - default: - //ensure user has access to content - AuthorizeRequest(Constants.Applications.Content.ToString(), true); - var content = Current.Services.ContentService.GetById(intNodeId); - if (content != null) - Current.Services.ContentService.Delete(content); - break; - } - } - else - { - throw new ArgumentException("The nodeId argument could not be parsed to an integer"); - } - } - - [WebMethod] - [ScriptMethod] - public void DisableUser(int userId) - { - AuthorizeRequest(Constants.Applications.Users.ToString(), true); - - var user = Services.UserService.GetUserById(userId); - if (user == null) return; - - user.IsApproved = false; - Services.UserService.Save(user); - } - - [WebMethod] - [ScriptMethod] - public string NiceUrl(int nodeId) - { - - AuthorizeRequest(true); - - var umbHelper = new UmbracoHelper(Current.UmbracoContext, Current.Services, Current.ApplicationCache); - - return umbHelper.Url(nodeId); - } - - [WebMethod] - [ScriptMethod] - public string ProgressStatus(string Key) - { - AuthorizeRequest(true); - - return Application[Context.Request.GetItemAsString("key")].ToString(); - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx deleted file mode 100644 index ae09ffa463..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="C#" CodeBehind="nodeSorter.asmx.cs" Class="umbraco.presentation.webservices.nodeSorter" %> diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs deleted file mode 100644 index 5e0cb289f0..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs +++ /dev/null @@ -1,263 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Web.Script.Services; -using System.Web.Services; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Web; -using Umbraco.Web.Actions; -using Umbraco.Web.Composing; - - -namespace umbraco.presentation.webservices -{ - /// - /// Summary description for nodeSorter - /// - [WebService(Namespace = "http://umbraco.org/")] - [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] - [ToolboxItem(false)] - [ScriptService] - public class nodeSorter : UmbracoAuthorizedWebService - { - [WebMethod] - public SortNode GetNodes(string ParentId, string App) - { - if (AuthorizeRequest()) - { - var nodes = new List(); - - // "hack for stylesheet" - if (App == "settings") - { - var stylesheet = Services.FileService.GetStylesheetByName(ParentId.EnsureEndsWith(".css")); - if (stylesheet == null) throw new InvalidOperationException("No stylesheet found by name " + ParentId); - - var sort = 0; - foreach (var child in stylesheet.Properties) - { - nodes.Add(new SortNode(child.Name.GetHashCode(), sort, child.Name, DateTime.Now)); - sort++; - } - - return new SortNode() - { - SortNodes = nodes.ToArray() - }; - } - else - { - var asInt = int.Parse(ParentId); - - var parent = new SortNode { Id = asInt }; - - var entityService = Services.EntityService; - - // Root nodes? - if (asInt == -1) - { - if (App == "media") - { - var rootMedia = entityService.GetRootEntities(UmbracoObjectTypes.Media); - nodes.AddRange(rootMedia.Select(media => new SortNode(media.Id, media.SortOrder, media.Name, media.CreateDate))); - } - else - { - var rootContent = entityService.GetRootEntities(UmbracoObjectTypes.Document); - nodes.AddRange(rootContent.Select(content => new SortNode(content.Id, content.SortOrder, content.Name, content.CreateDate))); - } - } - else - { - var children = entityService.GetChildren(asInt); - nodes.AddRange(children.Select(child => new SortNode(child.Id, child.SortOrder, child.Name, child.CreateDate))); - } - - - parent.SortNodes = nodes.ToArray(); - - return parent; - } - } - - throw new ArgumentException("User not logged in"); - } - - public void UpdateSortOrder(int ParentId, string SortOrder) - { - UpdateSortOrder(ParentId.ToString(), SortOrder); - } - - [WebMethod] - public void UpdateSortOrder(string ParentId, string SortOrder) - { - if (AuthorizeRequest() == false) return; - if (SortOrder.Trim().Length <= 0) return; - - var isContent = Context.Request.GetItemAsString("app") == "content" | Context.Request.GetItemAsString("app") == ""; - var isMedia = Context.Request.GetItemAsString("app") == "media"; - - //ensure user is authorized for the app requested - if (isContent && AuthorizeRequest(Constants.Applications.Content.ToString()) == false) return; - if (isMedia && AuthorizeRequest(Constants.Applications.Media.ToString()) == false) return; - - var ids = SortOrder.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); - if (isContent) - { - SortContent(ids, int.Parse(ParentId)); - } - else if (isMedia) - { - SortMedia(ids); - } - else - { - SortStylesheetProperties(ParentId, ids); - } - } - - private void SortMedia(string[] ids) - { - var mediaService = Services.MediaService; - var sortedMedia = new List(); - try - { - for (var i = 0; i < ids.Length; i++) - { - var id = int.Parse(ids[i]); - var m = mediaService.GetById(id); - sortedMedia.Add(m); - } - - // Save Media with new sort order and update content xml in db accordingly - var sorted = mediaService.Sort(sortedMedia); - } - catch (Exception ex) - { - Current.Logger.Error(ex, "Could not update media sort order"); - } - } - - - private void SortStylesheetProperties(string stylesheetName, string[] names) - { - var stylesheet = Services.FileService.GetStylesheetByName(stylesheetName.EnsureEndsWith(".css")); - if (stylesheet == null) throw new InvalidOperationException("No stylesheet found by name " + stylesheetName); - - var currProps = stylesheet.Properties.ToArray(); - //remove them all first - foreach (var prop in currProps) - { - stylesheet.RemoveProperty(prop.Name); - } - - //re-add them in the right order - for (var i = 0; i < names.Length; i++) - { - var found = currProps.Single(x => x.Name == names[i]); - stylesheet.AddProperty(found); - } - - Services.FileService.SaveStylesheet(stylesheet); - } - - private void SortContent(string[] ids, int parentId) - { - var contentService = Services.ContentService; - try - { - // Save content with new sort order and update db+cache accordingly - var intIds = new List(); - foreach (var stringId in ids) - { - int intId; - if (int.TryParse(stringId, out intId)) - intIds.Add(intId); - } - var sorted = contentService.Sort(intIds.ToArray()); - - // refresh sort order on cached xml - // but no... this is not distributed - solely relying on content service & events should be enough - //content.Instance.SortNodes(parentId); - - //send notifications! TODO: This should be put somewhere centralized instead of hard coded directly here - if (parentId > 0) - { - Services.NotificationService.SendNotification(contentService.GetById(parentId), Current.Actions.GetAction(), UmbracoContext, Services.TextService, GlobalSettings); - } - - } - catch (Exception ex) - { - Current.Logger.Error(ex, "Could not update content sort order"); - } - } - - } - - [Serializable] - public class SortNode - { - public SortNode() - { - } - - private SortNode[] _sortNodes; - - public SortNode[] SortNodes - { - get { return _sortNodes; } - set { _sortNodes = value; } - } - - public int TotalNodes - { - get { return _sortNodes != null ? _sortNodes.Length : 0; } - set { int test = value; } - } - - public SortNode(int Id, int SortOrder, string Name, DateTime CreateDate) - { - _id = Id; - _sortOrder = SortOrder; - _name = Name; - _createDate = CreateDate; - } - - private DateTime _createDate; - - public DateTime CreateDate - { - get { return _createDate; } - set { _createDate = value; } - } - - private string _name; - - public string Name - { - get { return _name; } - set { _name = value; } - } - - private int _sortOrder; - - public int SortOrder - { - get { return _sortOrder; } - set { _sortOrder = value; } - } - - private int _id; - - public int Id - { - get { return _id; } - set { _id = value; } - } - } -}