Merge pull request #3429 from umbraco/temp8-231-send-to-publish-with-variants

v8 - send to publish with variants
This commit is contained in:
Claus
2018-11-06 14:19:28 +01:00
committed by GitHub
54 changed files with 715 additions and 2293 deletions
+14 -14
View File
@@ -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;
}
}
}
+12 -17
View File
@@ -475,33 +475,28 @@ namespace Umbraco.Core.Models
/// <remarks>
/// Overriden to deal with specific object instances
/// </remarks>
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;
}
}
}
+11 -17
View File
@@ -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)
@@ -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<int>();
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<int>();
clonedEntity._contentTypeComposition = ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList();
}
}
}
@@ -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;
}
}
}
+15 -6
View File
@@ -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;
}
/// <summary>
/// Used by inheritors to modify the DeepCloning logic
/// </summary>
/// <param name="clone"></param>
protected virtual void PerformDeepClone(object clone)
{
}
}
}
+6 -15
View File
@@ -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);
}
}
}
+10 -14
View File
@@ -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<string>();
clone._removedProperties = new List<string>();
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<string>();
clonedEntity._removedProperties = new List<string>();
clonedEntity._properties = (MacroPropertyCollection)Properties.DeepClone();
//re-assign the event handler
clonedEntity._properties.CollectionChanged += clonedEntity.PropertiesChanged;
}
}
}
+7 -12
View File
@@ -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();
}
/// <inheritdoc />
+12 -17
View File
@@ -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<string, object>();
var cloneAdditionalData = clonedEntity._additionalData = new Dictionary<string, object>();
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<IReadOnlyUserGroup>(_userGroups);
clone._allowedSections = _allowedSections != null ? new List<string>(_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<IReadOnlyUserGroup>(_userGroups);
clonedEntity._allowedSections = _allowedSections != null ? new List<string>(_allowedSections) : null;
}
/// <summary>
@@ -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; }
/// <summary>
/// This will either be an HTML or text based summary depending on the email type being sent
/// </summary>
public string Summary { get; }
public string EditedUser { get; }
public string SiteUrl { get; }
}
}
@@ -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; }
}
}
+4 -11
View File
@@ -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();
}
}
}
+7 -13
View File
@@ -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;
}
}
}
+6 -11
View File
@@ -424,22 +424,17 @@ namespace Umbraco.Core.Models
}
/// <inheritdoc />
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<int>(() => PropertyGroupId.Value);
clonedEntity._propertyGroupId = new Lazy<int>(() => PropertyGroupId.Value);
}
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
}
}
}
+6 -12
View File
@@ -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;
}
}
}
+2 -2
View File
@@ -312,12 +312,12 @@ namespace Umbraco.Core.Services
/// <summary>
/// Sorts documents.
/// </summary>
bool Sort(IEnumerable<IContent> items, int userId = 0, bool raiseEvents = true);
OperationResult Sort(IEnumerable<IContent> items, int userId = 0, bool raiseEvents = true);
/// <summary>
/// Sorts documents.
/// </summary>
bool Sort(IEnumerable<int> ids, int userId = 0, bool raiseEvents = true);
OperationResult Sort(IEnumerable<int> ids, int userId = 0, bool raiseEvents = true);
#endregion
@@ -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
{
/// <summary>
/// Sends the notifications for the specified user regarding the specified node and action.
/// </summary>
/// <param name="entity"></param>
/// <param name="operatingUser"></param>
/// <param name="action"></param>
/// <param name="actionName"></param>
/// <param name="http"></param>
/// <param name="createSubject"></param>
/// <param name="createBody"></param>
void SendNotifications(IUser operatingUser, IUmbracoEntity entity, string action, string actionName, HttpContextBase http,
Func<IUser, string[], string> createSubject,
Func<IUser, string[], string> createBody);
/// <summary>
/// Sends the notifications for the specified user regarding the specified nodes and action.
/// </summary>
@@ -37,9 +22,9 @@ namespace Umbraco.Core.Services
/// <param name="http"></param>
/// <param name="createSubject"></param>
/// <param name="createBody"></param>
void SendNotifications(IUser operatingUser, IEnumerable<IUmbracoEntity> entities, string action, string actionName, HttpContextBase http,
Func<IUser, string[], string> createSubject,
Func<IUser, string[], string> createBody);
void SendNotifications(IUser operatingUser, IEnumerable<IContent> entities, string action, string actionName, Uri siteUri,
Func<(IUser user, NotificationEmailSubjectParams subject), string> createSubject,
Func<(IUser user, NotificationEmailBodyParams body, bool isHtml), string> createBody);
/// <summary>
/// Gets the notifications for the user
@@ -1906,17 +1906,19 @@ namespace Umbraco.Core.Services.Implement
/// <param name="items"></param>
/// <param name="userId"></param>
/// <param name="raiseEvents"></param>
/// <returns>True if sorting succeeded, otherwise False</returns>
public bool Sort(IEnumerable<IContent> items, int userId = 0, bool raiseEvents = true)
/// <returns>Result indicating what action was taken when handling the command.</returns>
public OperationResult Sort(IEnumerable<IContent> 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
/// <param name="ids"></param>
/// <param name="userId"></param>
/// <param name="raiseEvents"></param>
/// <returns>True if sorting succeeded, otherwise False</returns>
public bool Sort(IEnumerable<int> ids, int userId = 0, bool raiseEvents = true)
/// <returns>Result indicating what action was taken when handling the command.</returns>
public OperationResult Sort(IEnumerable<int> 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<IContent>(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<IContent>();
var saved = new List<IContent>();
@@ -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<IContent>(x, TreeChangeTypes.RefreshNode)).ToEventArgs());
@@ -1996,7 +2009,7 @@ namespace Umbraco.Core.Services.Implement
scope.Events.Dispatch(Published, this, new PublishEventArgs<IContent>(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
/// </summary>
public static event TypedEventHandler<IContentService, DeleteRevisionsEventArgs> DeletedVersions;
/// <summary>
/// Occurs before Sorting
/// </summary>
public static event TypedEventHandler<IContentService, SaveEventArgs<IContent>> Sorting;
/// <summary>
/// Occurs after Sorting
/// </summary>
public static event TypedEventHandler<IContentService, SaveEventArgs<IContent>> Sorted;
/// <summary>
/// Occurs before Save
/// </summary>
@@ -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));
}
/// <summary>
/// Sends the notifications for the specified user regarding the specified node and action.
/// </summary>
/// <param name="entity"></param>
/// <param name="operatingUser"></param>
/// <param name="action"></param>
/// <param name="actionName"></param>
/// <param name="http"></param>
/// <param name="createSubject"></param>
/// <param name="createBody"></param>
/// <remarks>
/// Currently this will only work for Content entities!
/// </remarks>
public void SendNotifications(IUser operatingUser, IUmbracoEntity entity, string action, string actionName, HttpContextBase http,
Func<IUser, string[], string> createSubject,
Func<IUser, string[], string> 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);
}
/// <summary>
/// Gets the previous version to the latest version of the content item if there is one
/// </summary>
@@ -131,20 +65,14 @@ namespace Umbraco.Core.Services.Implement
/// <param name="operatingUser"></param>
/// <param name="action"></param>
/// <param name="actionName"></param>
/// <param name="http"></param>
/// <param name="siteUri"></param>
/// <param name="createSubject"></param>
/// <param name="createBody"></param>
/// <remarks>
/// Currently this will only work for Content entities!
/// </remarks>
public void SendNotifications(IUser operatingUser, IEnumerable<IUmbracoEntity> entities, string action, string actionName, HttpContextBase http,
Func<IUser, string[], string> createSubject,
Func<IUser, string[], string> createBody)
public void SendNotifications(IUser operatingUser, IEnumerable<IContent> 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<IContent> == false)
throw new NotSupportedException();
var entitiesL = entities as List<IContent> ?? entities.Cast<IContent>().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<int, IContentBase>();
// 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
/// <param name="content"></param>
/// <param name="oldDoc"></param>
/// <param name="actionName">The action readable name - currently an action is just a single letter, this is the name associated with the letter </param>
/// <param name="http"></param>
/// <param name="siteUri"></param>
/// <param name="createSubject">Callback to create the mail subject</param>
/// <param name="createBody">Callback to create the mail body</param>
private NotificationRequest CreateNotificationRequest(IUser performingUser, IUser mailingUser, IContentBase content, IContentBase oldDoc,
string actionName, HttpContextBase http,
Func<IUser, string[], string> createSubject,
Func<IUser, string[], string> 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("<table style=\"width: 100 %; \">");
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("<tr>");
summary.Append("<th style='text-align: left; vertical-align: top; width: 25%;border-bottom: 1px solid #CCC'>");
summary.Append(p.PropertyType.Name);
summary.Append("</th>");
summary.Append("<td style='text-align: left; vertical-align: top;border-bottom: 1px solid #CCC'>");
summary.Append(newText);
summary.Append("</td>");
summary.Append("</tr>");
}
summary.Append("</table>");
}
}
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("<tr>");
summary.Append("<th style='text-align: left; vertical-align: top; width: 25%;'> Note: </th>");
summary.Append(
"<td style='text-align: left; vertical-align: top;'> <span style='background-color:red;'>Red for deleted characters</span>&nbsp;<span style='background-color:yellow;'>Yellow for inserted characters</span></td>");
summary.Append("</tr>");
summary.Append("<tr>");
summary.Append("<th style='text-align: left; vertical-align: top; width: 25%;'> New ");
summary.Append(p.PropertyType.Name);
summary.Append("</th>");
summary.Append("<td style='text-align: left; vertical-align: top;'>");
summary.Append(ReplaceLinks(CompareText(oldText, newText, true, false, "<span style='background-color:yellow;'>", string.Empty), http.Request));
summary.Append("</td>");
summary.Append("</tr>");
summary.Append("<tr>");
summary.Append("<th style='text-align: left; vertical-align: top; width: 25%;'> Old ");
summary.Append(p.PropertyType.Name);
summary.Append("</th>");
summary.Append("<td style='text-align: left; vertical-align: top;'>");
summary.Append(ReplaceLinks(CompareText(newText, oldText, true, false, "<span style='background-color:red;'>", string.Empty), http.Request));
summary.Append("</td>");
summary.Append("</tr>");
//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("<ul>");
foreach (var culture in culturesChanged)
{
summary.Append("<li>");
summary.Append(culture);
summary.Append("</li>");
}
summary.Append("</ul>");
}
else
{
summary.Append("<tr>");
summary.Append("<th style='text-align: left; vertical-align: top; width: 25%;'>");
summary.Append(p.PropertyType.Name);
summary.Append("</th>");
summary.Append("<td style='text-align: left; vertical-align: top;'>");
summary.Append(newText);
summary.Append("</td>");
summary.Append("</tr>");
//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(
"<tr><td colspan=\"2\" style=\"border-bottom: 1px solid #CCC; font-size: 2px;\">&nbsp;</td></tr>");
}
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(@"<html><head>
</head>
<body style='font-family: Trebuchet MS, arial, sans-serif; font-color: black;'>
", 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
/// <param name="oldString">The old string.</param>
private static void ReplaceHtmlSymbols(ref string oldString)
{
if (oldString.IsNullOrWhiteSpace()) return;
oldString = oldString.Replace("&nbsp;", " ");
oldString = oldString.Replace("&rsquo;", "'");
oldString = oldString.Replace("&amp;", "&");
@@ -512,69 +462,7 @@ namespace Umbraco.Core.Services.Implement
oldString = oldString.Replace("&rdquo;", "”");
oldString = oldString.Replace("&quot;", "\"");
}
/// <summary>
/// Compares the text.
/// </summary>
/// <param name="oldText">The old text.</param>
/// <param name="newText">The new text.</param>
/// <param name="displayInsertedText">if set to <c>true</c> [display inserted text].</param>
/// <param name="displayDeletedText">if set to <c>true</c> [display deleted text].</param>
/// <param name="insertedStyle">The inserted style.</param>
/// <param name="deletedStyle">The deleted style.</param>
/// <returns></returns>
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("</span>");
}
// 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("</span>");
} // 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!
+2
View File
@@ -395,6 +395,8 @@
<Compile Include="Models\Identity\IdentityMapperProfile.cs" />
<Compile Include="Models\Membership\MemberExportModel.cs" />
<Compile Include="Models\Membership\MemberExportProperty.cs" />
<Compile Include="Models\NotificationEmailBodyParams.cs" />
<Compile Include="Models\NotificationEmailSubjectParams.cs" />
<Compile Include="Models\PathValidationExtensions.cs" />
<Compile Include="Models\Entities\TreeEntityBase.cs" />
<Compile Include="Models\PropertyTagsExtensions.cs" />
+104
View File
@@ -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()
{
+4 -2
View File
@@ -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<IUrlSegmentProvider> urlSegmentProviders,
TypeLoader typeLoader,
@@ -159,10 +161,11 @@ namespace Umbraco.Tests.TestHelpers
var runtimeState = Mock.Of<IRuntimeState>();
var idkMap = new IdkMap(scopeProvider);
var localizationService = GetLazyService<ILocalizationService>(container, c => new LocalizationService(scopeProvider, logger, eventMessagesFactory, GetRepo<IDictionaryRepository>(c), GetRepo<IAuditRepository>(c), GetRepo<ILanguageRepository>(c)));
var userService = GetLazyService<IUserService>(container, c => new UserService(scopeProvider, logger, eventMessagesFactory, runtimeState, GetRepo<IUserRepository>(c), GetRepo<IUserGroupRepository>(c),globalSettings));
var dataTypeService = GetLazyService<IDataTypeService>(container, c => new DataTypeService(scopeProvider, logger, eventMessagesFactory, GetRepo<IDataTypeRepository>(c), GetRepo<IDataTypeContainerRepository>(c), GetRepo<IAuditRepository>(c), GetRepo<IEntityRepository>(c), GetRepo<IContentTypeRepository>(c)));
var contentService = GetLazyService<IContentService>(container, c => new ContentService(scopeProvider, logger, eventMessagesFactory, mediaFileSystem, GetRepo<IDocumentRepository>(c), GetRepo<IEntityRepository>(c), GetRepo<IAuditRepository>(c), GetRepo<IContentTypeRepository>(c), GetRepo<IDocumentBlueprintRepository>(c), GetRepo<ILanguageRepository>(c)));
var notificationService = GetLazyService<INotificationService>(container, c => new NotificationService(scopeProvider, userService.Value, contentService.Value, logger, GetRepo<INotificationsRepository>(c),globalSettings));
var notificationService = GetLazyService<INotificationService>(container, c => new NotificationService(scopeProvider, userService.Value, contentService.Value, localizationService.Value, logger, GetRepo<INotificationsRepository>(c), globalSettings, umbracoSettings.Content));
var serverRegistrationService = GetLazyService<IServerRegistrationService>(container, c => new ServerRegistrationService(scopeProvider, logger, eventMessagesFactory, GetRepo<IServerRegistrationRepository>(c)));
var memberGroupService = GetLazyService<IMemberGroupService>(container, c => new MemberGroupService(scopeProvider, logger, eventMessagesFactory, GetRepo<IMemberGroupRepository>(c)));
var memberService = GetLazyService<IMemberService>(container, c => new MemberService(scopeProvider, logger, eventMessagesFactory, memberGroupService.Value, mediaFileSystem, GetRepo<IMemberRepository>(c), GetRepo<IMemberTypeRepository>(c), GetRepo<IMemberGroupRepository>(c), GetRepo<IAuditRepository>(c)));
@@ -170,7 +173,6 @@ namespace Umbraco.Tests.TestHelpers
var contentTypeService = GetLazyService<IContentTypeService>(container, c => new ContentTypeService(scopeProvider, logger, eventMessagesFactory, contentService.Value, GetRepo<IContentTypeRepository>(c), GetRepo<IAuditRepository>(c), GetRepo<IDocumentTypeContainerRepository>(c), GetRepo<IEntityRepository>(c)));
var mediaTypeService = GetLazyService<IMediaTypeService>(container, c => new MediaTypeService(scopeProvider, logger, eventMessagesFactory, mediaService.Value, GetRepo<IMediaTypeRepository>(c), GetRepo<IAuditRepository>(c), GetRepo<IMediaTypeContainerRepository>(c), GetRepo<IEntityRepository>(c)));
var fileService = GetLazyService<IFileService>(container, c => new FileService(scopeProvider, logger, eventMessagesFactory, GetRepo<IStylesheetRepository>(c), GetRepo<IScriptRepository>(c), GetRepo<ITemplateRepository>(c), GetRepo<IPartialViewRepository>(c), GetRepo<IPartialViewMacroRepository>(c), GetRepo<IAuditRepository>(c)));
var localizationService = GetLazyService<ILocalizationService>(container, c => new LocalizationService(scopeProvider, logger, eventMessagesFactory, GetRepo<IDictionaryRepository>(c), GetRepo<IAuditRepository>(c), GetRepo<ILanguageRepository>(c)));
var memberTypeService = GetLazyService<IMemberTypeService>(container, c => new MemberTypeService(scopeProvider, logger, eventMessagesFactory, memberService.Value, GetRepo<IMemberTypeRepository>(c), GetRepo<IAuditRepository>(c), GetRepo<IEntityRepository>(c)));
var entityService = GetLazyService<IEntityService>(container, c => new EntityService(
@@ -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);
})();
@@ -102,11 +102,8 @@
//It was decided to just put these messages into the normal status messages.
var msg = "Unauthorized access to URL: <br/><i>" + rejection.config.url.split('?')[0] + "</i>";
if (rejection.config.data) {
msg += "<br/> with data: <br/><i>" + angular.toJson(rejection.config.data) + "</i><br/>Contact your administrator for information.";
}
var msg = "Unauthorized access to URL: <br/><i>" + rejection.config.url.split('?')[0] + "</i><br/>Contact your administrator for information.";
notificationsService.error("Authorization error", msg);
}
@@ -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);
})();
})();
-6
View File
@@ -342,7 +342,6 @@
<Content Include="Umbraco\Config\Lang\nb.xml" />
<Content Include="Umbraco\Config\Lang\sv.xml" />
<Content Include="Umbraco\Dialogs\republish.aspx" />
<Content Include="Umbraco\Dialogs\SendPublish.aspx" />
<Content Include="Umbraco\Developer\Packages\directoryBrowser.aspx" />
<Content Include="Umbraco\Developer\Packages\editPackage.aspx" />
<Content Include="Umbraco\Dialogs\protectPage.aspx" />
@@ -420,7 +419,6 @@
</None>
<Content Include="Umbraco\Masterpages\umbracoPage.Master" />
<Content Include="Umbraco\Masterpages\umbracoDialog.Master" />
<Content Include="Umbraco\Webservices\CheckForUpgrade.asmx" />
<Content Include="Umbraco\Create\simple.ascx" />
<Content Include="Umbraco\Developer\Macros\editMacro.aspx" />
<Content Include="Umbraco\Js\dualSelectBox.js" />
@@ -430,10 +428,6 @@
<SubType>Form</SubType>
</Content>
<!--<Content Include="Umbraco\users\PermissionEditor.aspx" />-->
<Content Include="Umbraco\Webservices\ajax.js" />
<Content Include="Umbraco\Webservices\codeEditorSave.asmx" />
<Content Include="Umbraco\Webservices\legacyAjaxCalls.asmx" />
<Content Include="Umbraco\Webservices\nodeSorter.asmx" />
<Content Include="Umbraco\Masterpages\default.Master" />
<None Include="web.Template.config">
<SubType>Designer</SubType>
@@ -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
]]></key>
<key alias="mailBodyVariantSummary">The following languages have been modified %0%</key>
<key alias="mailBodyHtml"><![CDATA[
<html>
<head>
@@ -1005,9 +1008,7 @@ To manage your website, simply open the Umbraco back office and start adding con
</table>
<p style='color: #392F54; font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0 0 15px;'>
<h3>Update summary:</h3>
<table style="width: 100%;">
%6%
</table>
%6%
</p>
<p style='color: #392F54; font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0 0 15px;'>
Have a nice day!<br /><br />
@@ -1028,6 +1029,9 @@ To manage your website, simply open the Umbraco back office and start adding con
</body>
</html>
]]></key>
<key alias="mailBodyVariantHtmlSummary"><![CDATA[<p>The following languages have been modified:</p>
%0%
]]></key>
<key alias="mailSubject">[%0%] Notification about %1% performed on %2%</key>
<key alias="notifications">Notifications</key>
</area>
@@ -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
]]></key>
<key alias="mailBodyVariantSummary">The following languages have been modified %0%</key>
<key alias="mailBodyHtml"><![CDATA[
<html>
<head>
@@ -1027,9 +1030,7 @@ To manage your website, simply open the Umbraco back office and start adding con
</table>
<p style='color: #392F54; font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0 0 15px;'>
<h3>Update summary:</h3>
<table style="width: 100%;">
%6%
</table>
%6%
</p>
<p style='color: #392F54; font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0 0 15px;'>
Have a nice day!<br /><br />
@@ -1050,6 +1051,9 @@ To manage your website, simply open the Umbraco back office and start adding con
</body>
</html>
]]></key>
<key alias="mailBodyVariantHtmlSummary"><![CDATA[<p>The following languages have been modified:</p>
%0%
]]></key>
<key alias="mailSubject">[%0%] Notification about %1% performed on %2%</key>
<key alias="notifications">Notifications</key>
</area>
@@ -1,13 +0,0 @@
<%@ Page language="c#" Codebehind="SendPublish.aspx.cs" AutoEventWireup="True" Inherits="umbraco.dialogs.SendPublish" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>umbraco - <%=Services.TextService.Localize("editContentSendToPublish")%></title>
<link href="../css/umbracoGui.css" type="text/css" rel="stylesheet"/>
</head>
<body style="padding: 10px;">
<h3><img src="../images/publish.gif" alt="Republish" align="absmiddle" style="float:left; margin-top: 3px; margin-right: 5px"/> <%=Services.TextService.Localize("editContentSendToPublishText")%></h3>
<br/>
<a href="#" onclick="javascript:UmbClientMgr.closeModalWindow();" style="margin-left: 30px" class="guiDialogNormal"><%=Services.TextService.Localize("closewindow")%></a>
</body>
</html>
@@ -1 +0,0 @@
<%@ WebService Language="C#" CodeBehind="CheckForUpgrade.asmx.cs" Class="umbraco.presentation.webservices.CheckForUpgrade" %>
@@ -1 +0,0 @@
<%@ WebService Language="c#" Codebehind="Developer.asmx.cs" Class="umbraco.webservices.Developer" %>
@@ -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. -----
/// <summary>The root object for the proxies to webservices.</summary>
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. -----
/// <summary>The root object for the ajax engine.</summary>
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 -----
///<summary>Start an AJAX action by entering it into the queue</summary>
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
///<summary>Start an AJAX action by entering it into the queue</summary>
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
///<summary>Check, if the next AJAX action can start.
///This is an internal method that should not be called from external.</summary>
///<remarks>for private use only.<remarks>
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
///<summary>The delay time of an action is over.</summary>
ajax.EndWait = function() {
ajax.timer = null;
ajax._next(true);
} // ajax.EndWait
///<summary>The current action timed out.</summary>
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
///<summary>Finish an AJAX Action the normal way</summary>
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
///<summary>Finish an AJAX Action with an exception</summary>
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
///<summary>Clear the current and all pending AJAX actions.</summary>
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 = "<img style='vertical-align:bottom' src='" + path + "ajax-loader.gif'>&nbsp;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
///<summary>Execute a soap call.
///Build the xml for the call of a soap method of a webservice
///and post it to the server.</summary>
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 = "<?xml version='1.0' encoding='utf-8'?>"
+ "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>"
+ "<soap:Body>"
+ "<" + 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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
} 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 = "<string>" + args[n].join("</string><string>") + "</string>";
} else if (typ[1] == "int[]") {
val = "<int>" + args[n].join("</int><int>") + "</int>";
} else if (typ[1] == "float[]") {
val = "<float>" + args[n].join("</float><float>") + "</float>";
} else if (typ[1] == "bool[]") {
val = "<boolean>" + args[n].join("</boolean><boolean>") + "</boolean>";
} // if
soap += "<" + typ[0] + ">" + val + "</" + typ[0] + ">"
} // for
// envelope end
soap += "</" + p.fname + ">"
+ "</soap:Body>"
+ "</soap:Envelope>";
// 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
///<summary>Callback method for a webservice call that dispatches the response to servive.func or service.onException.</summary>
///<remarks>for private use only.<remarks>
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
///<summary>Callback method to show the result of a soap call in an alert box.</summary>
///<remarks>To set up a debug output in an alert box use:
///proxies.service.method.corefunc = proxies.alertResult;</remarks>
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
///<summary>Show all the details of the returned data of a webservice call.
///Use this method for debugging transmission problems.</summary>
///<remarks>To set up a debug output in an alert box use:
///proxies.service.method.corefunc = proxies.alertResponseText;</remarks>
proxies.alertResponseText = function () {
if (proxies.xmlhttp.readyState == 4)
alert("Status:" + proxies.xmlhttp.status + "\nRESULT:" + proxies.xmlhttp.responseText);
} // proxies.alertResponseText
///<summary>show the details about an exception.</summary>
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
///<summary>Get a browser specific implementation of the XMLHttpRequest object.</summary>
// 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
///<summary>Get a browser specific implementation of the XMLDOM object, containing a XML document.</summary>
///<param name="xmlText">the xml document as string.</param>
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
///<summary>show the details of a javascript object.</summary>
///<remarks>This helps a lot while developing and debugging.</remarks>
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 -----
@@ -1 +0,0 @@
<%@ WebService Language="C#" CodeBehind="codeEditorSave.asmx.cs" Class="umbraco.presentation.webservices.codeEditorSave" %>
@@ -1 +0,0 @@
<%@ WebService Language="C#" CodeBehind="legacyAjaxCalls.asmx.cs" Class="umbraco.presentation.webservices.legacyAjaxCalls" %>
@@ -1 +0,0 @@
<%@ WebService Language="C#" CodeBehind="nodeSorter.asmx.cs" Class="umbraco.presentation.webservices.nodeSorter" %>
@@ -1 +0,0 @@
<%@ WebService Language="c#" Codebehind="templates.asmx.cs" Class="umbraco.webservices.templates" %>
@@ -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<ActionToPublish>());
base.Compose(composition);
composition.Container.RegisterSingleton<Notifier>();
}
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<ActionToPublish>(), args.Entity);
//Send notifications for the published action
ContentService.Published += (sender, args) =>
{
foreach (var content in args.PublishedEntities)
notificationService.SendNotification(content, actions.GetAction<ActionToPublish>());
};
ContentService.Published += (sender, args) => notifier.Notify(actions.GetAction<ActionPublish>(), 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<IContent>();
var updatedEntities = new List<IContent>();
//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<ActionNew>());
notificationService.SendNotification(updatedEntities, actions.GetAction<ActionUpdate>());
};
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<ActionDelete>());
};
ContentService.Deleted += (sender, args) => notifier.Notify(actions.GetAction<ActionDelete>(), args.DeletedEntities.ToArray());
//Send notifications for the unpublish action
ContentService.Unpublished += (sender, args) =>
ContentService.Unpublished += (sender, args) => notifier.Notify(actions.GetAction<ActionUnpublish>(), args.PublishedEntities.ToArray());
}
private void ContentServiceSorted(Notifier notifier, IContentService sender, Core.Events.SaveEventArgs<IContent> 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<ActionSort>(), new[] { parent });
}
private void ContentServiceSaved(Notifier notifier, IContentService sender, Core.Events.SaveEventArgs<IContent> args, ActionCollection actions)
{
var newEntities = new List<IContent>();
var updatedEntities = new List<IContent>();
//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<ActionUnpublish>());
};
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<ActionNew>(), newEntities.ToArray());
notifier.Notify(actions.GetAction<ActionUpdate>(), updatedEntities.ToArray());
}
/// <summary>
/// This class is used to send the notifications
/// </summary>
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;
/// <summary>
/// Constructor
/// </summary>
/// <param name="umbracoContextAccessor"></param>
/// <param name="notificationService"></param>
/// <param name="userService"></param>
/// <param name="textService"></param>
/// <param name="globalSettings"></param>
/// <param name="contentConfig"></param>
/// <param name="logger"></param>
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<IContent> 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
}));
}
}
}
}
}
@@ -41,6 +41,7 @@ namespace Umbraco.Web.Editors
{
private readonly ManifestParser _manifestParser;
private readonly UmbracoFeatures _features;
private readonly IRuntimeState _runtimeState;
private BackOfficeUserManager<BackOfficeIdentityUser> _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
+3 -6
View File
@@ -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<ContentController>("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<ActionSort>(), UmbracoContext, Services.TextService, GlobalSettings);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (Exception ex)
@@ -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<IUmbracoEntity> 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<IUmbracoEntity> 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<IUmbracoEntity> 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<IUmbracoEntity> 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));
}
}
}
@@ -48,6 +48,7 @@ namespace Umbraco.Web.Trees
internal static Attempt<LegacyUrlAction> GetUrlAndTitleFromLegacyAction(IAction action, string nodeId, string nodeType, string nodeName, string currentSection)
{
switch (action)
{
case ActionNew actionNew:
return Attempt.Succeed(
-28
View File
@@ -762,7 +762,6 @@
<Compile Include="Models\TagModel.cs" />
<Compile Include="Models\UpgradeCheckResponse.cs" />
<Compile Include="Models\PasswordChangedModel.cs" />
<Compile Include="NotificationServiceExtensions.cs" />
<Compile Include="PropertyEditors\ColorPickerConfigurationEditor.cs" />
<Compile Include="PropertyEditors\EmailAddressPropertyEditor.cs" />
<Compile Include="PropertyEditors\ImageCropperPropertyEditor.cs" />
@@ -1268,13 +1267,6 @@
<Compile Include="umbraco.presentation\umbraco\dialogs\republish.aspx.designer.cs">
<DependentUpon>republish.aspx</DependentUpon>
</Compile>
<Compile Include="umbraco.presentation\umbraco\dialogs\SendPublish.aspx.cs">
<DependentUpon>SendPublish.aspx</DependentUpon>
<SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="umbraco.presentation\umbraco\dialogs\SendPublish.aspx.designer.cs">
<DependentUpon>SendPublish.aspx</DependentUpon>
</Compile>
<Compile Include="umbraco.presentation\umbraco\developer\Packages\editPackage.aspx.cs">
<DependentUpon>editPackage.aspx</DependentUpon>
<SubType>ASPXCodeBehind</SubType>
@@ -1287,19 +1279,6 @@
<Compile Include="umbraco.presentation\umbraco\templateControls\ItemRenderer.cs" />
<Compile Include="umbraco.presentation\umbraco\templateControls\Macro.cs" />
<Compile Include="umbraco.presentation\umbraco\templateControls\ContentType.cs" />
<Compile Include="umbraco.presentation\umbraco\webservices\ajaxHelpers.cs" />
<Compile Include="umbraco.presentation\umbraco\webservices\CheckForUpgrade.asmx.cs">
<DependentUpon>CheckForUpgrade.asmx</DependentUpon>
<SubType>Component</SubType>
</Compile>
<Compile Include="umbraco.presentation\umbraco\webservices\legacyAjaxCalls.asmx.cs">
<DependentUpon>legacyAjaxCalls.asmx</DependentUpon>
<SubType>Component</SubType>
</Compile>
<Compile Include="umbraco.presentation\umbraco\webservices\nodeSorter.asmx.cs">
<DependentUpon>nodeSorter.asmx</DependentUpon>
<SubType>Component</SubType>
</Compile>
<Compile Include="UmbracoApplication.cs" />
<Compile Include="UmbracoContext.cs" />
<Compile Include="UmbracoModule.cs" />
@@ -1311,9 +1290,6 @@
</Compile>
<Compile Include="TagsController.cs" />
<Compile Include="UmbracoAuthorizedHttpHandler.cs" />
<Compile Include="umbraco.presentation\umbraco\webservices\UmbracoAuthorizedWebService.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="UmbracoHttpHandler.cs" />
<Compile Include="UmbracoWebService.cs">
<SubType>Component</SubType>
@@ -1340,10 +1316,7 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="UI\JavaScript\PreviewInitialize.js" />
<Content Include="umbraco.presentation\umbraco\webservices\CheckForUpgrade.asmx" />
<!--<Content Include="umbraco.presentation\umbraco\users\PermissionEditor.aspx" />-->
<Content Include="umbraco.presentation\umbraco\webservices\legacyAjaxCalls.asmx" />
<Content Include="umbraco.presentation\umbraco\webservices\nodeSorter.asmx" />
<Content Include="PublishedCache\NuCache\notes.txt" />
<Content Include="umbraco.presentation\umbraco\developer\RelationTypes\EditRelationType.aspx">
<SubType>ASPXCodeBehind</SubType>
@@ -1361,7 +1334,6 @@
<Content Include="umbraco.presentation\umbraco\dialogs\republish.aspx">
<SubType>ASPXCodeBehind</SubType>
</Content>
<Content Include="umbraco.presentation\umbraco\dialogs\SendPublish.aspx" />
<Content Include="umbraco.presentation\umbraco\developer\Packages\editPackage.aspx">
<SubType>ASPXCodeBehind</SubType>
</Content>
@@ -1,13 +0,0 @@
<%@ Page language="c#" Codebehind="SendPublish.aspx.cs" AutoEventWireup="True" Inherits="umbraco.dialogs.SendPublish" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>umbraco - <%=Services.TextService.Localize("editContentSendToPublish")%></title>
<link href="../css/umbracoGui.css" type="text/css" rel="stylesheet"/>
</head>
<body style="padding: 10px;">
<h3><img src="../images/publish.gif" alt="Republish" align="absmiddle" style="float:left; margin-top: 3px; margin-right: 5px"/> <%=Services.TextService.Localize("editContentSendToPublishText")%></h3>
<br/>
<a href="#" onclick="javascript:UmbClientMgr.closeModalWindow();" style="margin-left: 30px" class="guiDialogNormal"><%=Services.TextService.Localize("closewindow")%></a>
</body>
</html>
@@ -1,39 +0,0 @@
using System;
using Umbraco.Web;
using Umbraco.Core;
using Umbraco.Web.Actions;
using Umbraco.Web.Composing;
namespace umbraco.dialogs
{
/// <summary>
/// Runs all action handlers for the ActionToPublish action for the document with
/// the corresponding document id passed in by query string
/// </summary>
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<ActionToPublish>());
}
}
}
}
}
@@ -1,16 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
namespace umbraco.dialogs {
public partial class SendPublish {
}
}
@@ -1 +0,0 @@
<%@ WebService Language="C#" CodeBehind="CheckForUpgrade.asmx.cs" Class="umbraco.presentation.webservices.CheckForUpgrade" %>
@@ -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>
/// Summary description for CheckForUpgrade
/// </summary>
[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));
}
}
}
@@ -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
{
/// <summary>
/// An abstract web service class that has the methods and properties to correct validate an Umbraco user
/// </summary>
public abstract class UmbracoAuthorizedWebService : UmbracoWebService
{
private bool _hasValidated = false;
/// <summary>
/// Checks if the umbraco context id is valid
/// </summary>
/// <param name="currentUmbracoUserContextId"></param>
/// <returns></returns>
protected bool ValidateUserContextId(string currentUmbracoUserContextId)
{
return UmbracoContext.Security.ValidateCurrentUser();
}
/// <summary>
/// Checks if the username/password credentials are valid
/// </summary>
/// <param name="username"></param>
/// <param name="password"></param>
/// <returns></returns>
protected bool ValidateCredentials(string username, string password)
{
return UmbracoContext.Security.ValidateBackOfficeCredentials(username, password);
}
/// <summary>
/// Validates the user for access to a certain application
/// </summary>
/// <param name="app">The application alias.</param>
/// <param name="throwExceptions">true if an exception should be thrown if authorization fails</param>
/// <returns></returns>
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;
}
/// <summary>
/// Checks if the specified user as access to the app
/// </summary>
/// <param name="app"></param>
/// <param name="user"></param>
/// <returns></returns>
protected bool UserHasAppAccess(string app, IUser user)
{
return Security.UserHasSectionAccess(app, user);
}
/// <summary>
/// Checks if the specified user by username as access to the app
/// </summary>
/// <param name="app"></param>
/// <param name="username"></param>
/// <returns></returns>
protected bool UserHasAppAccess(string app, string username)
{
return Security.UserHasSectionAccess(app, username);
}
/// <summary>
/// Returns true if there is a valid logged in user and that ssl is enabled if required
/// </summary>
/// <param name="throwExceptions">true if an exception should be thrown if authorization fails</param>
/// <returns></returns>
protected bool AuthorizeRequest(bool throwExceptions = false)
{
var result = Security.AuthorizeRequest(throwExceptions);
return result == ValidateRequestAttempt.Success;
}
}
}
@@ -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);
}
}
}
@@ -1 +0,0 @@
<%@ WebService Language="C#" CodeBehind="legacyAjaxCalls.asmx.cs" Class="umbraco.presentation.webservices.legacyAjaxCalls" %>
@@ -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>
/// Summary description for legacyAjaxCalls
/// </summary>
[WebService(Namespace = "http://umbraco.org/webservices")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
[ScriptService]
public class legacyAjaxCalls : UmbracoAuthorizedWebService
{
/// <summary>
/// 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
/// </summary>
/// <param name="nodeId"></param>
/// <param name="alias"></param>
/// <param name="nodeType"></param>
[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);
}
}
/// <summary>
/// Permanently deletes a document/media object.
/// Used to remove an item from the recycle bin.
/// </summary>
/// <param name="nodeId"></param>
/// <param name="nodeType"></param>
[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();
}
}
}
@@ -1 +0,0 @@
<%@ WebService Language="C#" CodeBehind="nodeSorter.asmx.cs" Class="umbraco.presentation.webservices.nodeSorter" %>
@@ -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>
/// Summary description for nodeSorter
/// </summary>
[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<SortNode>();
// "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<IMedia>();
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<nodeSorter>(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<int>();
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<ActionSort>(), UmbracoContext, Services.TextService, GlobalSettings);
}
}
catch (Exception ex)
{
Current.Logger.Error<nodeSorter>(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; }
}
}
}