diff --git a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs index 66d9b2ac25..45e79a1b67 100644 --- a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs @@ -11,6 +11,10 @@ namespace Umbraco.Core.Cache /// /// /// + /// + /// This cache policy uses sliding expiration and caches instances for 5 minutes. However if allow zero count is true, then we use the + /// default policy with no expiry. + /// internal class DefaultRepositoryCachePolicy : DisposableObject, IRepositoryCachePolicy where TEntity : class, IAggregateRoot { @@ -54,7 +58,9 @@ namespace Umbraco.Core.Cache //just to be safe, we cannot cache an item without an identity if (entity.HasIdentity) { - Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => entity); + Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => entity, + timeout: TimeSpan.FromMinutes(5), + isSliding: true); } //If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared @@ -225,7 +231,9 @@ namespace Umbraco.Core.Cache //just to be safe, we cannot cache an item without an identity if (entity.HasIdentity) { - Cache.InsertCacheItem(cacheKey, () => entity); + Cache.InsertCacheItem(cacheKey, () => entity, + timeout: TimeSpan.FromMinutes(5), + isSliding: true); } }); } @@ -244,6 +252,7 @@ namespace Umbraco.Core.Cache { //there was nothing returned but we want to cache a zero count result so add an TEntity[] to the cache // to signify that there is a zero count cache + //NOTE: Don't set expiry/sliding for a zero count Cache.InsertCacheItem(GetCacheTypeKey(), () => new TEntity[] {}); } else @@ -256,7 +265,9 @@ namespace Umbraco.Core.Cache //just to be safe, we cannot cache an item without an identity if (localCopy.HasIdentity) { - Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => localCopy); + Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => localCopy, + timeout: TimeSpan.FromMinutes(5), + isSliding: true); } } } diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs index 40b100ef67..3b3c98fc80 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs @@ -11,6 +11,10 @@ namespace Umbraco.Core.Cache /// /// /// + /// + /// This caching policy has no sliding expiration but uses the default ObjectCache.InfiniteAbsoluteExpiration as it's timeout, so it + /// should not leave the cache unless the cache memory is exceeded and it gets thrown out. + /// internal class FullDataSetRepositoryCachePolicy : DefaultRepositoryCachePolicy where TEntity : class, IAggregateRoot { diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index 5f30c08ce7..3cff4f0298 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -98,45 +98,10 @@ namespace Umbraco.Core.Models.PublishedContent #endregion - #region Cache - - // these methods are called by ContentTypeCacheRefresher and DataTypeCacheRefresher - - internal static void ClearAll() - { - Logging.LogHelper.Debug("Clear all."); - // ok and faster to do it by types, assuming noone else caches PublishedContentType instances - //ApplicationContext.Current.ApplicationCache.ClearStaticCacheByKeySearch("PublishedContentType_"); - ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes(); - } - - internal static void ClearContentType(int id) - { - Logging.LogHelper.Debug("Clear content type w/id {0}.", () => id); - // requires a predicate because the key does not contain the ID - // faster than key strings comparisons anyway - ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes( - (key, value) => value.Id == id); - } - - internal static void ClearDataType(int id) - { - Logging.LogHelper.Debug("Clear data type w/id {0}.", () => id); - // there is no recursion to handle here because a PublishedContentType contains *all* its - // properties ie both its own properties and those that were inherited (it's based upon an - // IContentTypeComposition) and so every PublishedContentType having a property based upon - // the cleared data type, be it local or inherited, will be cleared. - ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes( - (key, value) => value.PropertyTypes.Any(x => x.DataTypeId == id)); - } - + public static PublishedContentType Get(PublishedItemType itemType, string alias) { - var key = string.Format("PublishedContentType_{0}_{1}", - itemType.ToString().ToLowerInvariant(), alias.ToLowerInvariant()); - - var type = ApplicationContext.Current.ApplicationCache.StaticCache.GetCacheItem(key, - () => CreatePublishedContentType(itemType, alias)); + var type = CreatePublishedContentType(itemType, alias); return type; } @@ -169,21 +134,8 @@ namespace Umbraco.Core.Models.PublishedContent return new PublishedContentType(contentType); } - // for unit tests - changing the callback must reset the cache obviously - private static Func _getPublishedContentTypeCallBack; - internal static Func GetPublishedContentTypeCallback - { - get { return _getPublishedContentTypeCallBack; } - set - { - // see note above - //ClearAll(); - ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheByKeySearch("PublishedContentType_"); - - _getPublishedContentTypeCallBack = value; - } - } - - #endregion + // for unit tests + internal static Func GetPublishedContentTypeCallback { get; set; } + } } diff --git a/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs b/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs index 859fa7cf5a..923348e729 100644 --- a/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs +++ b/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs @@ -18,8 +18,11 @@ namespace Umbraco.Core.Persistence.Relators // Is this the same DictionaryItem as the current one we're processing if (Current != null && Current.Id == a.Id) { - // Yes, just add this User2AppDto to the current item's collection - Current.User2AppDtos.Add(p); + if (p.AppAlias.IsNullOrWhiteSpace() == false) + { + // Yes, just add this User2AppDto to the current item's collection + Current.User2AppDtos.Add(p); + } // Return null to indicate we're not done with this User yet return null; @@ -35,7 +38,7 @@ namespace Umbraco.Core.Persistence.Relators Current = a; Current.User2AppDtos = new List(); //this can be null since we are doing a left join - if (p.AppAlias != null) + if (p.AppAlias.IsNullOrWhiteSpace() == false) { Current.User2AppDtos.Add(p); } diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index cc3fb37aa6..1f5cc1ecd4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -156,7 +156,7 @@ namespace Umbraco.Core.Persistence.Repositories "DELETE FROM cmsContentXml WHERE nodeId = @Id", "DELETE FROM cmsContent WHERE nodeId = @Id", "DELETE FROM umbracoAccess WHERE nodeId = @Id", - "DELETE FROM umbracoNode WHERE id = @Id" + "DELETE FROM umbracoNode WHERE id = @Id" }; return list; } diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index fd061dbda9..eb8dfde6b0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -85,7 +85,6 @@ namespace Umbraco.Core.Persistence.Repositories return moveInfo; } - /// /// Returns the content type ids that match the query /// diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index 955e316bc3..acc17b370a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -475,7 +475,7 @@ namespace Umbraco.Core.Persistence.Repositories if (aliases.Any() == false) return base.GetAll(); //return from base.GetAll, this is all cached - return base.GetAll().Where(x => aliases.Contains(x.Alias)); + return base.GetAll().Where(x => aliases.InvariantContains(x.Alias)); } public IEnumerable GetChildren(int masterTemplateId) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 680692ae4f..439b18d9c6 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -910,7 +910,7 @@ namespace Umbraco.Core.Services new MoveEventArgs(evtMsgs, new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent)), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } var moveInfo = new List> @@ -957,7 +957,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } } @@ -1080,7 +1080,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(asArray, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } } using (new WriteLock(Locker)) @@ -1124,7 +1124,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Save, "Bulk Save content performed by user", userId == -1 ? 0 : userId, Constants.System.Root); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } } @@ -1147,7 +1147,7 @@ namespace Umbraco.Core.Services new DeleteEventArgs(content, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } //Make sure that published content is unpublished before being deleted @@ -1178,7 +1178,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Delete, "Delete Content performed by user", userId, content.Id); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } } @@ -2043,7 +2043,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(content, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } } @@ -2075,7 +2075,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Save, "Save Content performed by user", userId, content.Id); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } } diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index 493768b368..b30a66b376 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -44,8 +44,9 @@ namespace Umbraco.Core.Services #region Containers - public Attempt CreateContentTypeContainer(int parentId, string name, int userId = 0) + public Attempt> CreateContentTypeContainer(int parentId, string name, int userId = 0) { + var evtMsgs = EventMessagesFactory.Get(); var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DocumentTypeContainerGuid)) { @@ -57,20 +58,32 @@ namespace Umbraco.Core.Services ParentId = parentId, CreatorId = userId }; + + if (SavingContentTypeContainer.IsRaisedEventCancelled( + new SaveEventArgs(container, evtMsgs), + this)) + { + return Attempt.Fail(new OperationStatus(container, OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + repo.AddOrUpdate(container); uow.Commit(); - return Attempt.Succeed(container.Id); + + SavedContentTypeContainer.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); + //TODO: Audit trail ? + + return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); } catch (Exception ex) { - return Attempt.Fail(ex); + return Attempt.Fail(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); } - //TODO: Audit trail ? } } - public Attempt CreateMediaTypeContainer(int parentId, string name, int userId = 0) + public Attempt> CreateMediaTypeContainer(int parentId, string name, int userId = 0) { + var evtMsgs = EventMessagesFactory.Get(); var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.MediaTypeContainerGuid)) { @@ -82,42 +95,83 @@ namespace Umbraco.Core.Services ParentId = parentId, CreatorId = userId }; + + if (SavingMediaTypeContainer.IsRaisedEventCancelled( + new SaveEventArgs(container, evtMsgs), + this)) + { + return Attempt.Fail(new OperationStatus(container, OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + repo.AddOrUpdate(container); uow.Commit(); - return Attempt.Succeed(container.Id); + + SavedMediaTypeContainer.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); + //TODO: Audit trail ? + + return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); } catch (Exception ex) { - return Attempt.Fail(ex); + return Attempt.Fail(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); } - //TODO: Audit trail ? } } - public void SaveContentTypeContainer(EntityContainer container, int userId = 0) + public Attempt SaveContentTypeContainer(EntityContainer container, int userId = 0) { - SaveContainer(container, Constants.ObjectTypes.DocumentTypeContainerGuid, "document type", userId); + return SaveContainer( + SavingContentTypeContainer, SavedContentTypeContainer, + container, Constants.ObjectTypes.DocumentTypeContainerGuid, "document type", userId); } - public void SaveMediaTypeContainer(EntityContainer container, int userId = 0) + public Attempt SaveMediaTypeContainer(EntityContainer container, int userId = 0) { - SaveContainer(container, Constants.ObjectTypes.MediaTypeContainerGuid, "media type", userId); + return SaveContainer( + SavingMediaTypeContainer, SavedMediaTypeContainer, + container, Constants.ObjectTypes.MediaTypeContainerGuid, "media type", userId); } - private void SaveContainer(EntityContainer container, Guid containerObjectType, string objectTypeName, int userId) + private Attempt SaveContainer( + TypedEventHandler> savingEvent, + TypedEventHandler> savedEvent, + EntityContainer container, + Guid containerObjectType, + string objectTypeName, int userId) { + var evtMsgs = EventMessagesFactory.Get(); + if (container.ContainedObjectType != containerObjectType) - throw new InvalidOperationException("Not a " + objectTypeName + " container."); + { + var ex = new InvalidOperationException("Not a " + objectTypeName + " container."); + return OperationStatus.Exception(evtMsgs, ex); + } + if (container.HasIdentity && container.IsPropertyDirty("ParentId")) - throw new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); + { + var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); + return OperationStatus.Exception(evtMsgs, ex); + } + + if (savingEvent.IsRaisedEventCancelled( + new SaveEventArgs(container, evtMsgs), + this)) + { + return OperationStatus.Cancelled(evtMsgs); + } var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, containerObjectType)) { repo.AddOrUpdate(container); - uow.Commit(); - //TODO: Audit trail ? + uow.Commit(); } + + savedEvent.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); + + //TODO: Audit trail ? + + return OperationStatus.Success(evtMsgs); } public EntityContainer GetContentTypeContainer(int containerId) @@ -226,28 +280,54 @@ namespace Umbraco.Core.Services } } - public void DeleteContentTypeContainer(int containerId, int userId = 0) + public Attempt DeleteContentTypeContainer(int containerId, int userId = 0) { + var evtMsgs = EventMessagesFactory.Get(); var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DocumentTypeContainerGuid)) { var container = repo.Get(containerId); - if (container == null) return; + if (container == null) return OperationStatus.NoOperation(evtMsgs); + + if (DeletingContentTypeContainer.IsRaisedEventCancelled( + new DeleteEventArgs(container, evtMsgs), + this)) + { + return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + repo.Delete(container); uow.Commit(); + + DeletedContentTypeContainer.RaiseEvent(new DeleteEventArgs(container, evtMsgs), this); + + return OperationStatus.Success(evtMsgs); //TODO: Audit trail ? } } - public void DeleteMediaTypeContainer(int containerId, int userId = 0) + public Attempt DeleteMediaTypeContainer(int containerId, int userId = 0) { + var evtMsgs = EventMessagesFactory.Get(); var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.MediaTypeContainerGuid)) { var container = repo.Get(containerId); - if (container == null) return; + if (container == null) return OperationStatus.NoOperation(evtMsgs); + + if (DeletingMediaTypeContainer.IsRaisedEventCancelled( + new DeleteEventArgs(container, evtMsgs), + this)) + { + return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + repo.Delete(container); uow.Commit(); + + DeletedMediaTypeContainer.RaiseEvent(new DeleteEventArgs(container, evtMsgs), this); + + return OperationStatus.Success(evtMsgs); //TODO: Audit trail ? } } @@ -1172,13 +1252,23 @@ namespace Umbraco.Core.Services uow.Commit(); } } - + #region Event Handlers - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> DeletingContentType; + public static event TypedEventHandler> SavingContentTypeContainer; + public static event TypedEventHandler> SavedContentTypeContainer; + public static event TypedEventHandler> DeletingContentTypeContainer; + public static event TypedEventHandler> DeletedContentTypeContainer; + public static event TypedEventHandler> SavingMediaTypeContainer; + public static event TypedEventHandler> SavedMediaTypeContainer; + public static event TypedEventHandler> DeletingMediaTypeContainer; + public static event TypedEventHandler> DeletedMediaTypeContainer; + + + /// + /// Occurs before Delete + /// + public static event TypedEventHandler> DeletingContentType; /// /// Occurs after Delete diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index a397975f04..035cfd0ab6 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -29,8 +29,9 @@ namespace Umbraco.Core.Services #region Containers - public Attempt CreateContainer(int parentId, string name, int userId = 0) + public Attempt> CreateContainer(int parentId, string name, int userId = 0) { + var evtMsgs = EventMessagesFactory.Get(); var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid)) { @@ -42,15 +43,26 @@ namespace Umbraco.Core.Services ParentId = parentId, CreatorId = userId }; + + if (SavingContainer.IsRaisedEventCancelled( + new SaveEventArgs(container, evtMsgs), + this)) + { + return Attempt.Fail(new OperationStatus(container, OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + repo.AddOrUpdate(container); uow.Commit(); - return Attempt.Succeed(container.Id); + + SavedContainer.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); + //TODO: Audit trail ? + + return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); } catch (Exception ex) { - return Attempt.Fail(ex); + return Attempt.Fail(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); } - //TODO: Audit trail ? } } @@ -107,31 +119,65 @@ namespace Umbraco.Core.Services } } - public void SaveContainer(EntityContainer container, int userId = 0) + public Attempt SaveContainer(EntityContainer container, int userId = 0) { - if (container.ContainedObjectType != Constants.ObjectTypes.DataTypeGuid) - throw new InvalidOperationException("Not a data type container."); + var evtMsgs = EventMessagesFactory.Get(); + + if (container.ContainedObjectType != Constants.ObjectTypes.DataTypeGuid) + { + var ex = new InvalidOperationException("Not a " + Constants.ObjectTypes.DataTypeGuid + " container."); + return OperationStatus.Exception(evtMsgs, ex); + } + if (container.HasIdentity && container.IsPropertyDirty("ParentId")) - throw new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); + { + var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); + return OperationStatus.Exception(evtMsgs, ex); + } + + if (SavingContainer.IsRaisedEventCancelled( + new SaveEventArgs(container, evtMsgs), + this)) + { + return OperationStatus.Cancelled(evtMsgs); + } var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid)) { repo.AddOrUpdate(container); uow.Commit(); - //TODO: Audit trail ? } + + SavedContainer.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); + + //TODO: Audit trail ? + + return OperationStatus.Success(evtMsgs); } - public void DeleteContainer(int containerId, int userId = 0) + public Attempt DeleteContainer(int containerId, int userId = 0) { + var evtMsgs = EventMessagesFactory.Get(); var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid)) { var container = repo.Get(containerId); - if (container == null) return; + if (container == null) return OperationStatus.NoOperation(evtMsgs); + + if (DeletingContainer.IsRaisedEventCancelled( + new DeleteEventArgs(container, evtMsgs), + this)) + { + return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + repo.Delete(container); uow.Commit(); + + DeletedContainer.RaiseEvent(new DeleteEventArgs(container, evtMsgs), this); + + return OperationStatus.Success(evtMsgs); //TODO: Audit trail ? } } @@ -537,6 +583,12 @@ namespace Umbraco.Core.Services } #region Event Handlers + + public static event TypedEventHandler> SavingContainer; + public static event TypedEventHandler> SavedContainer; + public static event TypedEventHandler> DeletingContainer; + public static event TypedEventHandler> DeletedContainer; + /// /// Occurs before Delete /// diff --git a/src/Umbraco.Core/Services/DomainService.cs b/src/Umbraco.Core/Services/DomainService.cs index ca9fe03dcb..3ffcb92778 100644 --- a/src/Umbraco.Core/Services/DomainService.cs +++ b/src/Umbraco.Core/Services/DomainService.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.Services new DeleteEventArgs(domain, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } var uow = UowProvider.GetUnitOfWork(); @@ -45,7 +45,7 @@ namespace Umbraco.Core.Services var args = new DeleteEventArgs(domain, false, evtMsgs); Deleted.RaiseEvent(args, this); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } public IDomain GetByName(string name) @@ -91,7 +91,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(domainEntity, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } var uow = UowProvider.GetUnitOfWork(); @@ -102,7 +102,7 @@ namespace Umbraco.Core.Services } Saved.RaiseEvent(new SaveEventArgs(domainEntity, false, evtMsgs), this); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } #region Event Handlers diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs index 3c6b8404b5..cd905a5ccc 100644 --- a/src/Umbraco.Core/Services/IContentTypeService.cs +++ b/src/Umbraco.Core/Services/IContentTypeService.cs @@ -21,10 +21,11 @@ namespace Umbraco.Core.Services /// Attempt ValidateComposition(IContentTypeComposition compo); - Attempt CreateContentTypeContainer(int parentId, string name, int userId = 0); - Attempt CreateMediaTypeContainer(int parentId, string name, int userId = 0); - void SaveContentTypeContainer(EntityContainer container, int userId = 0); - void SaveMediaTypeContainer(EntityContainer container, int userId = 0); + Attempt> CreateContentTypeContainer(int parentId, string name, int userId = 0); + Attempt> CreateMediaTypeContainer(int parentId, string name, int userId = 0); + Attempt SaveContentTypeContainer(EntityContainer container, int userId = 0); + Attempt SaveMediaTypeContainer(EntityContainer container, int userId = 0); + EntityContainer GetContentTypeContainer(int containerId); EntityContainer GetContentTypeContainer(Guid containerId); IEnumerable GetContentTypeContainers(int[] containerIds); @@ -35,8 +36,8 @@ namespace Umbraco.Core.Services IEnumerable GetMediaTypeContainers(int[] containerIds); IEnumerable GetMediaTypeContainers(string folderName, int level); IEnumerable GetMediaTypeContainers(IMediaType mediaType); - void DeleteMediaTypeContainer(int folderId, int userId = 0); - void DeleteContentTypeContainer(int containerId, int userId = 0); + Attempt DeleteMediaTypeContainer(int folderId, int userId = 0); + Attempt DeleteContentTypeContainer(int containerId, int userId = 0); /// /// Gets all property type aliases. diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index a94628aa87..9e119cd28a 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -10,14 +10,14 @@ namespace Umbraco.Core.Services /// public interface IDataTypeService : IService { - Attempt CreateContainer(int parentId, string name, int userId = 0); - void SaveContainer(EntityContainer container, int userId = 0); + Attempt> CreateContainer(int parentId, string name, int userId = 0); + Attempt SaveContainer(EntityContainer container, int userId = 0); EntityContainer GetContainer(int containerId); EntityContainer GetContainer(Guid containerId); IEnumerable GetContainers(string folderName, int level); IEnumerable GetContainers(IDataTypeDefinition dataTypeDefinition); IEnumerable GetContainers(int[] containerIds); - void DeleteContainer(int containerId, int userId = 0); + Attempt DeleteContainer(int containerId, int userId = 0); /// /// Gets a by its Name diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 0416101728..accbebfe0b 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -747,7 +747,7 @@ namespace Umbraco.Core.Services if (Deleting.IsRaisedEventCancelled( new DeleteEventArgs(media, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } //Delete children before deleting the 'possible parent' @@ -772,7 +772,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Delete, "Delete Media performed by user", userId, media.Id); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } /// @@ -791,7 +791,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(media, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } } @@ -816,7 +816,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Save, "Save Media performed by user", userId, media.Id); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } /// @@ -836,7 +836,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(asArray, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } } @@ -864,7 +864,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Save, "Save Media items performed by user", userId, -1); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } /// @@ -966,7 +966,7 @@ namespace Umbraco.Core.Services if (Trashing.IsRaisedEventCancelled( new MoveEventArgs(new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia)), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } var moveInfo = new List> @@ -1008,7 +1008,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Move, "Move Media to Recycle Bin performed by user", userId, media.Id); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } /// diff --git a/src/Umbraco.Core/Services/OperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus.cs index 31b684c4f1..1561eacbda 100644 --- a/src/Umbraco.Core/Services/OperationStatus.cs +++ b/src/Umbraco.Core/Services/OperationStatus.cs @@ -45,14 +45,25 @@ namespace Umbraco.Core.Services #region Static Helper methods - internal static OperationStatus Cancelled(EventMessages eventMessages) + internal static Attempt Exception(EventMessages eventMessages, Exception ex) { - return new OperationStatus(OperationStatusType.FailedCancelledByEvent, eventMessages); + eventMessages.Add(new EventMessage("", ex.Message, EventMessageType.Error)); + return Attempt.Fail(new OperationStatus(OperationStatusType.FailedExceptionThrown, eventMessages), ex); } - internal static OperationStatus Success(EventMessages eventMessages) + internal static Attempt Cancelled(EventMessages eventMessages) { - return new OperationStatus(OperationStatusType.Success, eventMessages); + return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, eventMessages)); + } + + internal static Attempt Success(EventMessages eventMessages) + { + return Attempt.Succeed(new OperationStatus(OperationStatusType.Success, eventMessages)); + } + + internal static Attempt NoOperation(EventMessages eventMessages) + { + return Attempt.Succeed(new OperationStatus(OperationStatusType.NoOperation, eventMessages)); } #endregion diff --git a/src/Umbraco.Core/Services/OperationStatusType.cs b/src/Umbraco.Core/Services/OperationStatusType.cs index 14f24c5c4e..85ec4a4746 100644 --- a/src/Umbraco.Core/Services/OperationStatusType.cs +++ b/src/Umbraco.Core/Services/OperationStatusType.cs @@ -12,11 +12,21 @@ namespace Umbraco.Core.Services /// The saving was successful. /// Success = 0, - + /// /// The saving has been cancelled by a 3rd party add-in /// - FailedCancelledByEvent = 14 + FailedCancelledByEvent = 14, + + /// + /// Failed, an exception was thrown/handled + /// + FailedExceptionThrown = 15, + + /// + /// When no operation is executed because it was not needed (i.e. deleting an item that doesn't exist) + /// + NoOperation = 100, //TODO: In the future, we might need to add more operations statuses, potentially like 'FailedByPermissions', etc... } diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 43651dd18b..9223cad3da 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -480,7 +480,7 @@ namespace Umbraco.Core.Services _logger.Error("Could not create folder: " + rootFolder, tryCreateFolder.Exception); throw tryCreateFolder.Exception; } - var rootFolderId = tryCreateFolder.Result; + var rootFolderId = tryCreateFolder.Result.Entity.Id; current = _contentTypeService.GetContentTypeContainer(rootFolderId); } @@ -514,7 +514,7 @@ namespace Umbraco.Core.Services _logger.Error("Could not create folder: " + folderName, tryCreateFolder.Exception); throw tryCreateFolder.Exception; } - return _contentTypeService.GetContentTypeContainer(tryCreateFolder.Result); + return _contentTypeService.GetContentTypeContainer(tryCreateFolder.Result.Entity.Id); } private IContentType CreateContentTypeFromXml(XElement documentType) @@ -982,7 +982,7 @@ namespace Umbraco.Core.Services _logger.Error("Could not create folder: " + rootFolder, tryCreateFolder.Exception); throw tryCreateFolder.Exception; } - current = _dataTypeService.GetContainer(tryCreateFolder.Result); + current = _dataTypeService.GetContainer(tryCreateFolder.Result.Entity.Id); } importedFolders.Add(name, current.Id); @@ -1015,7 +1015,7 @@ namespace Umbraco.Core.Services _logger.Error("Could not create folder: " + folderName, tryCreateFolder.Exception); throw tryCreateFolder.Exception; } - return _dataTypeService.GetContainer(tryCreateFolder.Result); + return _dataTypeService.GetContainer(tryCreateFolder.Result.Entity.Id); } private void SavePrevaluesFromXml(List dataTypes, IEnumerable dataTypeElements) diff --git a/src/Umbraco.Core/Services/PublicAccessService.cs b/src/Umbraco.Core/Services/PublicAccessService.cs index bb966309dc..c2eb536bca 100644 --- a/src/Umbraco.Core/Services/PublicAccessService.cs +++ b/src/Umbraco.Core/Services/PublicAccessService.cs @@ -183,7 +183,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(entry, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } repo.AddOrUpdate(entry); @@ -191,7 +191,7 @@ namespace Umbraco.Core.Services uow.Commit(); Saved.RaiseEvent(new SaveEventArgs(entry, false, evtMsgs), this); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } } @@ -207,7 +207,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(entry, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } var uow = UowProvider.GetUnitOfWork(); @@ -218,7 +218,7 @@ namespace Umbraco.Core.Services } Saved.RaiseEvent(new SaveEventArgs(entry, false, evtMsgs), this); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } /// @@ -232,7 +232,7 @@ namespace Umbraco.Core.Services new DeleteEventArgs(entry, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } var uow = UowProvider.GetUnitOfWork(); @@ -243,7 +243,7 @@ namespace Umbraco.Core.Services } Deleted.RaiseEvent(new DeleteEventArgs(entry, false, evtMsgs), this); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } /// diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index 864571d5df..3f168d4741 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -179,7 +179,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage @{ " + "\t" + @"Layout = null; -}", template.Content); +}".Lf(), template.Content.Lf()); } } @@ -209,7 +209,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage @{ " + "\t" + @"Layout = ""test.cshtml""; -}", template2.Content); +}".Lf(), template2.Content.Lf()); } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbselectwhen.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbselectwhen.directive.js new file mode 100644 index 0000000000..b8986c02d3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbselectwhen.directive.js @@ -0,0 +1,28 @@ +(function() { + 'use strict'; + + function SelectWhen($timeout) { + + function link(scope, el, attr, ctrl) { + + attr.$observe("umbSelectWhen", function(newValue) { + if (newValue === "true") { + $timeout(function() { + el.select(); + }); + } + }); + + } + + var directive = { + restrict: 'A', + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbSelectWhen', SelectWhen); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js index 870a7d45d6..b382fa1c32 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js @@ -24,12 +24,10 @@ // scope object, but that would mean we'd have to watch that value too in order to set the outer // ngModelCtrl.$modelValue. It's seems like less overhead to just do this and not have 2x watches. scope.lockedFieldForm.lockedField.$modelValue = undefined; - scope.lockedFieldForm.lockedField.$setViewValue(newValue); - scope.lockedFieldForm.lockedField.$render(); + scope.lockedFieldForm.lockedField.$render(); } + scope.lockedFieldForm.lockedField.$setViewValue(scope.lockedFieldForm.lockedField.$modelValue); }); - - var input = el.find('.umb-locked-field__input'); function activate() { @@ -57,36 +55,14 @@ scope.lock = function() { scope.locked = true; - input.unbind("blur"); }; scope.unlock = function() { scope.locked = false; - autoFocusField(); }; - function autoFocusField() { - - var onBlurHandler = function() { - scope.$apply(function(){ - scope.lock(); - }); - }; - - $timeout(function() { - input.focus(); - input.select(); - input.on("blur", onBlurHandler); - }); - - } - activate(); - scope.$on('$destroy', function() { - input.unbind('blur'); - }); - } var directive = { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index 5175dd3b4d..eed0cffadb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -1,167 +1,194 @@ (function() { - 'use strict'; + 'use strict'; - function MediaGridDirective($filter, mediaHelper) { + function MediaGridDirective($filter, mediaHelper) { - function link(scope, el, attr, ctrl) { + function link(scope, el, attr, ctrl) { - var itemDefaultHeight = 200; - var itemDefaultWidth = 200; - var itemMaxWidth = 200; - var itemMaxHeight = 200; + var itemDefaultHeight = 200; + var itemDefaultWidth = 200; + var itemMaxWidth = 200; + var itemMaxHeight = 200; + var itemMinWidth = 125; + var itemMinHeight = 125; - function activate() { + function activate() { - for (var i = 0; scope.items.length > i; i++) { - var item = scope.items[i]; - setItemData(item); - setOriginalSize(item, itemMaxHeight); - } + if (scope.itemMaxWidth) { + itemMaxWidth = scope.itemMaxWidth; + } - if(scope.items.length > 0) { - setFlexValues(scope.items); - } + if (scope.itemMaxHeight) { + itemMaxHeight = scope.itemMaxHeight; + } - } + if (scope.itemMinWidth) { + itemMinWidth = scope.itemMinWidth; + } - function setItemData(item) { - item.isFolder = !mediaHelper.hasFilePropertyType(item); - if(!item.isFolder){ - item.thumbnail = mediaHelper.resolveFile(item, true); - item.image = mediaHelper.resolveFile(item, false); - } - } + if (scope.itemMinWidth) { + itemMinHeight = scope.itemMinHeight; + } - function setOriginalSize(item, maxHeight) { + for (var i = 0; scope.items.length > i; i++) { + var item = scope.items[i]; + setItemData(item); + setOriginalSize(item, itemMaxHeight); + } - //set to a square by default - item.width = itemDefaultWidth; - item.height = itemDefaultHeight; - item.aspectRatio = 1; - - var widthProp = _.find(item.properties, function(v) { return (v.alias === "umbracoWidth"); }); - - if (widthProp && widthProp.value) { - item.width = parseInt(widthProp.value, 10); - if (isNaN(item.width)) { - item.width = itemDefaultWidth; - } - } - - var heightProp = _.find(item.properties, function(v) { return (v.alias === "umbracoHeight"); }); - - if (heightProp && heightProp.value) { - item.height = parseInt(heightProp.value, 10); - if (isNaN(item.height)) { - item.height = itemDefaultWidth; - } - } - - item.aspectRatio = item.width / item.height; - - // set max width and height - // landscape - if(item.aspectRatio >= 1) { - if(item.width > itemMaxWidth) { - item.width = itemMaxWidth; - item.height = itemMaxWidth / item.aspectRatio; - } - // portrait - } else { - if(item.height > itemMaxHeight) { - item.height = itemMaxHeight; - item.width = itemMaxHeight * item.aspectRatio; - } - } - - } - - function setFlexValues(mediaItems) { - - var flexSortArray = mediaItems; - var smallestImageWidth = null; - var widestImageAspectRatio = null; - - // sort array after image width with the widest image first - flexSortArray = $filter('orderBy')(flexSortArray, 'width', true); - - // find widest image aspect ratio - widestImageAspectRatio = flexSortArray[0].aspectRatio; - - // find smallest image width - smallestImageWidth = flexSortArray[flexSortArray.length - 1].width; - - for (var i = 0; flexSortArray.length > i; i++) { - - var mediaItem = flexSortArray[i]; - var flex = 1 / (widestImageAspectRatio / mediaItem.aspectRatio); - - if (flex === 0) { - flex = 1; - } - - var imageMinWidth = smallestImageWidth * flex; - - var flexStyle = { - "flex": flex + " 1 " + imageMinWidth + "px", - "max-width": mediaItem.width + "px", - "min-width": "125px" - }; - - mediaItem.flexStyle = flexStyle; + if (scope.items.length > 0) { + setFlexValues(scope.items); + } } - } - - scope.clickItem = function(item, $event, $index) { - if(scope.onClick) { - scope.onClick(item, $event, $index); + function setItemData(item) { + item.isFolder = !mediaHelper.hasFilePropertyType(item); + if (!item.isFolder) { + item.thumbnail = mediaHelper.resolveFile(item, true); + item.image = mediaHelper.resolveFile(item, false); + } } - }; - scope.clickItemName = function(item, $event, $index) { - if(scope.onClickName) { - scope.onClickName(item, $event, $index); - $event.stopPropagation(); + function setOriginalSize(item, maxHeight) { + + //set to a square by default + item.width = itemDefaultWidth; + item.height = itemDefaultHeight; + item.aspectRatio = 1; + + var widthProp = _.find(item.properties, function(v) { + return (v.alias === "umbracoWidth"); + }); + + if (widthProp && widthProp.value) { + item.width = parseInt(widthProp.value, 10); + if (isNaN(item.width)) { + item.width = itemDefaultWidth; + } + } + + var heightProp = _.find(item.properties, function(v) { + return (v.alias === "umbracoHeight"); + }); + + if (heightProp && heightProp.value) { + item.height = parseInt(heightProp.value, 10); + if (isNaN(item.height)) { + item.height = itemDefaultWidth; + } + } + + item.aspectRatio = item.width / item.height; + + // set max width and height + // landscape + if (item.aspectRatio >= 1) { + if (item.width > itemMaxWidth) { + item.width = itemMaxWidth; + item.height = itemMaxWidth / item.aspectRatio; + } + // portrait + } else { + if (item.height > itemMaxHeight) { + item.height = itemMaxHeight; + item.width = itemMaxHeight * item.aspectRatio; + } + } + } - }; - scope.hoverItemDetails = function(item, $event, hover) { - if(scope.onDetailsHover) { - scope.onDetailsHover(item, $event, hover); + function setFlexValues(mediaItems) { + + var flexSortArray = mediaItems; + var smallestImageWidth = null; + var widestImageAspectRatio = null; + + // sort array after image width with the widest image first + flexSortArray = $filter('orderBy')(flexSortArray, 'width', true); + + // find widest image aspect ratio + widestImageAspectRatio = flexSortArray[0].aspectRatio; + + // find smallest image width + smallestImageWidth = flexSortArray[flexSortArray.length - 1].width; + + for (var i = 0; flexSortArray.length > i; i++) { + + var mediaItem = flexSortArray[i]; + var flex = 1 / (widestImageAspectRatio / mediaItem.aspectRatio); + + if (flex === 0) { + flex = 1; + } + + var imageMinFlexWidth = smallestImageWidth * flex; + + var flexStyle = { + "flex": flex + " 1 " + imageMinFlexWidth + "px", + "max-width": mediaItem.width + "px", + "min-width": itemMinWidth + "px", + "min-height": itemMinHeight + "px" + }; + + mediaItem.flexStyle = flexStyle; + + } + } - }; - var unbindItemsWatcher = scope.$watch('items', function(newValue, oldValue){ - if(angular.isArray(newValue)) { - activate(); - } - }); + scope.clickItem = function(item, $event, $index) { + if (scope.onClick) { + scope.onClick(item, $event, $index); + } + }; - scope.$on('$destroy', function(){ - unbindItemsWatcher(); - }); + scope.clickItemName = function(item, $event, $index) { + if (scope.onClickName) { + scope.onClickName(item, $event, $index); + $event.stopPropagation(); + } + }; - } + scope.hoverItemDetails = function(item, $event, hover) { + if (scope.onDetailsHover) { + scope.onDetailsHover(item, $event, hover); + } + }; - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-media-grid.html', - scope: { - items: '=', - onDetailsHover: "=", - onClick: '=', - onClickName: "=", - filterBy: "=" - }, - link: link - }; + var unbindItemsWatcher = scope.$watch('items', function(newValue, oldValue) { + if (angular.isArray(newValue)) { + activate(); + } + }); - return directive; - } + scope.$on('$destroy', function() { + unbindItemsWatcher(); + }); - angular.module('umbraco.directives').directive('umbMediaGrid', MediaGridDirective); + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-media-grid.html', + scope: { + items: '=', + onDetailsHover: "=", + onClick: '=', + onClickName: "=", + filterBy: "=", + itemMaxWidth: "@", + itemMaxHeight: "@", + itemMinWidth: "@", + itemMinHeight: "@" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbMediaGrid', MediaGridDirective); })(); diff --git a/src/Umbraco.Web.UI.Client/src/less/components/card.less b/src/Umbraco.Web.UI.Client/src/less/components/card.less index 6e1603b3d8..de21406b6a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/card.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/card.less @@ -37,7 +37,7 @@ .umb-card-icons{ text-align: center; - vertical-align: center; + vertical-align: middle; display: block; list-style: none; margin: 0; diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index 960ec8a7e2..eb569c7cdf 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -27,7 +27,7 @@ label.control-label { } -.controls-row label{padding: 0 10px 0 10px; vertical-align: center} +.controls-row label{padding: 0 10px 0 10px; vertical-align: middle;} diff --git a/src/Umbraco.Web.UI.Client/src/less/installer.less b/src/Umbraco.Web.UI.Client/src/less/installer.less index 574fde27a9..aa8bbccfef 100644 --- a/src/Umbraco.Web.UI.Client/src/less/installer.less +++ b/src/Umbraco.Web.UI.Client/src/less/installer.less @@ -38,7 +38,7 @@ body { line-height: @baseLineHeight; color: @textColor; - vertical-align: center; + vertical-align: middle; text-align: center; } diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 4502527ce8..09f916c59f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -140,7 +140,10 @@ h5.-black { /* LABELS*/ .umb-control-group label.control-label { - text-align: left + text-align: left; +} +.umb-control-group label.control-label > div > label { + padding-left: 0; } .umb-control-group label .help-block, .umb-control-group label small { @@ -150,7 +153,7 @@ h5.-black { padding-top: 5px; } .umb-nolabel .controls { - margin-left: 0px; + margin-left: 0; } .controls-row { @@ -159,11 +162,15 @@ h5.-black { } .umb-user-panel .controls-row { - margin-left: 0px; + margin-left: 0; } .controls-row label { - display: inline-block + display: inline-block; +} + +.controls-row > div > label { + padding-left: 0; } .block-form .controls-row { @@ -171,10 +178,24 @@ h5.-black { padding-top: 0; } -.hidelabel > div > .controls-row, .hidelabel > .controls-row, .hidelabel .controls { - padding: 0px; +.hidelabel > div > .controls-row, .hidelabel > .controls-row, .hidelabel > div > .controls { + padding: 0; border: none; - margin: 0px !important; + margin: 0 !important; +} + +.controls-row > .vertical-align-items { + display: flex; + align-items: center; +} + +.controls-row > .vertical-align-items > input.umb-editor-tiny { + margin-left: 5px; + margin-right: 5px; +} + +.controls-row > .vertical-align-items > input.umb-editor-tiny:first-child { + margin-left: 0; } .thumbnails .selected { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html index 0ab9d6dba4..5bcbfbb3d2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html @@ -76,7 +76,11 @@ items="images" filter-by="searchTerm" on-click="clickHandler" - on-click-name="clickItemName"> + on-click-name="clickItemName" + item-max-width="150" + item-max-height="150" + item-min-width="100" + item-min-height="100"> diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html index 744e635f21..217d420a3a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html @@ -21,7 +21,10 @@ umb-auto-resize required val-server-field="{{serverValidationField}}" - title="{{ngModel}}" /> + title="{{ngModel}}" + focus-when="{{!locked}}" + umb-select-when="{{!locked}}" + on-blur="lock()" /> diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html index d62435ac28..5cd6d5c82f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html @@ -1,20 +1,20 @@
+
+
-
+ - +
+ +
{{item.name}}
+
-
- -
{{item.name}}
-
+
+ {{item.name}} + {{item.name}} -
- {{item.name}} - {{item.name}} - - - -
+ +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html index f90f607e9d..a2c0ff534e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html @@ -4,7 +4,7 @@ ng-if="entityType !== 'media'"> - Sorry, we can not find what you are looking for + + + + + @@ -31,7 +37,7 @@ files-uploaded="vm.onUploadComplete" accept="{{vm.acceptedFileTypes}}" max-file-size="{{vm.maxFileSize}}" - hide-dropzone="{{!vm.activeDrag && items.length > 0 || !items && options.filter }}" + hide-dropzone="{{ options.filter.length > 0 }}" compact="{{ items.length > 0 }}" files-queued="vm.onFilesQueue"> @@ -68,9 +74,9 @@ - Sorry, we can not find what you are looking for + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js index a8a1ac0766..d4ca07aede 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js @@ -19,7 +19,7 @@ vm.activeDrag = false; vm.mediaDetailsTooltip = {}; vm.itemsWithoutFolders = []; - vm.isRecycleBin = $scope.contentId === '-21'; + vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; vm.dragEnter = dragEnter; vm.dragLeave = dragLeave; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html index 24f1dabf33..276274dce3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html @@ -14,7 +14,7 @@ files-uploaded="vm.onUploadComplete" accept="{{vm.acceptedFileTypes}}" max-file-size="{{vm.maxFileSize}}" - hide-dropzone="{{!vm.activeDrag && items.length > 0 || !items && options.filter }}" + hide-dropzone="{{options.filter.length > 0}}" compact="{{ items.length > 0 }}" files-queued="vm.onFilesQueue"> @@ -31,18 +31,12 @@ on-sort="vm.sort"> - - - -
- Sorry, we can not find what you are looking for + + + + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js index 59327524d6..8a2de97f0e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js @@ -10,7 +10,7 @@ vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; vm.activeDrag = false; - vm.isRecycleBin = $scope.contentId === '-21'; + vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; vm.selectItem = selectItem; vm.clickItem = clickItem; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html index f00dfa06c1..fbc97224ab 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html @@ -2,35 +2,41 @@
- +
- - {{css.name}} +
- × - Pixels +
+ × + Pixels +
- Pixels +
+ Pixels +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 258173ce0f..cd0067bf78 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -448,6 +448,7 @@ Retry Permissions Search + Sorry, we can not find what you are looking for Server Show Show page on Send diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index 44a6efe9ff..c44b3f2b51 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -141,9 +141,7 @@ namespace Umbraco.Web.Cache ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.ContentTypeCacheKey); //clear static object cache global::umbraco.cms.businesslogic.ContentType.RemoveAllDataTypeCache(); - - PublishedContentType.ClearAll(); - + base.RefreshAll(); } @@ -280,9 +278,7 @@ namespace Umbraco.Web.Cache //clears the dictionary object cache of the legacy ContentType global::umbraco.cms.businesslogic.ContentType.RemoveFromDataTypeCache(payload.Alias); - - PublishedContentType.ClearContentType(payload.Id); - + //need to recursively clear the cache for each child content type foreach (var descendant in payload.DescendantPayloads) { diff --git a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs index 11b3ab6294..173f4dcb86 100644 --- a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs @@ -108,7 +108,6 @@ namespace Umbraco.Web.Cache if (dataTypeCache) dataTypeCache.Result.ClearCacheByKeySearch(string.Format("{0}{1}", CacheKeys.DataTypePreValuesCacheKey, payload.Id)); - PublishedContentType.ClearDataType(payload.Id); }); base.Refresh(jsonPayload); diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index cb6e56cb13..6358656d00 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -81,7 +81,8 @@ namespace Umbraco.Web.Editors ///
/// /// - [EnsureUserPermissionForContent("id")] + [OutgoingEditorModelEvent] + [EnsureUserPermissionForContent("id")] public ContentItemDisplay GetById(int id) { var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); @@ -116,6 +117,7 @@ namespace Umbraco.Web.Editors /// If this is a container type, we'll remove the umbContainerView tab for a new item since /// it cannot actually list children if it doesn't exist yet. /// + [OutgoingEditorModelEvent] public ContentItemDisplay GetEmpty(string contentTypeAlias, int parentId) { var contentType = Services.ContentTypeService.GetContentType(contentTypeAlias); diff --git a/src/Umbraco.Web/Editors/EditorModelEventManager.cs b/src/Umbraco.Web/Editors/EditorModelEventManager.cs new file mode 100644 index 0000000000..0414bc0cdd --- /dev/null +++ b/src/Umbraco.Web/Editors/EditorModelEventManager.cs @@ -0,0 +1,85 @@ +using System; +using System.Web.Http.Filters; +using Umbraco.Core.Events; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Editors +{ + public abstract class EditorModelEventArgs : EventArgs + { + protected EditorModelEventArgs(object model, UmbracoContext umbracoContext) + { + Model = model; + UmbracoContext = umbracoContext; + } + + public object Model { get; private set; } + public UmbracoContext UmbracoContext { get; private set; } + } + + public sealed class EditorModelEventArgs : EditorModelEventArgs + { + public EditorModelEventArgs(T model, UmbracoContext umbracoContext) + : base(model, umbracoContext) + { + Model = model; + } + + public new T Model { get; private set; } + } + + /// + /// Used to emit events for editor models in the back office + /// + public sealed class EditorModelEventManager + { + public static event TypedEventHandler> SendingContentModel; + public static event TypedEventHandler> SendingMediaModel; + public static event TypedEventHandler> SendingMemberModel; + + private static void OnSendingContentModel(HttpActionExecutedContext sender, EditorModelEventArgs e) + { + var handler = SendingContentModel; + if (handler != null) handler(sender, e); + } + + private static void OnSendingMediaModel(HttpActionExecutedContext sender, EditorModelEventArgs e) + { + var handler = SendingMediaModel; + if (handler != null) handler(sender, e); + } + + private static void OnSendingMemberModel(HttpActionExecutedContext sender, EditorModelEventArgs e) + { + var handler = SendingMemberModel; + if (handler != null) handler(sender, e); + } + + /// + /// Based on the type, emit's a specific event + /// + /// + /// + internal static void EmitEvent(HttpActionExecutedContext sender, EditorModelEventArgs e) + { + var contentItemDisplay = e.Model as ContentItemDisplay; + if (contentItemDisplay != null) + { + OnSendingContentModel(sender, (EditorModelEventArgs) e); + } + + var mediaItemDisplay = e.Model as MediaItemDisplay; + if (mediaItemDisplay != null) + { + OnSendingMediaModel(sender, (EditorModelEventArgs)e); + } + + var memberItemDisplay = e.Model as MemberDisplay; + if (memberItemDisplay != null) + { + OnSendingMemberModel(sender, (EditorModelEventArgs)e); + } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index a2637e68e8..e84aaadce5 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -63,13 +63,14 @@ namespace Umbraco.Web.Editors : base(umbracoContext) { } - + /// /// Gets an empty content item for the /// /// /// /// + [OutgoingEditorModelEvent] public MediaItemDisplay GetEmpty(string contentTypeAlias, int parentId) { var contentType = Services.ContentTypeService.GetMediaType(contentTypeAlias); @@ -92,6 +93,7 @@ namespace Umbraco.Web.Editors ///
/// /// + [OutgoingEditorModelEvent] [EnsureUserPermissionForMedia("id")] public MediaItemDisplay GetById(int id) { diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 0788ba43eb..f03cda1bba 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -145,6 +145,7 @@ namespace Umbraco.Web.Editors ///
/// /// + [OutgoingEditorModelEvent] public MemberDisplay GetByKey(Guid key) { MembershipUser foundMembershipMember; @@ -196,6 +197,7 @@ namespace Umbraco.Web.Editors ///
/// /// + [OutgoingEditorModelEvent] public MemberDisplay GetEmpty(string contentTypeAlias = null) { IMember emptyContent; diff --git a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs index 6523925505..f7c9b3d6a5 100644 --- a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs @@ -40,9 +40,14 @@ namespace Umbraco.Web.Install.InstallSteps var clientDependencyConfig = new ClientDependencyConfiguration(_applicationContext.ProfilingLogger.Logger); var clientDependencyUpdated = clientDependencyConfig.IncreaseVersionNumber(); - var security = new WebSecurity(_httpContext, _applicationContext); - security.PerformLogin(0); - + //During a new install we'll log the default user in (which is id = 0). + // During an upgrade, the user will already need to be logged in in order to run the installer. + if (InstallTypeTarget == InstallationType.NewInstall) + { + var security = new WebSecurity(_httpContext, _applicationContext); + security.PerformLogin(0); + } + //reports the ended install var ih = new InstallHelper(UmbracoContext.Current); ih.InstallStatus(true, ""); diff --git a/src/Umbraco.Web/Models/PublishedContentModels.cs b/src/Umbraco.Web/Models/PublishedContentModels.cs deleted file mode 100644 index d6d3cd9973..0000000000 --- a/src/Umbraco.Web/Models/PublishedContentModels.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.Web.PublishedContentModels -{ - class Empty - { - } -} diff --git a/src/Umbraco.Web/Mvc/RenderModelBinder.cs b/src/Umbraco.Web/Mvc/RenderModelBinder.cs index 20045604d0..172befb791 100644 --- a/src/Umbraco.Web/Mvc/RenderModelBinder.cs +++ b/src/Umbraco.Web/Mvc/RenderModelBinder.cs @@ -7,8 +7,8 @@ using Umbraco.Web.Models; namespace Umbraco.Web.Mvc { - public class RenderModelBinder : IModelBinder - { + public class RenderModelBinder : IModelBinder, IModelBinderProvider + { /// /// Binds the model to a value by using the specified controller context and binding context. /// @@ -18,14 +18,13 @@ namespace Umbraco.Web.Mvc /// The controller context.The binding context. public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { - if (bindingContext.ModelType != typeof (RenderModel)) return null; - object model; if (controllerContext.RouteData.DataTokens.TryGetValue("umbraco", out model) == false) return null; - return model as RenderModel; - } + var culture = UmbracoContext.Current.PublishedContentRequest.Culture; + return BindModel(model, bindingContext.ModelType, culture); + } // source is the model that we have // modelType is the type of the model that we need to bind to @@ -103,5 +102,18 @@ namespace Umbraco.Web.Mvc throw new ModelBindingException(string.Format("Cannot bind source type {0} to model type {1}.", sourceType, modelType)); } + + public IModelBinder GetBinder(Type modelType) + { + // can bind to RenderModel + if (modelType == typeof(RenderModel)) return this; + + // can bind to RenderModel + if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(RenderModel<>)) return this; + + // can bind to TContent where TContent : IPublishedContent + if (typeof(IPublishedContent).IsAssignableFrom(modelType)) return this; + return null; + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 2ae1436d58..ac3fb00af2 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -319,6 +319,7 @@ + @@ -686,6 +687,7 @@ + diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 3c2b7267a6..fca5ff2371 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -125,6 +125,7 @@ namespace Umbraco.Web if (umbracoSettings == null) throw new ArgumentNullException("umbracoSettings"); if (urlProviders == null) throw new ArgumentNullException("urlProviders"); + //if there's already a singleton, and we're not replacing then there's no need to ensure anything if (UmbracoContext.Current != null) { if (replaceContext == false) @@ -132,6 +133,39 @@ namespace Umbraco.Web UmbracoContext.Current._replacing = true; } + var umbracoContext = CreateContext(httpContext, applicationContext, webSecurity, umbracoSettings, urlProviders, preview ?? false); + + //assign the singleton + UmbracoContext.Current = umbracoContext; + return UmbracoContext.Current; + } + + /// + /// Creates a standalone UmbracoContext instance + /// + /// + /// + /// + /// + /// + /// + /// + /// A new instance of UmbracoContext + /// + public static UmbracoContext CreateContext( + HttpContextBase httpContext, + ApplicationContext applicationContext, + WebSecurity webSecurity, + IUmbracoSettingsSection umbracoSettings, + IEnumerable urlProviders, + bool preview) + { + if (httpContext == null) throw new ArgumentNullException("httpContext"); + if (applicationContext == null) throw new ArgumentNullException("applicationContext"); + if (webSecurity == null) throw new ArgumentNullException("webSecurity"); + if (umbracoSettings == null) throw new ArgumentNullException("umbracoSettings"); + if (urlProviders == null) throw new ArgumentNullException("urlProviders"); + var umbracoContext = new UmbracoContext( httpContext, applicationContext, @@ -142,15 +176,15 @@ namespace Umbraco.Web // create the RoutingContext, and assign var routingContext = new RoutingContext( umbracoContext, - + //TODO: Until the new cache is done we can't really expose these to override/mock new Lazy>(() => ContentFinderResolver.Current.Finders), new Lazy(() => ContentLastChanceFinderResolver.Current.Finder), - + // create the nice urls provider // there's one per request because there are some behavior parameters that can be changed new Lazy( - () => new UrlProvider( + () => new UrlProvider( umbracoContext, umbracoSettings.WebRouting, urlProviders), @@ -159,9 +193,7 @@ namespace Umbraco.Web //assign the routing context back umbracoContext.RoutingContext = routingContext; - //assign the singleton - UmbracoContext.Current = umbracoContext; - return UmbracoContext.Current; + return umbracoContext; } /// @@ -460,17 +492,9 @@ namespace Umbraco.Web protected override void DisposeResources() { Security.DisposeIfDisposable(); - Security = null; - _umbracoContext = null; - //Before we set these to null but in fact these are application lifespan singletons so - //there's no reason we need to set them to null and this also caused a problem with packages - //trying to access the cache properties on RequestEnd. - //http://issues.umbraco.org/issue/U4-2734 - //http://our.umbraco.org/projects/developer-tools/301-url-tracker/version-2/44327-Issues-with-URL-Tracker-in-614 - //ContentCache = null; - //MediaCache = null; - //Application = null; + //If not running in a web ctx, ensure the thread based instance is nulled + _umbracoContext = null; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs b/src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs new file mode 100644 index 0000000000..3c74f15b8d --- /dev/null +++ b/src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs @@ -0,0 +1,38 @@ +using System; +using System.Net.Http; +using System.Web.Http.Filters; +using Umbraco.Core; +using Umbraco.Web.Editors; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.WebApi.Filters +{ + /// + /// Used to emit outgoing editor model events + /// + internal sealed class OutgoingEditorModelEventAttribute : ActionFilterAttribute + { + public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) + { + if (actionExecutedContext.Response == null) return; + + var user = UmbracoContext.Current.Security.CurrentUser; + if (user == null) return; + + var objectContent = actionExecutedContext.Response.Content as ObjectContent; + if (objectContent != null) + { + var model = objectContent.Value; + + if (model != null) + { + EditorModelEventManager.EmitEvent(actionExecutedContext, new EditorModelEventArgs( + (dynamic)model, + UmbracoContext.Current)); + } + } + + base.OnActionExecuted(actionExecutedContext); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index d2f7910e5c..1034d33297 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -135,7 +135,7 @@ namespace Umbraco.Web ViewEngines.Engines.Add(new PluginViewEngine()); //set model binder - ModelBinders.Binders.Add(new KeyValuePair(typeof(RenderModel), new RenderModelBinder())); + ModelBinderProviders.BinderProviders.Add(new RenderModelBinder()); // is a provider ////add the profiling action filter //GlobalFilters.Filters.Add(new ProfilingActionFilter()); diff --git a/src/umbraco.cms/businesslogic/media/MediaType.cs b/src/umbraco.cms/businesslogic/media/MediaType.cs index 2dec2cd56f..308b01c853 100644 --- a/src/umbraco.cms/businesslogic/media/MediaType.cs +++ b/src/umbraco.cms/businesslogic/media/MediaType.cs @@ -161,7 +161,9 @@ namespace umbraco.cms.businesslogic.media protected override void setupNode() { var mediaType = ApplicationContext.Current.Services.ContentTypeService.GetMediaType(Id); - SetupNode(mediaType); + // If it's null, it's probably a folder + if (mediaType != null) + SetupNode(mediaType); } #endregion