From 21f2929ab32f2181c25508a546f0f0de63f04501 Mon Sep 17 00:00:00 2001 From: sna Date: Fri, 8 Apr 2016 14:37:33 +0100 Subject: [PATCH 01/10] U4-8199 Remove the internal obsolete HasAccess method. It it not used and marked as obsolete. --- .../Services/PublicAccessServiceExtensions.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs b/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs index 0b682b2b6e..00b3290f5c 100644 --- a/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs +++ b/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs @@ -53,23 +53,6 @@ namespace Umbraco.Core.Services && currentMemberRoles.Contains(x.RuleValue)); } - [Obsolete("this is only used for backward compat")] - internal static bool HasAccess(this IPublicAccessService publicAccessService, int documentId, object providerUserKey, IContentService contentService, MembershipProvider membershipProvider, RoleProvider roleProvider) - { - var content = contentService.GetById(documentId); - if (content == null) return true; - - var entry = publicAccessService.GetEntryForContent(content); - if (entry == null) return true; - - var member = membershipProvider.GetUser(providerUserKey, false); - if (member == null) return false; - - var roles = roleProvider.GetRolesForUser(member.UserName); - return entry.Rules.Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType - && roles.Contains(x.RuleValue)); - } - public static bool HasAccess(this IPublicAccessService publicAccessService, string path, MembershipUser member, RoleProvider roleProvider) { var entry = publicAccessService.GetEntryForContent(path.EnsureEndsWith(path)); From 12f4873c903e0e108f13171ce67ca5c11a424589 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 13 May 2016 19:42:37 +0200 Subject: [PATCH 02/10] Kill IHttpContextAccessor --- .../ServicesCompositionRoot.cs | 5 +-- .../Events/IEventMessagesFactory.cs | 2 ++ ...ry.cs => TransientEventMessagesFactory.cs} | 7 +++- src/Umbraco.Core/Umbraco.Core.csproj | 2 +- .../Services/ThreadSafetyServiceTest.cs | 2 +- .../TestHelpers/BaseDatabaseFactoryTest.cs | 2 +- .../TestHelpers/BaseUmbracoApplicationTest.cs | 2 +- src/Umbraco.Web/DefaultHttpContextAccessor.cs | 20 ----------- src/Umbraco.Web/IHttpContextAccessor.cs | 15 -------- .../RequestLifespanMessagesFactory.cs | 28 --------------- .../ScopeContextEventMessagesFactory.cs | 34 +++++++++++++++++++ .../SingletonHttpContextAccessor.cs | 12 ------- src/Umbraco.Web/Umbraco.Web.csproj | 5 +-- .../Umbraco.Web.csproj.DotSettings | 2 +- src/Umbraco.Web/UmbracoContextExtensions.cs | 12 ++++--- src/Umbraco.Web/WebBootManager.cs | 3 +- 16 files changed, 60 insertions(+), 93 deletions(-) rename src/Umbraco.Core/Events/{TransientMessagesFactory.cs => TransientEventMessagesFactory.cs} (58%) delete mode 100644 src/Umbraco.Web/DefaultHttpContextAccessor.cs delete mode 100644 src/Umbraco.Web/IHttpContextAccessor.cs delete mode 100644 src/Umbraco.Web/RequestLifespanMessagesFactory.cs create mode 100644 src/Umbraco.Web/ScopeContextEventMessagesFactory.cs delete mode 100644 src/Umbraco.Web/SingletonHttpContextAccessor.cs diff --git a/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs b/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs index 0326072447..4f74797f45 100644 --- a/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs +++ b/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs @@ -21,8 +21,9 @@ namespace Umbraco.Core.DependencyInjection { container.RegisterSingleton(); - //These will be replaced by the web boot manager when running in a web context - container.Register(); + // register a transient messages factory, which will be replaced byt the web + // boot manager when running in a web context + container.Register(); //the context container.RegisterSingleton(); diff --git a/src/Umbraco.Core/Events/IEventMessagesFactory.cs b/src/Umbraco.Core/Events/IEventMessagesFactory.cs index cb2391186d..992220ab87 100644 --- a/src/Umbraco.Core/Events/IEventMessagesFactory.cs +++ b/src/Umbraco.Core/Events/IEventMessagesFactory.cs @@ -8,5 +8,7 @@ namespace Umbraco.Core.Events public interface IEventMessagesFactory { EventMessages Get(); + + EventMessages GetOrDefault(); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/TransientMessagesFactory.cs b/src/Umbraco.Core/Events/TransientEventMessagesFactory.cs similarity index 58% rename from src/Umbraco.Core/Events/TransientMessagesFactory.cs rename to src/Umbraco.Core/Events/TransientEventMessagesFactory.cs index 5cd291a37f..880332ac40 100644 --- a/src/Umbraco.Core/Events/TransientMessagesFactory.cs +++ b/src/Umbraco.Core/Events/TransientEventMessagesFactory.cs @@ -3,11 +3,16 @@ namespace Umbraco.Core.Events /// /// A simple/default transient messages factory /// - internal class TransientMessagesFactory : IEventMessagesFactory + internal class TransientEventMessagesFactory : IEventMessagesFactory { public EventMessages Get() { return new EventMessages(); } + + public EventMessages GetOrDefault() + { + return null; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 0533a7c82c..46f109731d 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -319,7 +319,7 @@ - + diff --git a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs index d1e96850b3..7c26e6924c 100644 --- a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs +++ b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs @@ -54,7 +54,7 @@ namespace Umbraco.Tests.Services //here we are going to override the ServiceContext because normally with our test cases we use a //global Database object but this is NOT how it should work in the web world or in any multi threaded scenario. //we need a new Database object for each thread. - var evtMsgs = new TransientMessagesFactory(); + var evtMsgs = new TransientEventMessagesFactory(); ApplicationContext.Services = TestObjects.GetServiceContext( repositoryFactory, _uowProvider, diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index 72008524a2..bc7d2e3ff4 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -113,7 +113,7 @@ namespace Umbraco.Tests.TestHelpers // but, that will NOT prevent _appContext from NOT being configured, because it cannot connect // to the database to check the migrations ;-( - var evtMsgs = new TransientMessagesFactory(); + var evtMsgs = new TransientEventMessagesFactory(); var databaseContext = new DatabaseContext(databaseFactory, Logger); var repositoryFactory = Container.GetInstance(); var serviceContext = TestObjects.GetServiceContext( diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index b88f5d4612..2c333c0a2c 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -215,7 +215,7 @@ namespace Umbraco.Tests.TestHelpers /// protected virtual void SetupApplicationContext() { - var evtMsgs = new TransientMessagesFactory(); + var evtMsgs = new TransientEventMessagesFactory(); ApplicationContext.Current = new ApplicationContext( //assign the db context new DatabaseContext(new DefaultDatabaseFactory( diff --git a/src/Umbraco.Web/DefaultHttpContextAccessor.cs b/src/Umbraco.Web/DefaultHttpContextAccessor.cs deleted file mode 100644 index 121c90e1af..0000000000 --- a/src/Umbraco.Web/DefaultHttpContextAccessor.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Web; - -namespace Umbraco.Web -{ - internal class DefaultHttpContextAccessor : IHttpContextAccessor - { - private readonly Func _httpContext; - - public DefaultHttpContextAccessor(Func httpContext) - { - _httpContext = httpContext; - } - - public HttpContextBase Value - { - get { return _httpContext(); } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/IHttpContextAccessor.cs b/src/Umbraco.Web/IHttpContextAccessor.cs deleted file mode 100644 index 068783725a..0000000000 --- a/src/Umbraco.Web/IHttpContextAccessor.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Web; - -namespace Umbraco.Web -{ - /// - /// Used to retrieve the HttpContext - /// - /// - /// NOTE: This has a singleton lifespan - /// - public interface IHttpContextAccessor - { - HttpContextBase Value { get; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/RequestLifespanMessagesFactory.cs b/src/Umbraco.Web/RequestLifespanMessagesFactory.cs deleted file mode 100644 index 26ac3bd5df..0000000000 --- a/src/Umbraco.Web/RequestLifespanMessagesFactory.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using Umbraco.Core.Events; - -namespace Umbraco.Web -{ - /// - /// Stores the instance of EventMessages in the current request so all events will share the same instance - /// - internal class RequestLifespanMessagesFactory : IEventMessagesFactory - { - private readonly IHttpContextAccessor _httpAccessor; - - public RequestLifespanMessagesFactory(IHttpContextAccessor httpAccessor) - { - if (httpAccessor == null) throw new ArgumentNullException("httpAccessor"); - _httpAccessor = httpAccessor; - } - - public EventMessages Get() - { - if (_httpAccessor.Value.Items[typeof (RequestLifespanMessagesFactory).Name] == null) - { - _httpAccessor.Value.Items[typeof(RequestLifespanMessagesFactory).Name] = new EventMessages(); - } - return (EventMessages)_httpAccessor.Value.Items[typeof (RequestLifespanMessagesFactory).Name]; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/ScopeContextEventMessagesFactory.cs b/src/Umbraco.Web/ScopeContextEventMessagesFactory.cs new file mode 100644 index 0000000000..66ee150b55 --- /dev/null +++ b/src/Umbraco.Web/ScopeContextEventMessagesFactory.cs @@ -0,0 +1,34 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Events; + +namespace Umbraco.Web +{ + /// + /// Stores the instance of EventMessages in the current scope context so all events will share the same instance. + /// + internal class ScopeContextEventMessagesFactory : IEventMessagesFactory + { + private readonly IScopeContextAdapter _scopeContextAdapter; + private const string ContextKey = nameof(ScopeContextEventMessagesFactory); + + public ScopeContextEventMessagesFactory(IScopeContextAdapter scopeContextAdapter) + { + if (scopeContextAdapter == null) throw new ArgumentNullException(nameof(scopeContextAdapter)); + _scopeContextAdapter = scopeContextAdapter; + } + + public EventMessages Get() + { + var evtMsgs = (EventMessages) _scopeContextAdapter.Get(ContextKey); + if (evtMsgs == null) + _scopeContextAdapter.Set(ContextKey, evtMsgs = new EventMessages()); + return evtMsgs; + } + + public EventMessages GetOrDefault() + { + return (EventMessages) _scopeContextAdapter.Get(ContextKey); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/SingletonHttpContextAccessor.cs b/src/Umbraco.Web/SingletonHttpContextAccessor.cs deleted file mode 100644 index cdeafa48e1..0000000000 --- a/src/Umbraco.Web/SingletonHttpContextAccessor.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Web; - -namespace Umbraco.Web -{ - internal class SingletonHttpContextAccessor : IHttpContextAccessor - { - public HttpContextBase Value - { - get { return new HttpContextWrapper(HttpContext.Current); } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index c8a70ba6e2..41c9338134 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -323,14 +323,12 @@ - - @@ -418,7 +416,7 @@ - + @@ -435,7 +433,6 @@ - diff --git a/src/Umbraco.Web/Umbraco.Web.csproj.DotSettings b/src/Umbraco.Web/Umbraco.Web.csproj.DotSettings index 662f95686e..73e96563f9 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj.DotSettings +++ b/src/Umbraco.Web/Umbraco.Web.csproj.DotSettings @@ -1,2 +1,2 @@  - CSharp50 \ No newline at end of file + CSharp60 \ No newline at end of file diff --git a/src/Umbraco.Web/UmbracoContextExtensions.cs b/src/Umbraco.Web/UmbracoContextExtensions.cs index 8cd38df6d1..42d8a5cfde 100644 --- a/src/Umbraco.Web/UmbracoContextExtensions.cs +++ b/src/Umbraco.Web/UmbracoContextExtensions.cs @@ -13,6 +13,9 @@ namespace Umbraco.Web /// public static class UmbracoContextExtensions { + // fixme - this class is generally ugly now that we have proper IoC + // the current umbraco context belongs to the ScopeContext not the HttpContext + /// /// tries to get the Umbraco context from the HttpContext /// @@ -53,10 +56,11 @@ namespace Umbraco.Web /// public static EventMessages GetCurrentEventMessages(this UmbracoContext umbracoContext) { - var msgs = umbracoContext.HttpContext.Items[typeof (RequestLifespanMessagesFactory).Name]; - if (msgs == null) return null; - return (EventMessages) msgs; + // fixme - this is ugly + // the event messages factory should be injected / supplied by the container to whoever needs it! + var scopeContextAdapter = new DefaultScopeContextAdapter(); + var eventMessagesFactory = new ScopeContextEventMessagesFactory(scopeContextAdapter); + return eventMessagesFactory.GetOrDefault(); } - } } diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 545d7d7e24..1486d77774 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -338,7 +338,6 @@ namespace Umbraco.Web //no need to declare as per request, it's lifetime is already managed as a singleton container.Register(factory => new HttpContextWrapper(HttpContext.Current)); - container.RegisterSingleton(); container.RegisterSingleton(); container.RegisterSingleton(factory => new PublishedContentCache()); container.RegisterSingleton(); @@ -348,7 +347,7 @@ namespace Umbraco.Web container.RegisterSingleton(); //Replace services: - container.Register(); + container.Register(); container.RegisterSingleton(); container.RegisterSingleton(); } From ddf38407d89db077eccd58a187095f94fd096b12 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 18 May 2016 10:55:19 +0200 Subject: [PATCH 03/10] U4-4847 Refactor ContentService (#1266) * U4-4748 - refactor Content-, Media- and MemberTypeRepository * Cleanup Attempt * Cleanup OperationStatus * U4-4748 - refactor Content-, Media- and MemberTypeService * U4-4748 - cleanup locking * U4-4748 - refactor Content-, Media- and MemberRepository * U4-4748 - refactor ContentService (in progress) * U4-4748 - all unit of work must be completed * U4-4748 - refactor locks, fix tests * U4-4748 - deal with fixmes * U4-4748 - lock table migration * Update UmbracoVersion * Fix AuthorizeUpgrade * U4-4748 - cleanup+bugfix lock objects * U4-4748 - bugfix * updates a string interpolation --- .../{Attempt{T}.cs => AttemptOfT.cs} | 300 +- src/Umbraco.Core/Constants-ObjectTypes.cs | 15 +- src/Umbraco.Core/Constants-System.cs | 7 +- .../ServicesCompositionRoot.cs | 13 +- .../Events/RecycleBinEventArgs.cs | 19 + src/Umbraco.Core/IO/IOHelper.cs | 62 +- src/Umbraco.Core/Models/Content.cs | 70 +- src/Umbraco.Core/Models/ContentBase.cs | 12 +- src/Umbraco.Core/Models/ContentExtensions.cs | 332 +- src/Umbraco.Core/Models/ContentType.cs | 26 - src/Umbraco.Core/Models/ContentTypeBase.cs | 21 + .../Models/ContentTypeExtensions.cs | 4 +- src/Umbraco.Core/Models/IContentBase.cs | 7 - src/Umbraco.Core/Models/Media.cs | 2 +- src/Umbraco.Core/Models/Member.cs | 5 - .../PublishedContent/PublishedContentType.cs | 4 +- src/Umbraco.Core/Models/PublishedState.cs | 49 +- src/Umbraco.Core/Models/Rdbms/LockDto.cs | 24 + .../Persistence/Constants-Locks.cs | 18 + .../Persistence/Factories/ContentFactory.cs | 1 - .../Migrations/Initial/BaseDataCreation.cs | 30 +- .../Initial/DatabaseSchemaCreation.cs | 3 +- .../TargetVersionEight/AddLockObjects.cs | 48 + .../TargetVersionEight/AddLockTable.cs | 31 + .../AddServerRegistrationColumnsAndLock.cs | 4 +- .../Repositories/ContentRepository.cs | 536 ++-- .../Repositories/ContentTypeRepository.cs | 87 +- ...sitory.cs => ContentTypeRepositoryBase.cs} | 2504 +++++++-------- .../DataTypeDefinitionRepository.cs | 12 +- .../Interfaces/IContentRepository.cs | 9 +- .../IContentTypeCompositionRepository.cs | 11 - .../Interfaces/IContentTypeRepository.cs | 4 +- .../Interfaces/IContentTypeRepositoryBase.cs | 23 + .../Interfaces/IMediaTypeRepository.cs | 12 +- .../Interfaces/IMemberTypeRepository.cs | 9 +- .../IServerRegistrationRepository.cs | 3 - .../Repositories/MediaRepository.cs | 248 +- .../Repositories/MediaTypeRepository.cs | 103 +- .../Repositories/MemberRepository.cs | 247 +- .../Repositories/MemberTypeRepository.cs | 97 +- .../ServerRegistrationRepository.cs | 11 - .../Repositories/VersionableRepositoryBase.cs | 12 +- .../SqlSyntax/ISqlSyntaxProvider.cs | 1 + .../SqlSyntax/MySqlSyntaxProvider.cs | 2 +- .../SqlSyntax/SqlCeSyntaxProvider.cs | 12 +- .../SqlSyntax/SqlSyntaxProviderBase.cs | 7 +- .../UnitOfWork/IDatabaseUnitOfWork.cs | 4 +- .../Persistence/UnitOfWork/IUnitOfWork.cs | 3 + .../Persistence/UnitOfWork/NPocoUnitOfWork.cs | 16 +- src/Umbraco.Core/Publishing/PublishStatus.cs | 42 +- .../Publishing/PublishStatusType.cs | 59 +- .../Publishing/PublishingStrategy.cs | 469 --- .../Publishing/UnPublishStatus.cs | 34 +- src/Umbraco.Core/ServiceContextExtensions.cs | 21 + src/Umbraco.Core/Services/AuditService.cs | 4 + src/Umbraco.Core/Services/ContentService.cs | 2693 +++++++++-------- .../Services/ContentTypeService.cs | 1407 +-------- .../Services/ContentTypeServiceBase.cs | 903 +++++- src/Umbraco.Core/Services/DataTypeService.cs | 116 +- src/Umbraco.Core/Services/DomainService.cs | 28 +- src/Umbraco.Core/Services/EntityService.cs | 82 +- .../Services/EntityXmlSerializer.cs | 2 +- .../Services/ExternalLoginService.cs | 8 +- src/Umbraco.Core/Services/FileService.cs | 127 +- .../Services/IContentTypeService.cs | 261 +- .../Services/IContentTypeServiceBase.cs | 52 + src/Umbraco.Core/Services/IDataTypeService.cs | 2 +- src/Umbraco.Core/Services/IFileService.cs | 2 +- .../Services/IMediaTypeService.cs | 10 + .../Services/IMemberTypeService.cs | 5 +- .../Services/IPublicAccessService.cs | 2 +- .../Services/LocalizationService.cs | 33 +- src/Umbraco.Core/Services/MacroService.cs | 14 +- src/Umbraco.Core/Services/MediaService.cs | 97 +- src/Umbraco.Core/Services/MediaTypeService.cs | 57 + .../Services/MemberGroupService.cs | 12 +- src/Umbraco.Core/Services/MemberService.cs | 133 +- .../Services/MemberTypeService.cs | 218 +- .../Services/MigrationEntryService.cs | 8 +- .../Services/NotificationService.cs | 19 +- src/Umbraco.Core/Services/OperationStatus.cs | 234 +- .../Services/OperationStatusType.cs | 55 +- src/Umbraco.Core/Services/PackagingService.cs | 31 +- .../Services/PublicAccessService.cs | 43 +- src/Umbraco.Core/Services/RelationService.cs | 69 +- .../Services/ServerRegistrationService.cs | 15 +- src/Umbraco.Core/Services/ServiceContext.cs | 11 +- src/Umbraco.Core/Services/TagService.cs | 64 +- src/Umbraco.Core/Services/TaskService.cs | 20 +- src/Umbraco.Core/Services/UserService.cs | 86 +- src/Umbraco.Core/Umbraco.Core.csproj | 15 +- .../Models/ContentExtensionsTests.cs | 683 ++++- src/Umbraco.Tests/Models/ContentTests.cs | 57 +- src/Umbraco.Tests/Models/MediaXmlTest.cs | 2 +- .../Repositories/ContentRepositoryTest.cs | 20 +- .../Repositories/MediaRepositoryTest.cs | 6 +- .../Persistence/UnitOfWorkTests.cs | 67 + .../PublishedContent/PublishedMediaTests.cs | 2 +- .../Publishing/PublishingStrategyTests.cs | 59 +- src/Umbraco.Tests/Services/BaseServiceTest.cs | 2 +- .../Services/ContentServicePerformanceTest.cs | 12 +- .../Services/ContentServiceTests.cs | 569 ++-- .../Services/ContentTypeServiceTests.cs | 84 +- .../Services/DataTypeServiceTests.cs | 2 +- .../Services/EntityServiceTests.cs | 6 +- .../Services/MediaServiceTests.cs | 6 +- .../Services/ThreadSafetyServiceTest.cs | 72 +- .../TestHelpers/BaseDatabaseFactoryTest.cs | 1 - .../TestHelpers/BaseUmbracoApplicationTest.cs | 1 - .../TestHelpers/TestObjects-Mocks.cs | 1 + src/Umbraco.Tests/TestHelpers/TestObjects.cs | 10 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../Umbraco/Views/AuthorizeUpgrade.cshtml | 3 +- .../umbraco/dialogs/ChangeDocType.aspx.cs | 4 +- .../Cache/CacheRefresherEventHandler.cs | 20 +- .../Cache/ContentTypeCacheRefresher.cs | 2 +- src/Umbraco.Web/Editors/ContentController.cs | 2 +- .../Editors/ContentTypeController.cs | 34 +- .../Editors/ContentTypeControllerBase.cs | 31 +- src/Umbraco.Web/Editors/EntityController.cs | 8 +- src/Umbraco.Web/Editors/MediaController.cs | 4 +- .../Editors/MediaTypeController.cs | 34 +- .../Editors/MemberTypeController.cs | 4 +- .../Editors/TemplateQueryController.cs | 2 +- .../Models/Mapping/ContentModelMapper.cs | 2 +- .../ContentTypeModelMapperExtensions.cs | 4 +- .../Mapping/LockedCompositionsResolver.cs | 4 +- src/Umbraco.Web/PublishedContentExtensions.cs | 4 +- src/Umbraco.Web/Services/SectionService.cs | 9 +- .../Trees/ContentTypeTreeController.cs | 4 +- .../WebApi/Binders/ContentItemBinder.cs | 2 +- .../WebApi/Binders/MediaItemBinder.cs | 2 +- .../WebServices/BulkPublishController.cs | 2 +- .../Packages/installedPackage.aspx.cs | 2 +- .../dialogs/exportDocumenttype.aspx.cs | 2 +- .../umbraco/dialogs/protectPage.aspx.cs | 4 +- src/umbraco.cms/businesslogic/ContentType.cs | 7 +- .../businesslogic/media/MediaType.cs | 12 +- .../businesslogic/web/DocumentType.cs | 6 +- 139 files changed, 7539 insertions(+), 6981 deletions(-) rename src/Umbraco.Core/{Attempt{T}.cs => AttemptOfT.cs} (63%) create mode 100644 src/Umbraco.Core/Models/Rdbms/LockDto.cs create mode 100644 src/Umbraco.Core/Persistence/Constants-Locks.cs create mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddLockObjects.cs create mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddLockTable.cs rename src/Umbraco.Core/Persistence/Repositories/{ContentTypeBaseRepository.cs => ContentTypeRepositoryBase.cs} (94%) delete mode 100644 src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeCompositionRepository.cs create mode 100644 src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepositoryBase.cs delete mode 100644 src/Umbraco.Core/Publishing/PublishingStrategy.cs create mode 100644 src/Umbraco.Core/ServiceContextExtensions.cs create mode 100644 src/Umbraco.Core/Services/IContentTypeServiceBase.cs create mode 100644 src/Umbraco.Core/Services/IMediaTypeService.cs create mode 100644 src/Umbraco.Core/Services/MediaTypeService.cs create mode 100644 src/Umbraco.Tests/Persistence/UnitOfWorkTests.cs diff --git a/src/Umbraco.Core/Attempt{T}.cs b/src/Umbraco.Core/AttemptOfT.cs similarity index 63% rename from src/Umbraco.Core/Attempt{T}.cs rename to src/Umbraco.Core/AttemptOfT.cs index fc8fa3dcc9..d9e2abb186 100644 --- a/src/Umbraco.Core/Attempt{T}.cs +++ b/src/Umbraco.Core/AttemptOfT.cs @@ -1,174 +1,128 @@ -using System; -using Umbraco.Core.Dynamics; - -namespace Umbraco.Core -{ - /// - /// Represents the result of an operation attempt. - /// - /// The type of the attempted operation result. - [Serializable] - public struct Attempt - { - private readonly bool _success; - private readonly T _result; - private readonly Exception _exception; - - /// - /// Gets a value indicating whether this was successful. - /// - public bool Success - { - get { return _success; } - } - - /// - /// Gets the exception associated with an unsuccessful attempt. - /// - public Exception Exception { get { return _exception; } } - - /// - /// Gets the exception associated with an unsuccessful attempt. - /// - /// Keep it for backward compatibility sake. - [Obsolete(".Error is obsolete, you should use .Exception instead.", false)] - public Exception Error { get { return _exception; } } - - /// - /// Gets the attempt result. - /// - public T Result - { - get { return _result; } - } - - // optimize, use a singleton failed attempt - private static readonly Attempt Failed = new Attempt(false, default(T), null); - - /// - /// Represents an unsuccessful attempt. - /// - /// Keep it for backward compatibility sake. - [Obsolete(".Failed is obsolete, you should use Attempt.Fail() instead.", false)] - public static readonly Attempt False = Failed; - - // private - use Succeed() or Fail() methods to create attempts - private Attempt(bool success, T result, Exception exception) - { - _success = success; - _result = result; - _exception = exception; - } - - /// - /// Initialize a new instance of the struct with a result. - /// - /// A value indicating whether the attempt is successful. - /// The result of the attempt. - /// Keep it for backward compatibility sake. - [Obsolete("Attempt ctors are obsolete, you should use Attempt.Succeed(), Attempt.Fail() or Attempt.If() instead.", false)] - public Attempt(bool success, T result) - : this(success, result, null) - { } - - /// - /// Initialize a new instance of the struct representing a failed attempt, with an exception. - /// - /// The exception causing the failure of the attempt. - /// Keep it for backward compatibility sake. - [Obsolete("Attempt ctors are obsolete, you should use Attempt.Succeed(), Attempt.Fail() or Attempt.If() instead.", false)] - public Attempt(Exception exception) - : this(false, default(T), exception) - { } - - /// - /// Creates a successful attempt. - /// - /// The successful attempt. - public static Attempt Succeed() - { - return new Attempt(true, default(T), null); - } - - /// - /// Creates a successful attempt with a result. - /// - /// The result of the attempt. - /// The successful attempt. - public static Attempt Succeed(T result) - { - return new Attempt(true, result, null); - } - - /// - /// Creates a failed attempt. - /// - /// The failed attempt. - public static Attempt Fail() - { - return Failed; - } - - /// - /// Creates a failed attempt with an exception. - /// - /// The exception causing the failure of the attempt. - /// The failed attempt. - public static Attempt Fail(Exception exception) - { - return new Attempt(false, default(T), exception); - } - - /// - /// Creates a failed attempt with a result. - /// - /// The result of the attempt. - /// The failed attempt. - public static Attempt Fail(T result) - { - return new Attempt(false, result, null); - } - - /// - /// Creates a failed attempt with a result and an exception. - /// - /// The result of the attempt. - /// The exception causing the failure of the attempt. - /// The failed attempt. - public static Attempt Fail(T result, Exception exception) - { - return new Attempt(false, result, exception); - } - - /// - /// Creates a successful or a failed attempt. - /// - /// A value indicating whether the attempt is successful. - /// The attempt. - public static Attempt SucceedIf(bool condition) - { - return condition ? new Attempt(true, default(T), null) : Failed; - } - - /// - /// Creates a successful or a failed attempt, with a result. - /// - /// A value indicating whether the attempt is successful. - /// The result of the attempt. - /// The attempt. - public static Attempt SucceedIf(bool condition, T result) - { - return new Attempt(condition, result, null); - } - - /// - /// Implicity operator to check if the attempt was successful without having to access the 'success' property - /// - /// - /// - public static implicit operator bool(Attempt a) - { - return a.Success; - } - } +using System; + +namespace Umbraco.Core +{ + /// + /// Represents the result of an operation attempt. + /// + /// The type of the attempted operation result. + [Serializable] + public struct Attempt + { + /// + /// Gets a value indicating whether this was successful. + /// + public bool Success { get; } + + /// + /// Gets the exception associated with an unsuccessful attempt. + /// + public Exception Exception { get; } + + /// + /// Gets the attempt result. + /// + public T Result { get; } + + // optimize, use a singleton failed attempt + private static readonly Attempt Failed = new Attempt(false, default(T), null); + + // private - use Succeed() or Fail() methods to create attempts + private Attempt(bool success, T result, Exception exception) + { + Success = success; + Result = result; + Exception = exception; + } + + /// + /// Creates a successful attempt. + /// + /// The successful attempt. + public static Attempt Succeed() + { + return new Attempt(true, default(T), null); + } + + /// + /// Creates a successful attempt with a result. + /// + /// The result of the attempt. + /// The successful attempt. + public static Attempt Succeed(T result) + { + return new Attempt(true, result, null); + } + + /// + /// Creates a failed attempt. + /// + /// The failed attempt. + public static Attempt Fail() + { + return Failed; + } + + /// + /// Creates a failed attempt with an exception. + /// + /// The exception causing the failure of the attempt. + /// The failed attempt. + public static Attempt Fail(Exception exception) + { + return new Attempt(false, default(T), exception); + } + + /// + /// Creates a failed attempt with a result. + /// + /// The result of the attempt. + /// The failed attempt. + public static Attempt Fail(T result) + { + return new Attempt(false, result, null); + } + + /// + /// Creates a failed attempt with a result and an exception. + /// + /// The result of the attempt. + /// The exception causing the failure of the attempt. + /// The failed attempt. + public static Attempt Fail(T result, Exception exception) + { + return new Attempt(false, result, exception); + } + + /// + /// Creates a successful or a failed attempt. + /// + /// A value indicating whether the attempt is successful. + /// The attempt. + public static Attempt SucceedIf(bool condition) + { + return condition ? new Attempt(true, default(T), null) : Failed; + } + + /// + /// Creates a successful or a failed attempt, with a result. + /// + /// A value indicating whether the attempt is successful. + /// The result of the attempt. + /// The attempt. + public static Attempt SucceedIf(bool condition, T result) + { + return new Attempt(condition, result, null); + } + + /// + /// Implicity operator to check if the attempt was successful without having to access the 'success' property + /// + /// + /// + public static implicit operator bool(Attempt a) + { + return a.Success; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Constants-ObjectTypes.cs b/src/Umbraco.Core/Constants-ObjectTypes.cs index 560cd4b306..6fd3f01fe2 100644 --- a/src/Umbraco.Core/Constants-ObjectTypes.cs +++ b/src/Umbraco.Core/Constants-ObjectTypes.cs @@ -69,6 +69,11 @@ namespace Umbraco.Core /// public const string Document = "C66BA18E-EAF3-4CFF-8A22-41B16D66A972"; + /// + /// Guid for a Document object. + /// + public static readonly Guid DocumentGuid = new Guid(Document); + /// /// Guid for a Document Type object. /// @@ -84,6 +89,11 @@ namespace Umbraco.Core /// public const string Media = "B796F64C-1F99-4FFB-B886-4BF4BC011A9C"; + /// + /// Guid for a Media object. + /// + public static readonly Guid MediaGuid = new Guid(Media); + /// /// Guid for the Media Recycle Bin. /// @@ -143,7 +153,10 @@ namespace Umbraco.Core /// public const string LockObject = "87A9F1FF-B1E4-4A25-BABB-465A4A47EC41"; - + /// + /// Guid for a Lock object. + /// + public static readonly Guid LockObjectGuid = new Guid(LockObject); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Constants-System.cs b/src/Umbraco.Core/Constants-System.cs index 82e3a1ff3f..4ac1ff9a70 100644 --- a/src/Umbraco.Core/Constants-System.cs +++ b/src/Umbraco.Core/Constants-System.cs @@ -25,9 +25,6 @@ public const int DefaultContentListViewDataTypeId = -95; public const int DefaultMediaListViewDataTypeId = -96; public const int DefaultMembersListViewDataTypeId = -97; - - // identifiers for lock objects - public const int ServersLock = -331; - } - } + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs b/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs index 4f74797f45..e6a100b3e2 100644 --- a/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs +++ b/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs @@ -19,34 +19,33 @@ namespace Umbraco.Core.DependencyInjection { public void Compose(IServiceRegistry container) { - container.RegisterSingleton(); - // register a transient messages factory, which will be replaced byt the web // boot manager when running in a web context container.Register(); - + //the context container.RegisterSingleton(); - + //now the services... container.RegisterSingleton(); container.RegisterSingleton(); container.RegisterSingleton(); container.RegisterSingleton(); - container.RegisterSingleton(); + container.RegisterSingleton(); container.RegisterSingleton(); container.RegisterSingleton(); container.RegisterSingleton(); container.RegisterSingleton(); container.RegisterSingleton(); container.RegisterSingleton(); + container.RegisterSingleton(); container.RegisterSingleton(); container.RegisterSingleton(); container.RegisterSingleton(); container.RegisterSingleton(); container.RegisterSingleton(); container.RegisterSingleton(); - container.RegisterSingleton(); + container.RegisterSingleton(); container.RegisterSingleton(); container.RegisterSingleton(); container.RegisterSingleton(); @@ -84,7 +83,7 @@ namespace Umbraco.Core.DependencyInjection factory.GetInstance>(), factory.GetInstance())); - //TODO: These are replaced in the web project - we need to declare them so that + //TODO: These are replaced in the web project - we need to declare them so that // something is wired up, just not sure this is very nice but will work for now. container.RegisterSingleton(); container.RegisterSingleton(); diff --git a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs index ca4bbd2719..7818156207 100644 --- a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs +++ b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs @@ -17,6 +17,16 @@ namespace Umbraco.Core.Events Files = new List(); } + public RecycleBinEventArgs(Guid nodeObjectType, bool emptiedSuccessfully) + : base(false) + { + AllPropertyData = new Dictionary>(); + NodeObjectType = nodeObjectType; + Ids = new int[0]; + RecycleBinEmptiedSuccessfully = emptiedSuccessfully; + Files = new List(); + } + public RecycleBinEventArgs(Guid nodeObjectType, Dictionary> allPropertyData) : base(true) { @@ -26,6 +36,15 @@ namespace Umbraco.Core.Events Files = new List(); } + public RecycleBinEventArgs(Guid nodeObjectType) + : base(true) + { + AllPropertyData = new Dictionary>(); + NodeObjectType = nodeObjectType; + Ids = new int[0]; + Files = new List(); + } + /// /// Backwards compatibility constructor /// diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index 4da43d35bb..60f72ed19c 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -4,8 +4,10 @@ using System.Globalization; using System.Reflection; using System.IO; using System.Configuration; +using System.Linq; using System.Web; using System.Text.RegularExpressions; +using System.Threading.Tasks; using System.Web.Hosting; using ICSharpCode.SharpZipLib.Zip; using Umbraco.Core.Configuration; @@ -306,7 +308,7 @@ namespace Umbraco.Core.IO var debugFolder = Path.Combine(binFolder, "debug"); if (Directory.Exists(debugFolder)) return debugFolder; -#endif +#endif var releaseFolder = Path.Combine(binFolder, "release"); if (Directory.Exists(releaseFolder)) return releaseFolder; @@ -341,7 +343,7 @@ namespace Umbraco.Core.IO public static void EnsurePathExists(string path) { - var absolutePath = IOHelper.MapPath(path); + var absolutePath = MapPath(path); if (Directory.Exists(absolutePath) == false) Directory.CreateDirectory(absolutePath); } @@ -349,14 +351,58 @@ namespace Umbraco.Core.IO public static void EnsureFileExists(string path, string contents) { var absolutePath = IOHelper.MapPath(path); - if (File.Exists(absolutePath) == false) + if (File.Exists(absolutePath)) return; + + using (var writer = File.CreateText(absolutePath)) { - using (var writer = File.CreateText(absolutePath)) - { - writer.Write(contents); - } + writer.Write(contents); } - } + + /// + /// Deletes all files passed in. + /// + /// + /// + /// + internal static bool DeleteFiles(IEnumerable files, Action onError = null) + { + //ensure duplicates are removed + files = files.Distinct(); + + var allsuccess = true; + + var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); + Parallel.ForEach(files, file => + { + try + { + if (file.IsNullOrWhiteSpace()) return; + + var relativeFilePath = fs.GetRelativePath(file); + if (fs.FileExists(relativeFilePath) == false) return; + + var parentDirectory = Path.GetDirectoryName(relativeFilePath); + + // don't want to delete the media folder if not using directories. + if (UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories && parentDirectory != fs.GetRelativePath("/")) + { + //issue U4-771: if there is a parent directory the recursive parameter should be true + fs.DeleteDirectory(parentDirectory, String.IsNullOrEmpty(parentDirectory) == false); + } + else + { + fs.DeleteFile(file, true); + } + } + catch (Exception e) + { + onError?.Invoke(file, e); + allsuccess = false; + } + }); + + return allsuccess; + } } } diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index b8e96c2793..be24049364 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -17,12 +17,14 @@ namespace Umbraco.Core.Models private IContentType _contentType; private ITemplate _template; private bool _published; + private bool? _publishedOriginal; private string _language; private DateTime? _releaseDate; private DateTime? _expireDate; private int _writer; private string _nodeName;//NOTE Once localization is introduced this will be the non-localized Node Name. private bool _permissionsChanged; + /// /// Constructor for creating a Content object /// @@ -31,8 +33,7 @@ namespace Umbraco.Core.Models /// ContentType for the current Content object public Content(string name, IContent parent, IContentType contentType) : this(name, parent, contentType, new PropertyCollection()) - { - } + { } /// /// Constructor for creating a Content object @@ -47,6 +48,7 @@ namespace Umbraco.Core.Models Mandate.ParameterNotNull(contentType, "contentType"); _contentType = contentType; + PublishedState = PublishedState.Unpublished; } /// @@ -57,8 +59,7 @@ namespace Umbraco.Core.Models /// ContentType for the current Content object public Content(string name, int parentId, IContentType contentType) : this(name, parentId, contentType, new PropertyCollection()) - { - } + { } /// /// Constructor for creating a Content object @@ -73,6 +74,7 @@ namespace Umbraco.Core.Models Mandate.ParameterNotNull(contentType, "contentType"); _contentType = contentType; + PublishedState = PublishedState.Unpublished; } private static readonly PropertyInfo TemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.Template); @@ -95,7 +97,13 @@ namespace Umbraco.Core.Models [DataMember] public virtual ITemplate Template { - get { return _template; } + get + { + if (_template == null) + return _contentType.DefaultTemplate; + + return _template; + } set { SetPropertyValueAndDetectChanges(o => @@ -146,11 +154,19 @@ namespace Umbraco.Core.Models SetPropertyValueAndDetectChanges(o => { _published = value; + _publishedOriginal = _publishedOriginal ?? _published; + PublishedState = _published ? PublishedState.Published : PublishedState.Unpublished; return _published; }, _published, PublishedSelector); } } + [IgnoreDataMember] + public bool PublishedOriginal + { + get { return _publishedOriginal ?? false; } + } + /// /// Language of the data contained within this Content object. /// @@ -305,12 +321,14 @@ namespace Umbraco.Core.Models /// public void ChangePublishedState(PublishedState state) { - Published = state == PublishedState.Published; + if (state == PublishedState.Published || state == PublishedState.Unpublished) + throw new ArgumentException("Invalid state."); + Published = state == PublishedState.Publishing; PublishedState = state; } [DataMember] - internal PublishedState PublishedState { get; set; } + internal PublishedState PublishedState { get; private set; } /// /// Gets or sets the unique identifier of the published version, if any. @@ -322,24 +340,26 @@ namespace Umbraco.Core.Models /// Gets a value indicating whether the content has a published version. /// public bool HasPublishedVersion { get { return PublishedVersionGuid != default(Guid); } } - - /// - /// Changes the Trashed state of the content object - /// - /// Boolean indicating whether content is trashed (true) or not trashed (false) - /// - public override void ChangeTrashedState(bool isTrashed, int parentId = -20) - { - Trashed = isTrashed; - ParentId = parentId; - - //If the content is trashed and is published it should be marked as unpublished - if (isTrashed && Published) - { - ChangePublishedState(PublishedState.Unpublished); - } - } + public override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) + { + base.ResetDirtyProperties(rememberPreviouslyChangedProperties); + + // take care of the published state + switch (PublishedState) + { + case PublishedState.Saving: + case PublishedState.Unpublishing: + PublishedState = PublishedState.Unpublished; + break; + case PublishedState.Publishing: + PublishedState = PublishedState.Published; + break; + } + + _publishedOriginal = _published; + } + /// /// Method to call when Entity is being updated /// @@ -377,6 +397,8 @@ namespace Umbraco.Core.Models property.Version = clone.Version; } + clone.PublishedVersionGuid = Guid.Empty; + return clone; } diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index b3d0f693d9..aff5dc35a3 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -118,6 +118,16 @@ namespace Umbraco.Core.Models } } + /// + /// Sets the ParentId from the lazy integer id + /// + /// Id of the Parent + internal protected void SetLazyParentId(Lazy parentId) + { + _parentId = parentId; + OnPropertyChanged(ParentIdSelector); + } + /// /// Gets or sets the name of the entity /// @@ -483,8 +493,6 @@ namespace Umbraco.Core.Models get { return _lastInvalidProperties; } } - public abstract void ChangeTrashedState(bool isTrashed, int parentId = -20); - #region Dirty property handling /// diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs index e91996e32a..ceed239d53 100644 --- a/src/Umbraco.Core/Models/ContentExtensions.cs +++ b/src/Umbraco.Core/Models/ContentExtensions.cs @@ -1,13 +1,10 @@ using System; using System.Collections.Generic; using System.Drawing; -using System.Drawing.Drawing2D; -using System.Drawing.Imaging; using System.Globalization; using System.IO; using System.Linq; using System.Web; -using System.Xml; using System.Xml.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -17,9 +14,6 @@ using Umbraco.Core.IO; using Umbraco.Core.Media; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Strings; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Services; namespace Umbraco.Core.Models @@ -43,175 +37,184 @@ namespace Umbraco.Core.Models } /// - /// Determines if the item should be persisted at all + /// Determines whether the content should be persisted. /// - /// - /// - /// - /// In one particular case, a content item shouldn't be persisted: - /// * The item exists and is published - /// * A call to ContentService.Save is made - /// * The item has not been modified whatsoever apart from changing it's published status from published to saved - /// - /// In this case, there is no reason to make any database changes at all - /// + /// The content. + /// True is the content should be persisted, otherwise false. + /// See remarks in overload. internal static bool RequiresSaving(this IContent entity) { - var publishedState = ((Content)entity).PublishedState; - return RequiresSaving(entity, publishedState); + return RequiresSaving(entity, ((Content) entity).PublishedState); } /// - /// Determines if the item should be persisted at all + /// Determines whether the content should be persisted. /// - /// - /// - /// + /// The content. + /// The published state of the content. + /// True is the content should be persisted, otherwise false. /// - /// In one particular case, a content item shouldn't be persisted: - /// * The item exists and is published - /// * A call to ContentService.Save is made - /// * The item has not been modified whatsoever apart from changing it's published status from published to saved - /// - /// In this case, there is no reason to make any database changes at all + /// This is called by the repository when persisting an existing content, to + /// figure out whether it needs to persist the content at all. /// internal static bool RequiresSaving(this IContent entity, PublishedState publishedState) { - var publishedChanged = entity.IsPropertyDirty("Published") && publishedState != PublishedState.Unpublished; - //check if any user prop has changed - var propertyValueChanged = entity.IsAnyUserPropertyDirty(); - - //We need to know if any other property apart from Published was changed here - //don't create a new version if the published state has changed to 'Save' but no data has actually been changed - if (publishedChanged && entity.Published == false && propertyValueChanged == false) + // note: publishedState is always the entity's PublishedState except for tests + + var content = (Content) entity; + var userPropertyChanged = content.IsAnyUserPropertyDirty(); + var dirtyProps = content.GetDirtyProperties(); + //var contentPropertyChanged = content.IsEntityDirty(); + var contentPropertyChangedExceptPublished = dirtyProps.Any(x => x != "Published"); + + // we don't want to save (write to DB) if we are "saving" either a published content + // (.Saving) or an unpublished content (.Unpublished) and strictly nothing has changed + + var noSave = (publishedState == PublishedState.Saving || publishedState == PublishedState.Unpublished) + && userPropertyChanged == false + && contentPropertyChangedExceptPublished == false; + + return noSave == false; + } + + /// + /// Determines whether a new version of the content should be created. + /// + /// The content. + /// True if a new version should be created, otherwise false. + /// See remarks in overload. + internal static bool RequiresNewVersion(this IContent entity) + { + return RequiresNewVersion(entity, ((Content) entity).PublishedState); + } + + /// + /// Determines whether a new version of the content should be created. + /// + /// The content. + /// The published state of the content. + /// True if a new version should be created, otherwise false. + /// + /// This is called by the repository when persisting an existing content, to + /// figure out whether it needs to create a new version for that content. + /// A new version needs to be created when: + /// * The publish status is changed + /// * The language is changed + /// * A content property is changed (? why ?) + /// * The item is already published and is being published again and any property value is changed (to enable a rollback) + /// + internal static bool RequiresNewVersion(this IContent entity, PublishedState publishedState) + { + // note: publishedState is always the entity's PublishedState except for tests + + // read + // http://issues.umbraco.org/issue/U4-2589 (save & publish & creating new versions) + // http://issues.umbraco.org/issue/U4-3404 (pressing preview does save then preview) + // http://issues.umbraco.org/issue/U4-5510 (previewing & creating new versions) + // + // slightly modifying the rules to make more sense (marked with CHANGE) + // but should respect the result of the discussions in those issues + + // figure out whether .Language has changed + // this language stuff was an old POC and should be removed + var hasLanguageChanged = entity.IsPropertyDirty("Language"); + if (hasLanguageChanged) + return true; // language change => new version + + var content = (Content) entity; + //var contentPropertyChanged = content2.IsEntityDirty(); + var userPropertyChanged = content.IsAnyUserPropertyDirty(); + var dirtyProps = content.GetDirtyProperties(); + var contentPropertyChangedExceptPublished = dirtyProps.Any(x => x != "Published"); + var wasPublished = content.PublishedOriginal; + + switch (publishedState) { - //at this point we need to check if any non property value has changed that wasn't the published state - var changedProps = ((TracksChangesEntityBase)entity).GetDirtyProperties(); - if (changedProps.Any(x => x != "Published") == false) - { + case PublishedState.Publishing: + // changed state, publishing either a published or an unpublished version: + // DO create a new (published) version IF it was published already AND + // anything has changed, else can reuse the current version + return (contentPropertyChangedExceptPublished || userPropertyChanged) && wasPublished; + + case PublishedState.Unpublishing: + // changed state, unpublishing a published version: + // DO create a new (draft) version and preserve the (formerly) published + // version for rollback purposes IF the version that's being saved is the + // published version, else it's a draft that we can reuse + return wasPublished; + + case PublishedState.Saving: + // changed state, saving a published version: + // DO create a new (draft) version and preserve the published version IF + // anything has changed, else do NOT create a new version (pointless) + return contentPropertyChangedExceptPublished || userPropertyChanged; + + case PublishedState.Published: + // unchanged state, saving a published version: + // (can happen eg when moving content, never otherwise) + // do NOT create a new version as we're just saving after operations (eg + // move) that cannot be rolled back anyway - ensure that's really it + if (userPropertyChanged) + throw new InvalidOperationException("Invalid PublishedState \"Published\" with user property changes."); return false; - } + + case PublishedState.Unpublished: + // unchanged state, saving an unpublished version: + // do NOT create a new version for user property changes, + // BUT create a new version in case of content property changes, for + // rollback purposes + return contentPropertyChangedExceptPublished; + + default: + throw new NotSupportedException(); } - - return true; } /// - /// Determines if a new version should be created + /// Determines whether the database published flag should be cleared for versions + /// other than this content version. /// - /// - /// + /// The content. + /// True if the published flag should be cleared, otherwise false. + /// See remarks in overload. + internal static bool RequiresClearPublishedFlag(this IContent entity) + { + var publishedState = ((Content) entity).PublishedState; + var requiresNewVersion = entity.RequiresNewVersion(publishedState); + return entity.RequiresClearPublishedFlag(publishedState, requiresNewVersion); + } + + /// + /// Determines whether the database published flag should be cleared for versions + /// other than this content version. + /// + /// The content. + /// The published state of the content. + /// Indicates whether the content is a new version. + /// True if the published flag should be cleared, otherwise false. /// - /// A new version needs to be created when: - /// * The publish status is changed - /// * The language is changed - /// * The item is already published and is being published again and any property value is changed (to enable a rollback) + /// This is called by the repository when persisting an existing content, to + /// figure out whether it needs to clear the published flag for other versions. /// - internal static bool ShouldCreateNewVersion(this IContent entity) + internal static bool RequiresClearPublishedFlag(this IContent entity, PublishedState publishedState, bool isNewVersion) { - var publishedState = ((Content)entity).PublishedState; - return ShouldCreateNewVersion(entity, publishedState); - } + // note: publishedState is always the entity's PublishedState except for tests - /// - /// Returns a list of all dirty user defined properties - /// - /// - public static IEnumerable GetDirtyUserProperties(this IContentBase entity) - { - return entity.Properties.Where(x => x.IsDirty()).Select(x => x.Alias); - } - - public static bool IsAnyUserPropertyDirty(this IContentBase entity) - { - return entity.Properties.Any(x => x.IsDirty()); - } - - public static bool WasAnyUserPropertyDirty(this IContentBase entity) - { - return entity.Properties.Any(x => x.WasDirty()); - } - - /// - /// Determines if a new version should be created - /// - /// - /// - /// - /// - /// A new version needs to be created when: - /// * The publish status is changed - /// * The language is changed - /// * The item is already published and is being published again and any property value is changed (to enable a rollback) - /// - internal static bool ShouldCreateNewVersion(this IContent entity, PublishedState publishedState) - { - //check if the published state has changed or the language - var publishedChanged = entity.IsPropertyDirty("Published") && publishedState != PublishedState.Unpublished; - var langChanged = entity.IsPropertyDirty("Language"); - var contentChanged = publishedChanged || langChanged; - - //check if any user prop has changed - var propertyValueChanged = entity.IsAnyUserPropertyDirty(); - - //return true if published or language has changed - if (contentChanged) - { + // new, published version => everything else must be cleared + if (isNewVersion && entity.Published) return true; - } - //check if any content prop has changed - var contentDataChanged = ((Content)entity).IsEntityDirty(); + // if that entity was published then that entity has the flag and + // it does not need to be cleared for other versions + // NOT TRUE when unpublishing we create a NEW version + //var wasPublished = ((Content)entity).PublishedOriginal; + //if (wasPublished) + // return false; - //return true if the item is published and a property has changed or if any content property has changed - return (propertyValueChanged && publishedState == PublishedState.Published) || contentDataChanged; - } - - /// - /// Determines if the published db flag should be set to true for the current entity version and all other db - /// versions should have their flag set to false. - /// - /// - /// - /// - /// This is determined by: - /// * If a new version is being created and the entity is published - /// * If the published state has changed and the entity is published OR the entity has been un-published. - /// - internal static bool ShouldClearPublishedFlagForPreviousVersions(this IContent entity) - { - var publishedState = ((Content)entity).PublishedState; - return entity.ShouldClearPublishedFlagForPreviousVersions(publishedState, entity.ShouldCreateNewVersion(publishedState)); - } - - /// - /// Determines if the published db flag should be set to true for the current entity version and all other db - /// versions should have their flag set to false. - /// - /// - /// - /// - /// - /// - /// This is determined by: - /// * If a new version is being created and the entity is published - /// * If the published state has changed and the entity is published OR the entity has been un-published. - /// - internal static bool ShouldClearPublishedFlagForPreviousVersions(this IContent entity, PublishedState publishedState, bool isCreatingNewVersion) - { - if (isCreatingNewVersion && entity.Published) - { - return true; - } - - //If Published state has changed then previous versions should have their publish state reset. - //If state has been changed to unpublished the previous versions publish state should also be reset. - if (entity.IsPropertyDirty("Published") && (entity.Published || publishedState == PublishedState.Unpublished)) - { - return true; - } - - return false; + // clear whenever we are publishing or unpublishing + // publishing: because there might be a previously published version, which needs to be cleared + // unpublishing: same - we might be a saved version, not the published one, which needs to be cleared + return publishedState == PublishedState.Publishing || publishedState == PublishedState.Unpublishing; } /// @@ -312,9 +315,9 @@ namespace Umbraco.Core.Models return content.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Contains(recycleBinId.ToInvariantString()); } - + /// - /// Removes characters that are not valide XML characters from all entity properties + /// Removes characters that are not valide XML characters from all entity properties /// of type string. See: http://stackoverflow.com/a/961504/5018 /// /// @@ -454,7 +457,7 @@ namespace Umbraco.Core.Models /// The containing the file that will be uploaded public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value) { - // Ensure we get the filename without the path in IE in intranet mode + // Ensure we get the filename without the path in IE in intranet mode // http://stackoverflow.com/questions/382464/httppostedfile-filename-different-from-ie var fileName = value.FileName; if (fileName.LastIndexOf(@"\") > 0) @@ -597,7 +600,7 @@ namespace Umbraco.Core.Models #endregion #region User/Profile methods - + /// /// Gets the for the Creator of this media item. /// @@ -672,7 +675,7 @@ namespace Umbraco.Core.Models ///// ///// ///// - ///// The tags returned are only relavent for published content & saved media or members + ///// The tags returned are only relavent for published content & saved media or members ///// //public static IEnumerable GetTags(this IContentBase content, string propertyTypeAlias, string tagGroup = "default") //{ @@ -909,5 +912,24 @@ namespace Umbraco.Core.Models return ((PackagingService)(packagingService)).Export(member); } #endregion + + #region Dirty + + public static IEnumerable GetDirtyUserProperties(this IContentBase entity) + { + return entity.Properties.Where(x => x.IsDirty()).Select(x => x.Alias); + } + + public static bool IsAnyUserPropertyDirty(this IContentBase entity) + { + return entity.Properties.Any(x => x.IsDirty()); + } + + public static bool WasAnyUserPropertyDirty(this IContentBase entity) + { + return entity.Properties.Any(x => x.WasDirty()); + } + + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index 0926e48e31..d711cf5a1a 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -152,31 +152,5 @@ namespace Umbraco.Core.Models { return DeepCloneWithResetIdentities(alias); } - - /// - /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset - /// - /// - public IContentType DeepCloneWithResetIdentities(string alias) - { - var clone = (ContentType)DeepClone(); - clone.Alias = alias; - clone.Key = Guid.Empty; - foreach (var propertyGroup in clone.PropertyGroups) - { - propertyGroup.ResetIdentity(); - propertyGroup.ResetDirtyProperties(false); - } - foreach (var propertyType in clone.PropertyTypes) - { - propertyType.ResetIdentity(); - propertyType.ResetDirtyProperties(false); - } - - clone.ResetIdentity(); - clone.ResetDirtyProperties(false); - return clone; - } - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index a0305d2cfb..21ff40ce05 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -642,5 +642,26 @@ namespace Umbraco.Core.Models return clone; } + + public IContentType DeepCloneWithResetIdentities(string alias) + { + var clone = (ContentType)DeepClone(); + clone.Alias = alias; + clone.Key = Guid.Empty; + foreach (var propertyGroup in clone.PropertyGroups) + { + propertyGroup.ResetIdentity(); + propertyGroup.ResetDirtyProperties(false); + } + foreach (var propertyType in clone.PropertyTypes) + { + propertyType.ResetIdentity(); + propertyType.ResetDirtyProperties(false); + } + + clone.ResetIdentity(); + clone.ResetDirtyProperties(false); + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/ContentTypeExtensions.cs b/src/Umbraco.Core/Models/ContentTypeExtensions.cs index a47b430979..6301b46950 100644 --- a/src/Umbraco.Core/Models/ContentTypeExtensions.cs +++ b/src/Umbraco.Core/Models/ContentTypeExtensions.cs @@ -14,8 +14,8 @@ namespace Umbraco.Core.Models public static IEnumerable Descendants(this IContentTypeBase contentType) { var contentTypeService = ApplicationContext.Current.Services.ContentTypeService; - var descendants = contentTypeService.GetContentTypeChildren(contentType.Id) - .SelectRecursive(type => contentTypeService.GetContentTypeChildren(type.Id)); + var descendants = contentTypeService.GetChildren(contentType.Id) + .SelectRecursive(type => contentTypeService.GetChildren(type.Id)); return descendants; } diff --git a/src/Umbraco.Core/Models/IContentBase.cs b/src/Umbraco.Core/Models/IContentBase.cs index 7d2075cb65..d7e26808c8 100644 --- a/src/Umbraco.Core/Models/IContentBase.cs +++ b/src/Umbraco.Core/Models/IContentBase.cs @@ -72,12 +72,5 @@ namespace Umbraco.Core.Models /// /// True if content is valid otherwise false bool IsValid(); - - /// - /// Changes the Trashed state of the content object - /// - /// Boolean indicating whether content is trashed (true) or not trashed (false) - /// - void ChangeTrashedState(bool isTrashed, int parentId = -20); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Media.cs b/src/Umbraco.Core/Models/Media.cs index a7e794a400..d91fe9fdf8 100644 --- a/src/Umbraco.Core/Models/Media.cs +++ b/src/Umbraco.Core/Models/Media.cs @@ -112,7 +112,7 @@ namespace Umbraco.Core.Models /// /// Boolean indicating whether content is trashed (true) or not trashed (false) /// - public override void ChangeTrashedState(bool isTrashed, int parentId = -20) + public void ChangeTrashedState(bool isTrashed, int parentId = -20) { Trashed = isTrashed; //The Media Recycle Bin Id is -21 so we correct that here diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 70c21e4307..ace6c2bf67 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -522,11 +522,6 @@ namespace Umbraco.Core.Models get { return _contentType; } } - public override void ChangeTrashedState(bool isTrashed, int parentId = -20) - { - throw new NotSupportedException("Members can't be trashed as no Recycle Bin exists, so use of this method is invalid"); - } - /* Internal experiment - only used for mapping queries. * Adding these to have first level properties instead of the Properties collection. */ diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index d05960b08f..9baf0c1024 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -166,10 +166,10 @@ namespace Umbraco.Core.Models.PublishedContent switch (itemType) { case PublishedItemType.Content: - contentType = ApplicationContext.Current.Services.ContentTypeService.GetContentType(alias); + contentType = ApplicationContext.Current.Services.ContentTypeService.Get(alias); break; case PublishedItemType.Media: - contentType = ApplicationContext.Current.Services.ContentTypeService.GetMediaType(alias); + contentType = ApplicationContext.Current.Services.MediaTypeService.Get(alias); break; case PublishedItemType.Member: contentType = ApplicationContext.Current.Services.MemberTypeService.Get(alias); diff --git a/src/Umbraco.Core/Models/PublishedState.cs b/src/Umbraco.Core/Models/PublishedState.cs index 4469eba9cc..d169262802 100644 --- a/src/Umbraco.Core/Models/PublishedState.cs +++ b/src/Umbraco.Core/Models/PublishedState.cs @@ -1,9 +1,54 @@ -namespace Umbraco.Core.Models +using System; + +namespace Umbraco.Core.Models { + /// + /// The IContent states of a content version. + /// public enum PublishedState { + // when a content version is loaded, its state is one of those two: + + /// + /// The version is published. + /// Published, + + /// + /// The version is not published. + /// + /// Also: the version is being saved, in order to register changes + /// made to an unpublished version of the content. Unpublished, - Saved + + // legacy - remove + [Obsolete("kill!", true)] + Saved, + + // when it is saved, its state can also be one of those: + + /// + /// The version is being saved, in order to register changes made to a published content. + /// + /// The Saving state is transitional. Once the version + /// is saved, its state changes to Unpublished. + Saving, + + /// + /// The version is being saved, in order to publish the content. + /// + /// The Publishing state is transitional. Once the version + /// is saved, its state changes to Published. The content is published, + /// and all other versions are unpublished. + Publishing, + + /// + /// The version is being saved, in order to unpublish the content. + /// + /// The Unpublishing state is transitional. Once the version + /// is saved, its state changes to Unpublished. The content and all + /// other versions are unpublished. + Unpublishing + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/LockDto.cs b/src/Umbraco.Core/Models/Rdbms/LockDto.cs new file mode 100644 index 0000000000..5227c15c66 --- /dev/null +++ b/src/Umbraco.Core/Models/Rdbms/LockDto.cs @@ -0,0 +1,24 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Models.Rdbms +{ + [TableName("umbracoLock")] + [PrimaryKey("id")] + [ExplicitColumns] + internal class LockDto + { + [Column("id")] + [PrimaryKeyColumn(Name = "PK_umbracoLock")] + public int Id { get; set; } + + [Column("value")] + [NullSetting(NullSetting = NullSettings.NotNull)] + public int Value { get; set; } = 1; + + [Column("name")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Length(64)] + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Constants-Locks.cs b/src/Umbraco.Core/Persistence/Constants-Locks.cs new file mode 100644 index 0000000000..89d7cf7391 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Constants-Locks.cs @@ -0,0 +1,18 @@ +// ReSharper disable once CheckNamespace +namespace Umbraco.Core +{ + static partial class Constants + { + public static class Locks + { + public const int Servers = -331; + public const int ContentTypes = -332; + public const int ContentTree = -333; + public const int MediaTree = -334; + public const int MemberTree = -335; + public const int MediaTypes = -336; + public const int MemberTypes = -337; + public const int Domains = -338; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs index d21aea33e6..49445ffc9d 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs @@ -48,7 +48,6 @@ namespace Umbraco.Core.Persistence.Factories ExpireDate = dto.ExpiresDate.HasValue ? dto.ExpiresDate.Value : (DateTime?)null, ReleaseDate = dto.ReleaseDate.HasValue ? dto.ReleaseDate.Value : (DateTime?)null, Version = dto.ContentVersionDto.VersionId, - PublishedState = dto.Published ? PublishedState.Published : PublishedState.Unpublished, PublishedVersionGuid = dto.DocumentPublishedReadOnlyDto == null ? default(Guid) : dto.DocumentPublishedReadOnlyDto.VersionId }; //on initial construction we don't want to have dirty properties tracked diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index 66d9c8be0c..76f29f3350 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -27,14 +27,19 @@ namespace Umbraco.Core.Persistence.Migrations.Initial /// Name of the table to create base data for public void InitializeBaseData(string tableName) { - _logger.Info(string.Format("Creating data in table {0}", tableName)); + _logger.Info($"Creating data in table {tableName}"); if(tableName.Equals("umbracoNode")) { - CreateUmbracNodeData(); + CreateUmbracoNodeData(); } - if(tableName.Equals("cmsContentType")) + if (tableName.Equals("umbracoLock")) + { + CreateUmbracoLockData(); + } + + if (tableName.Equals("cmsContentType")) { CreateCmsContentTypeData(); } @@ -102,7 +107,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial _logger.Info(string.Format("Done creating data in table {0}", tableName)); } - private void CreateUmbracNodeData() + private void CreateUmbracoNodeData() { _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -1, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1", SortOrder = 0, UniqueId = new Guid("916724a5-173d-4619-b97e-b9de133dd6f5"), Text = "SYSTEM DATA: umbraco master root", NodeObjectType = new Guid(Constants.ObjectTypes.SystemRoot), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -20, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1,-20", SortOrder = 0, UniqueId = new Guid("0F582A79-1E41-4CF0-BFA0-76340651891A"), Text = "Recycle Bin", NodeObjectType = new Guid(Constants.ObjectTypes.ContentRecycleBin), CreateDate = DateTime.Now }); @@ -135,15 +140,28 @@ namespace Umbraco.Core.Persistence.Migrations.Initial _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1043, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1043", SortOrder = 2, UniqueId = new Guid("1df9f033-e6d4-451f-b8d2-e0cbc50a836f"), Text = "Image Cropper", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1044, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1044", SortOrder = 0, UniqueId = new Guid("d59be02f-1df9-4228-aa1e-01917d806cda"), Text = Constants.Conventions.MemberTypes.DefaultAlias, NodeObjectType = new Guid(Constants.ObjectTypes.MemberType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1045, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1045", SortOrder = 2, UniqueId = new Guid("7E3962CC-CE20-4FFC-B661-5897A894BA7E"), Text = "Multiple Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - + //TODO: We're not creating these for 7.0 //_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1039, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1039", SortOrder = 2, UniqueId = new Guid("06f349a9-c949-4b6a-8660-59c10451af42"), Text = "Ultimate Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); //_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1038, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1038", SortOrder = 2, UniqueId = new Guid("1251c96c-185c-4e9b-93f4-b48205573cbd"), Text = "Simple Editor", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - + //_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1042, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1042", SortOrder = 2, UniqueId = new Guid("0a452bd5-83f9-4bc3-8403-1286e13fb77e"), Text = "Macro Container", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); } + private void CreateUmbracoLockData() + { + // all lock objects + _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.Servers, Name = "Servers" }); + _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.ContentTypes, Name = "ContentTypes" }); + _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.ContentTree, Name = "ContentTree" }); + _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.MediaTypes, Name = "MediaTypes" }); + _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.MediaTree, Name = "MediaTree" }); + _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.MemberTypes, Name = "MemberTypes" }); + _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.MemberTree, Name = "MemberTree" }); + _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.Domains, Name = "Domains" }); + } + private void CreateCmsContentTypeData() { _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Constants.Conventions.MediaTypes.Folder, Icon = "icon-folder", Thumbnail = "icon-folder", IsContainer = false, AllowAtRoot = true }); diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs index 9891476b40..31e3152f04 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs @@ -84,7 +84,8 @@ namespace Umbraco.Core.Persistence.Migrations.Initial {44, typeof (ExternalLoginDto)}, {45, typeof (MigrationDto)}, {46, typeof (UmbracoDeployChecksumDto)}, - {47, typeof (UmbracoDeployDependencyDto)} + {47, typeof (UmbracoDeployDependencyDto)}, + {48, typeof (LockDto) } }; #endregion diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddLockObjects.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddLockObjects.cs new file mode 100644 index 0000000000..238196cc40 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddLockObjects.cs @@ -0,0 +1,48 @@ +using System; +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionEight +{ + [Migration("8.0.0", 101, GlobalSettings.UmbracoMigrationName)] + public class AddLockObjects : MigrationBase + { + public AddLockObjects(ILogger logger) + : base(logger) + { } + + public override void Up() + { + // some may already exist, just ensure everything we need is here + EnsureLockObject(Constants.Locks.Servers, "Servers"); + EnsureLockObject(Constants.Locks.ContentTypes, "ContentTypes"); + EnsureLockObject(Constants.Locks.ContentTree, "ContentTree"); + EnsureLockObject(Constants.Locks.MediaTree, "MediaTree"); + EnsureLockObject(Constants.Locks.MemberTree, "MemberTree"); + EnsureLockObject(Constants.Locks.MediaTypes, "MediaTypes"); + EnsureLockObject(Constants.Locks.MemberTypes, "MemberTypes"); + EnsureLockObject(Constants.Locks.Domains, "Domains"); + } + + public override void Down() + { + // not implemented + } + + private void EnsureLockObject(int id, string name) + { + Execute.Code(db => + { + var exists = db.Exists(id); + if (exists) return string.Empty; + // be safe: delete old umbracoNode lock objects if any + db.Execute($"DELETE FROM umbracoNode WHERE id={id};"); + // then create umbracoLock object + db.Execute($"INSERT umbracoLock (id, name, value) VALUES ({id}, '{name}', 1);"); + return string.Empty; + }); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddLockTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddLockTable.cs new file mode 100644 index 0000000000..ceff5e3537 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddLockTable.cs @@ -0,0 +1,31 @@ +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionEight +{ + [Migration("8.0.0", 100, GlobalSettings.UmbracoMigrationName)] + public class AddLockTable : MigrationBase + { + public AddLockTable(ILogger logger) + : base(logger) + { } + + public override void Up() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + if (tables.InvariantContains("umbracoLock") == false) + { + Create.Table("umbracoLock") + .WithColumn("id").AsInt32().PrimaryKey("PK_umbracoLock") + .WithColumn("value").AsInt32().NotNullable() + .WithColumn("name").AsString(64).NotNullable(); + } + } + + public override void Down() + { + // not implemented + } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs index d605151249..c73201850c 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs @@ -23,7 +23,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe Create.Column("isMaster").OnTable("umbracoServer").AsBoolean().NotNullable().WithDefaultValue(0); } - EnsureLockObject(Constants.System.ServersLock, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers"); + EnsureLockObject(Constants.Locks.Servers, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers"); } public override void Down() @@ -50,7 +50,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe sortOrder = 0, uniqueId = new Guid(uniqueId), text = text, - nodeObjectType = new Guid(Constants.ObjectTypes.LockObject), + nodeObjectType = Constants.ObjectTypes.LockObjectGuid, createDate = DateTime.Now }); } diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index b3808ef804..7d4579611e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -1,17 +1,9 @@ using System; using System.Collections.Generic; -using System.Data; using System.Globalization; using System.Linq; -using System.Linq.Expressions; -using System.Net.Http.Headers; -using System.Text; using System.Xml.Linq; using NPoco; -using StackExchange.Profiling.Helpers.Dapper; -using Umbraco.Core.Configuration; -using Umbraco.Core.Dynamics; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -30,7 +22,7 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { /// - /// Represents a repository for doing CRUD operations for + /// Represents a repository for doing CRUD operations for . /// internal class ContentRepository : RecycleBinRepository, IContentRepository { @@ -44,9 +36,9 @@ namespace Umbraco.Core.Persistence.Repositories public ContentRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection contentSection, IMappingResolver mappingResolver) : base(work, cacheHelper, logger, contentSection, mappingResolver) { - if (contentTypeRepository == null) throw new ArgumentNullException("contentTypeRepository"); - if (templateRepository == null) throw new ArgumentNullException("templateRepository"); - if (tagRepository == null) throw new ArgumentNullException("tagRepository"); + if (contentTypeRepository == null) throw new ArgumentNullException(nameof(contentTypeRepository)); + if (templateRepository == null) throw new ArgumentNullException(nameof(templateRepository)); + if (tagRepository == null) throw new ArgumentNullException(nameof(tagRepository)); _contentTypeRepository = contentTypeRepository; _templateRepository = templateRepository; _tagRepository = tagRepository; @@ -73,7 +65,7 @@ namespace Umbraco.Core.Persistence.Repositories if (dto == null) return null; - var content = CreateContentFromDto(dto, dto.ContentVersionDto.VersionId, sql); + var content = CreateContentFromDto(dto, dto.ContentVersionDto.VersionId); return content; } @@ -83,7 +75,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); if (ids.Any()) { - sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); + sql.Where("umbracoNode.id in (@ids)", new { /*ids =*/ ids }); } //we only want the newest ones with this method @@ -98,7 +90,8 @@ namespace Umbraco.Core.Persistence.Repositories var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate() .Where(x => x.Newest) - .OrderByDescending(x => x.VersionDate) + //.OrderByDescending(x => x.VersionDate) + .OrderBy(x => x.Level) .OrderBy(x => x.SortOrder); return MapQueryDtos(Database.Fetch(sql)); @@ -173,100 +166,12 @@ namespace Umbraco.Core.Persistence.Repositories return list; } - protected override Guid NodeObjectTypeId - { - get { return new Guid(Constants.ObjectTypes.Document); } - } + protected override Guid NodeObjectTypeId => new Guid(Constants.ObjectTypes.Document); #endregion #region Overrides of VersionableRepositoryBase - public void RebuildXmlStructures(Func serializer, int groupSize = 5000, IEnumerable contentTypeIds = null) - { - - //Ok, now we need to remove the data and re-insert it, we'll do this all in one transaction too. - using (var tr = Database.GetTransaction()) - { - //Remove all the data first, if anything fails after this it's no problem the transaction will be reverted - if (contentTypeIds == null) - { - var subQuery = Sql() - .Select("DISTINCT cmsContentXml.nodeId") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId); - - var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); - Database.Execute(deleteSql); - } - else - { - foreach (var id in contentTypeIds) - { - var id1 = id; - var subQuery = Sql() - .Select("cmsDocument.nodeId") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(dto => dto.Published) - .Where( dto => dto.ContentTypeId == id1); - - var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); - Database.Execute(deleteSql); - } - } - - //now insert the data, again if something fails here, the whole transaction is reversed - if (contentTypeIds == null) - { - var query = Query.Where(x => x.Published == true); - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); - } - else - { - foreach (var contentTypeId in contentTypeIds) - { - //copy local - var id = contentTypeId; - var query = Query.Where(x => x.Published == true && x.ContentTypeId == id && x.Trashed == false); - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); - } - } - - tr.Complete(); - } - } - - private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, ITransaction tr, int pageSize) - { - var pageIndex = 0; - var total = long.MinValue; - var processed = 0; - do - { - //NOTE: This is an important call, we cannot simply make a call to: - // GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending); - // because that method is used to query 'latest' content items where in this case we don't necessarily - // want latest content items because a pulished content item might not actually be the latest. - // see: http://issues.umbraco.org/issue/U4-6322 & http://issues.umbraco.org/issue/U4-5982 - var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, - MapQueryDtos, "Path", Direction.Ascending, true); - - var xmlItems = (from descendant in descendants - let xml = serializer(descendant) - select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }).ToArray(); - - //bulk insert it into the database - Database.BulkInsertRecords(SqlSyntax, xmlItems, tr); - - processed += xmlItems.Length; - - pageIndex++; - } while (processed < total); - } - public override IContent GetByVersion(Guid versionId) { var sql = GetBaseQuery(false); @@ -278,7 +183,7 @@ namespace Umbraco.Core.Persistence.Repositories if (dto == null) return null; - var content = CreateContentFromDto(dto, versionId, sql); + var content = CreateContentFromDto(dto, versionId); return content; } @@ -360,9 +265,7 @@ namespace Umbraco.Core.Persistence.Repositories //ensure the default template is assigned if (entity.Template == null) - { entity.Template = entity.ContentType.DefaultTemplate; - } //Ensure unique name on the same level entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name); @@ -375,10 +278,10 @@ namespace Umbraco.Core.Persistence.Repositories //NOTE Should the logic below have some kind of fallback for empty parent ids ? //Logic for setting Path, Level and SortOrder - var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId }); + var parent = Database.First("WHERE id = @ParentId", new { /*ParentId =*/ entity.ParentId }); var level = parent.Level + 1; var maxSortOrder = Database.ExecuteScalar( - "SELECT coalesce(max(sortOrder),-1) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", + "SELECT coalesce(max(sortOrder),-1) FROM umbracoNode WHERE parentId = @ParentId AND nodeObjectType = @NodeObjectType", new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId }); var sortOrder = maxSortOrder + 1; @@ -387,7 +290,7 @@ namespace Umbraco.Core.Persistence.Repositories nodeDto.Path = parent.Path; nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture)); nodeDto.SortOrder = sortOrder; - var o = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto); + var o = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto); //Update with new correct path nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); @@ -401,6 +304,8 @@ namespace Umbraco.Core.Persistence.Repositories //Assign the same permissions to it as the parent node // http://issues.umbraco.org/issue/U4-2161 + // fixme STOP new-ing repos everywhere! + // var prepo = UnitOfWork.CreateRepository>(); var permissionsRepo = new PermissionRepository(UnitOfWork, _cacheHelper); var parentPermissions = permissionsRepo.GetPermissionsForEntity(entity.ParentId).ToArray(); //if there are parent permissions then assign them, otherwise leave null and permissions will become the @@ -448,15 +353,11 @@ namespace Umbraco.Core.Persistence.Repositories //Update Properties with its newly set Id foreach (var property in entity.Properties) - { property.Id = keyDictionary[property.PropertyTypeId]; - } //lastly, check if we are a creating a published version , then update the tags table if (entity.Published) - { - UpdatePropertyTags(entity, _tagRepository); - } + UpdateEntityTags(entity, _tagRepository); // published => update published version infos, else leave it blank if (entity.Published) @@ -476,7 +377,9 @@ namespace Umbraco.Core.Persistence.Repositories protected override void PersistUpdatedItem(IContent entity) { - var publishedState = ((Content)entity).PublishedState; + var content = (Content) entity; + var publishedState = content.PublishedState; + var publishedStateChanged = publishedState == PublishedState.Publishing || publishedState == PublishedState.Unpublishing; //check if we need to make any database changes at all if (entity.RequiresSaving(publishedState) == false) @@ -486,11 +389,11 @@ namespace Umbraco.Core.Persistence.Repositories } //check if we need to create a new version - bool shouldCreateNewVersion = entity.ShouldCreateNewVersion(publishedState); - if (shouldCreateNewVersion) + var requiresNewVersion = entity.RequiresNewVersion(publishedState); + if (requiresNewVersion) { //Updates Modified date and Version Guid - ((Content)entity).UpdatingEntity(); + content.UpdatingEntity(); } else { @@ -506,14 +409,10 @@ namespace Umbraco.Core.Persistence.Repositories //Look up parent to get and set the correct Path and update SortOrder if ParentId has changed if (entity.IsPropertyDirty("ParentId")) { - var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId }); + var parent = Database.First("WHERE id = @ParentId", new { /*ParentId =*/ entity.ParentId }); entity.Path = string.Concat(parent.Path, ",", entity.Id); entity.Level = parent.Level + 1; - var maxSortOrder = - Database.ExecuteScalar( - "SELECT coalesce(max(sortOrder),0) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", - new { ParentId = entity.ParentId, NodeObjectType = NodeObjectTypeId }); - entity.SortOrder = maxSortOrder + 1; + entity.SortOrder = NextChildSortOrder(entity.ParentId); //Question: If we move a node, should we update permissions to inherit from the new parent if the parent has permissions assigned? // if we do that, then we'd need to propogate permissions all the way downward which might not be ideal for many people. @@ -522,7 +421,7 @@ namespace Umbraco.Core.Persistence.Repositories var factory = new ContentFactory(NodeObjectTypeId, entity.Id); //Look up Content entry to get Primary for updating the DTO - var contentDto = Database.SingleOrDefault("WHERE nodeId = @Id", new { Id = entity.Id }); + var contentDto = Database.SingleOrDefault("WHERE nodeId = @Id", new { /*Id =*/ entity.Id }); factory.SetPrimaryKey(contentDto.PrimaryKey); var dto = factory.BuildDto(entity); @@ -538,37 +437,17 @@ namespace Umbraco.Core.Persistence.Repositories Database.Update(newContentDto); } - //a flag that we'll use later to create the tags in the tag db table - var publishedStateChanged = false; - //If Published state has changed then previous versions should have their publish state reset. //If state has been changed to unpublished the previous versions publish state should also be reset. //if (((ICanBeDirty)entity).IsPropertyDirty("Published") && (entity.Published || publishedState == PublishedState.Unpublished)) - if (entity.ShouldClearPublishedFlagForPreviousVersions(publishedState, shouldCreateNewVersion)) - { - var publishedDocs = Database.Fetch("WHERE nodeId = @Id AND published = @IsPublished", new { Id = entity.Id, IsPublished = true }); - foreach (var doc in publishedDocs) - { - var docDto = doc; - docDto.Published = false; - Database.Update(docDto); - } - - //this is a newly published version so we'll update the tags table too (end of this method) - publishedStateChanged = true; - } + if (entity.RequiresClearPublishedFlag(publishedState, requiresNewVersion)) + ClearPublishedFlag(entity); //Look up (newest) entries by id in cmsDocument table to set newest = false - var documentDtos = Database.Fetch("WHERE nodeId = @Id AND newest = @IsNewest", new { Id = entity.Id, IsNewest = true }); - foreach (var documentDto in documentDtos) - { - var docDto = documentDto; - docDto.Newest = false; - Database.Update(docDto); - } + ClearNewestFlag(entity); var contentVersionDto = dto.ContentVersionDto; - if (shouldCreateNewVersion) + if (requiresNewVersion) { //Create a new version - cmsContentVersion //Assumes a new Version guid and Version date (modified date) has been set @@ -580,7 +459,7 @@ namespace Umbraco.Core.Persistence.Repositories else { //In order to update the ContentVersion we need to retrieve its primary key id - var contentVerDto = Database.SingleOrDefault("WHERE VersionId = @Version", new { Version = entity.Version }); + var contentVerDto = Database.SingleOrDefault("WHERE VersionId = @Version", new { /*Version =*/ entity.Version }); contentVersionDto.Id = contentVerDto.Id; Database.Update(contentVersionDto); @@ -595,7 +474,7 @@ namespace Umbraco.Core.Persistence.Repositories //Add Properties foreach (var propertyDataDto in propertyDataDtos) { - if (shouldCreateNewVersion == false && propertyDataDto.Id > 0) + if (requiresNewVersion == false && propertyDataDto.Id > 0) { Database.Update(propertyDataDto); } @@ -617,19 +496,38 @@ namespace Umbraco.Core.Persistence.Repositories } } - //lastly, check if we are a newly published version and then update the tags table - if (publishedStateChanged && entity.Published) + // tags: + if (HasTagProperty(entity)) { - UpdatePropertyTags(entity, _tagRepository); - } - else if (publishedStateChanged && (entity.Trashed || entity.Published == false)) - { - //it's in the trash or not published remove all entity tags - ClearEntityTags(entity, _tagRepository); + // if path-published, update tags, else clear tags + switch (content.PublishedState) + { + case PublishedState.Publishing: + // explicitely publishing, must update tags + UpdateEntityTags(entity, _tagRepository); + break; + case PublishedState.Unpublishing: + // explicitely unpublishing, must clear tags + ClearEntityTags(entity, _tagRepository); + break; + case PublishedState.Saving: + // saving, nothing to do + break; + case PublishedState.Published: + case PublishedState.Unpublished: + // no change, depends on path-published + // that should take care of trashing and un-trashing + if (IsPathPublished(entity)) // slightly expensive ;-( + UpdateEntityTags(entity, _tagRepository); + else + ClearEntityTags(entity, _tagRepository); + break; + } } // published => update published version infos, // else if unpublished then clear published version infos + // else leave unchanged if (entity.Published) { dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto @@ -639,7 +537,7 @@ namespace Umbraco.Core.Persistence.Repositories NodeId = dto.NodeId, Published = true }; - ((Content)entity).PublishedVersionGuid = dto.VersionId; + content.PublishedVersionGuid = dto.VersionId; } else if (publishedStateChanged) { @@ -650,12 +548,20 @@ namespace Umbraco.Core.Persistence.Repositories NodeId = dto.NodeId, Published = false }; - ((Content)entity).PublishedVersionGuid = default(Guid); + content.PublishedVersionGuid = default(Guid); } entity.ResetDirtyProperties(); } + private int NextChildSortOrder(int parentId) + { + var maxSortOrder = + Database.ExecuteScalar( + "SELECT coalesce(max(sortOrder),0) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", + new { ParentId = parentId, NodeObjectType = NodeObjectTypeId }); + return maxSortOrder + 1; + } #endregion @@ -664,9 +570,7 @@ namespace Umbraco.Core.Persistence.Repositories public IEnumerable GetByPublishedVersion(IQuery query) { // we WANT to return contents in top-down order, ie parents should come before children - // ideal would be pure xml "document order" which can be achieved with: - // ORDER BY substring(path, 1, len(path) - charindex(',', reverse(path))), sortOrder - // but that's probably an overkill - sorting by level,sortOrder should be enough + // ideal would be pure xml "document order" - which we cannot achieve at database level var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); @@ -680,27 +584,42 @@ namespace Umbraco.Core.Persistence.Repositories foreach (var dto in dtos) { - //Check in the cache first. If it exists there AND it is published - // then we can use that entity. Otherwise if it is not published (which can be the case - // because we only store the 'latest' entries in the cache which might not be the published - // version) + // check cache first, if it exists and is published, use it + // it may exist and not be published as the cache has 'latest version used' var fromCache = RuntimeCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); - //var fromCache = TryGetFromCache(dto.NodeId); - if (fromCache != null && fromCache.Published) - { - yield return fromCache; - } - else - { - yield return CreateContentFromDto(dto, dto.VersionId, sql); - } + yield return fromCache != null && fromCache.Published + ? fromCache + : CreateContentFromDto(dto, dto.VersionId); } } - public int CountPublished() + public int CountPublished(string contentTypeAlias = null) { - var sql = GetBaseQuery(true).Where(x => x.Trashed == false) - .Where(x => x.Published == true); + var sql = Sql(); + if (contentTypeAlias.IsNullOrWhiteSpace()) + { + sql.SelectCount() + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId && x.Trashed == false) + .Where(x => x.Published); + } + else + { + sql.SelectCount() + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.ContentTypeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId && x.Trashed == false) + .Where(x => x.Alias == contentTypeAlias) + .Where(x => x.Published); + } + return Database.ExecuteScalar(sql); } @@ -710,10 +629,10 @@ namespace Umbraco.Core.Persistence.Repositories repo.ReplaceEntityPermissions(permissionSet); } - public void ClearPublished(IContent content) + public void ClearPublishedFlag(IContent content) { - // race cond! - var documentDtos = Database.Fetch("WHERE nodeId=@id AND published=@published", new { id = content.Id, published = true }); + // no race cond if locked + var documentDtos = Database.Fetch("WHERE nodeId=@Id AND published=@IsPublished", new { /*Id =*/ content.Id, IsPublished = true }); foreach (var documentDto in documentDtos) { documentDto.Published = false; @@ -721,6 +640,17 @@ namespace Umbraco.Core.Persistence.Repositories } } + public void ClearNewestFlag(IContent content) + { + // no race cond if locked + var documentDtos = Database.Fetch("WHERE nodeId=@Id AND newest=@IsNewest", new { /*Id =*/ content.Id, IsNewest = true }); + foreach (var documentDto in documentDtos) + { + documentDto.Newest = false; + Database.Update(documentDto); + } + } + /// /// Assigns a single permission to the current content item for the specified user ids /// @@ -739,35 +669,6 @@ namespace Umbraco.Core.Persistence.Repositories return repo.GetPermissionsForEntity(entityId); } - /// - /// Adds/updates content/published xml - /// - /// - /// - public void AddOrUpdateContentXml(IContent content, Func xml) - { - _contentXmlRepository.AddOrUpdate(new ContentXmlEntity(content, xml)); - } - - /// - /// Used to remove the content xml for a content item - /// - /// - public void DeleteContentXml(IContent content) - { - _contentXmlRepository.Delete(new ContentXmlEntity(content)); - } - - /// - /// Adds/updates preview xml - /// - /// - /// - public void AddOrUpdatePreviewXml(IContent content, Func xml) - { - _contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity(content, xml)); - } - /// /// Gets paged content results /// @@ -793,42 +694,40 @@ namespace Umbraco.Core.Persistence.Repositories filterSql); } - /// - /// Returns the persisted content's preview XML structure - /// - /// - /// - public XElement GetContentXml(int contentId) + public bool IsPathPublished(IContent content) { - var sql = Sql().SelectAll().From().Where(d => d.NodeId == contentId); - var dto = Database.SingleOrDefault(sql); - if (dto == null) return null; - return XElement.Parse(dto.Xml); - } + // fail fast + if (content.Path.StartsWith("-1,-20,")) + return false; + // succeed fast + if (content.ParentId == -1) + return content.HasPublishedVersion; - /// - /// Returns the persisted content's preview XML structure - /// - /// - /// - /// - public XElement GetContentPreviewXml(int contentId, Guid version) - { - var sql = Sql().SelectAll().From() - .Where(d => d.NodeId == contentId && d.VersionId == version); - var dto = Database.SingleOrDefault(sql); - if (dto == null) return null; - return XElement.Parse(dto.Xml); + var syntaxUmbracoNode = SqlSyntax.GetQuotedTableName("umbracoNode"); + var syntaxPath = SqlSyntax.GetQuotedColumnName("path"); + var syntaxConcat = SqlSyntax.GetConcat(syntaxUmbracoNode + "." + syntaxPath, "',%'"); + + var sql = string.Format(@"SELECT COUNT({0}.{1}) +FROM {0} +JOIN {2} ON ({0}.{1}={2}.{3} AND {2}.{4}=@published) +WHERE (@path LIKE {5})", + syntaxUmbracoNode, + SqlSyntax.GetQuotedColumnName("id"), + SqlSyntax.GetQuotedTableName("cmsDocument"), + SqlSyntax.GetQuotedColumnName("nodeId"), + SqlSyntax.GetQuotedColumnName("published"), + syntaxConcat); + + var count = Database.ExecuteScalar(sql, new { @published=true, @path=content.Path }); + count += 1; // because content does not count + return count == content.Level; } #endregion #region IRecycleBinRepository members - protected override int RecycleBinId - { - get { return Constants.System.RecycleBinContent; } - } + protected override int RecycleBinId => Constants.System.RecycleBinContent; #endregion @@ -885,7 +784,7 @@ namespace Umbraco.Core.Persistence.Repositories return dtosWithContentTypes.Select(d => CreateContentFromDto( d.dto, contentTypes.First(ct => ct.Id == d.dto.ContentVersionDto.ContentDto.ContentTypeId), - templates.FirstOrDefault(tem => tem.Id == (d.dto.TemplateId.HasValue ? d.dto.TemplateId.Value : -1)), + templates.FirstOrDefault(tem => tem.Id == (d.dto.TemplateId ?? -1)), propertyData[d.dto.NodeId])); } @@ -900,7 +799,7 @@ namespace Umbraco.Core.Persistence.Repositories private IContent CreateContentFromDto(DocumentDto dto, IContentType contentType, ITemplate template, - Models.PropertyCollection propCollection) + PropertyCollection propCollection) { var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId); var content = factory.BuildEntity(dto); @@ -929,9 +828,8 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// - /// /// - private IContent CreateContentFromDto(DocumentDto dto, Guid versionId, Sql docSql) + private IContent CreateContentFromDto(DocumentDto dto, Guid versionId) { var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); @@ -979,7 +877,7 @@ namespace Umbraco.Core.Persistence.Repositories if (dto.Text.ToLowerInvariant().Equals(currentName.ToLowerInvariant())) { - currentName = nodeName + string.Format(" ({0})", uniqueNumber); + currentName = nodeName + $" ({uniqueNumber})"; uniqueNumber++; } } @@ -987,5 +885,151 @@ namespace Umbraco.Core.Persistence.Repositories return currentName; } + + #region Xml - Should Move! + + public void RebuildXmlStructures(Func serializer, int groupSize = 5000, IEnumerable contentTypeIds = null) + { + + //Ok, now we need to remove the data and re-insert it, we'll do this all in one transaction too. + using (var tr = Database.GetTransaction()) + { + //Remove all the data first, if anything fails after this it's no problem the transaction will be reverted + if (contentTypeIds == null) + { + var subQuery = Sql() + .Select("DISTINCT cmsContentXml.nodeId") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId); + + var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); + Database.Execute(deleteSql); + } + else + { + foreach (var id in contentTypeIds) + { + var id1 = id; + var subQuery = Sql() + .Select("cmsDocument.nodeId") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(dto => dto.Published) + .Where(dto => dto.ContentTypeId == id1); + + var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); + Database.Execute(deleteSql); + } + } + + //now insert the data, again if something fails here, the whole transaction is reversed + if (contentTypeIds == null) + { + var query = Query.Where(x => x.Published); + RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); + } + else + { + foreach (var contentTypeId in contentTypeIds) + { + //copy local + var id = contentTypeId; + var query = Query.Where(x => x.Published && x.ContentTypeId == id && x.Trashed == false); + RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); + } + } + + tr.Complete(); + } + } + + private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, ITransaction tr, int pageSize) + { + var pageIndex = 0; + long total; + var processed = 0; + do + { + //NOTE: This is an important call, we cannot simply make a call to: + // GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending); + // because that method is used to query 'latest' content items where in this case we don't necessarily + // want latest content items because a pulished content item might not actually be the latest. + // see: http://issues.umbraco.org/issue/U4-6322 & http://issues.umbraco.org/issue/U4-5982 + var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, + MapQueryDtos, "Path", Direction.Ascending, true); + + var xmlItems = (from descendant in descendants + let xml = serializer(descendant) + select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }).ToArray(); + + //bulk insert it into the database + Database.BulkInsertRecords(SqlSyntax, xmlItems, tr); + + processed += xmlItems.Length; + + pageIndex++; + } while (processed < total); + } + + /// + /// Adds/updates content/published xml + /// + /// + /// + public void AddOrUpdateContentXml(IContent content, Func xml) + { + _contentXmlRepository.AddOrUpdate(new ContentXmlEntity(content, xml)); + } + + /// + /// Used to remove the content xml for a content item + /// + /// + public void DeleteContentXml(IContent content) + { + _contentXmlRepository.Delete(new ContentXmlEntity(content)); + } + + /// + /// Adds/updates preview xml + /// + /// + /// + public void AddOrUpdatePreviewXml(IContent content, Func xml) + { + _contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity(content, xml)); + } + + /// + /// Returns the persisted content's preview XML structure + /// + /// + /// + public XElement GetContentXml(int contentId) + { + var sql = Sql().SelectAll().From().Where(d => d.NodeId == contentId); + var dto = Database.SingleOrDefault(sql); + if (dto == null) return null; + return XElement.Parse(dto.Xml); + } + + /// + /// Returns the persisted content's preview XML structure + /// + /// + /// + /// + public XElement GetContentPreviewXml(int contentId, Guid version) + { + var sql = Sql().SelectAll().From() + .Where(d => d.NodeId == contentId && d.VersionId == version); + var dto = Database.SingleOrDefault(sql); + if (dto == null) return null; + return XElement.Parse(dto.Xml); + } + + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index 40cf02af38..aacd51c685 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -3,26 +3,21 @@ using System.Collections.Generic; using System.Linq; using NPoco; using Umbraco.Core.Cache; -using Umbraco.Core.Events; -using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; - -using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories { /// /// Represents a repository for doing CRUD operations for /// - internal class ContentTypeRepository : ContentTypeBaseRepository, IContentTypeRepository + internal class ContentTypeRepository : ContentTypeRepositoryBase, IContentTypeRepository { private readonly ITemplateRepository _templateRepository; @@ -51,6 +46,23 @@ namespace Umbraco.Core.Persistence.Repositories return GetAll().FirstOrDefault(x => x.Id == id); } + protected override IContentType PerformGet(Guid id) + { + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Key == id); + } + + protected override IContentType PerformGet(string alias) + { + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); + } + + protected override bool PerformExists(Guid id) + { + return GetAll().FirstOrDefault(x => x.Key == id) != null; + } + protected override IEnumerable PerformGetAll(params int[] ids) { if (ids.Any()) @@ -63,6 +75,12 @@ namespace Umbraco.Core.Persistence.Repositories return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, this, _templateRepository); } + + protected override IEnumerable PerformGetAll(params Guid[] ids) + { + // use the underlying GetAll which will force cache all content types + return ids.Any() ? GetAll().Where(x => ids.Contains(x.Key)) : GetAll(); + } protected override IEnumerable PerformGetByQuery(IQuery query) { var sqlClause = GetBaseQuery(false); @@ -154,28 +172,14 @@ namespace Umbraco.Core.Persistence.Repositories protected override IEnumerable GetDeleteClauses() { - var list = new List - { - "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", - "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", - "DELETE FROM cmsContentTypeAllowedContentType WHERE Id = @Id", - "DELETE FROM cmsContentTypeAllowedContentType WHERE AllowedId = @Id", - "DELETE FROM cmsContentType2ContentType WHERE parentContentTypeId = @Id", - "DELETE FROM cmsContentType2ContentType WHERE childContentTypeId = @Id", - "DELETE FROM cmsPropertyType WHERE contentTypeId = @Id", - "DELETE FROM cmsPropertyTypeGroup WHERE contenttypeNodeId = @Id", - "DELETE FROM cmsDocumentType WHERE contentTypeNodeId = @Id", - "DELETE FROM cmsContentType WHERE nodeId = @Id", - "DELETE FROM umbracoNode WHERE id = @Id" - }; - return list; + var l = (List) base.GetDeleteClauses(); // we know it's a list + l.Add("DELETE FROM cmsDocumentType WHERE contentTypeNodeId = @Id"); + l.Add("DELETE FROM cmsContentType WHERE nodeId = @Id"); + l.Add("DELETE FROM umbracoNode WHERE id = @Id"); + return l; } - protected override Guid NodeObjectTypeId - { - get { return new Guid(Constants.ObjectTypes.DocumentType); } - } + protected override Guid NodeObjectTypeId => Constants.ObjectTypes.DocumentTypeGuid; /// /// Deletes a content type @@ -290,36 +294,5 @@ namespace Umbraco.Core.Persistence.Repositories entity.ResetDirtyProperties(); } - - protected override IContentType PerformGet(Guid id) - { - //use the underlying GetAll which will force cache all content types - return GetAll().FirstOrDefault(x => x.Key == id); - } - - protected override IContentType PerformGet(string alias) - { - //use the underlying GetAll which will force cache all content types - return GetAll().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); - } - - protected override IEnumerable PerformGetAll(params Guid[] ids) - { - //use the underlying GetAll which will force cache all content types - - if (ids.Any()) - { - return GetAll().Where(x => ids.Contains(x.Key)); - } - else - { - return GetAll(); - } - } - - protected override bool PerformExists(Guid id) - { - return GetAll().FirstOrDefault(x => x.Key == id) != null; - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepositoryBase.cs similarity index 94% rename from src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs rename to src/Umbraco.Core/Persistence/Repositories/ContentTypeRepositoryBase.cs index a446a0a390..cd1ef66168 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepositoryBase.cs @@ -1,1230 +1,1274 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using NPoco; -using Umbraco.Core.Events; -using Umbraco.Core.Exceptions; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Rdbms; - -using Umbraco.Core.Persistence.Factories; -using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Persistence.Repositories -{ - /// - /// Represent an abstract Repository for ContentType based repositories - /// - /// Exposes shared functionality - /// - internal abstract class ContentTypeBaseRepository : NPocoRepositoryBase, IReadRepository - where TEntity : class, IContentTypeComposition - { - protected ContentTypeBaseRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, IMappingResolver mappingResolver) - : base(work, cache, logger, mappingResolver) - { - } - - public IEnumerable> Move(TEntity toMove, EntityContainer container) - { - var parentId = Constants.System.Root; - if (container != null) - { - // Check on paths - if ((string.Format(",{0},", container.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) - { - throw new DataOperationException(MoveOperationStatusType.FailedNotAllowedByPath); - } - parentId = container.Id; - } - - //used to track all the moved entities to be given to the event - var moveInfo = new List> - { - new MoveEventInfo(toMove, toMove.Path, parentId) - }; - - - // get the level delta (old pos to new pos) - var levelDelta = container == null - ? 1 - toMove.Level - : container.Level + 1 - toMove.Level; - - // move to parent (or -1), update path, save - toMove.ParentId = parentId; - var toMovePath = toMove.Path + ","; // save before changing - toMove.Path = (container == null ? Constants.System.Root.ToString() : container.Path) + "," + toMove.Id; - toMove.Level = container == null ? 1 : container.Level + 1; - AddOrUpdate(toMove); - - //update all descendants, update in order of level - var descendants = GetByQuery(Query.Where(type => type.Path.StartsWith(toMovePath))); - var paths = new Dictionary(); - paths[toMove.Id] = toMove.Path; - - foreach (var descendant in descendants.OrderBy(x => x.Level)) - { - moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); - - descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id; - descendant.Level += levelDelta; - - AddOrUpdate(descendant); - } - - return moveInfo; - } - /// - /// Returns the content type ids that match the query - /// - /// - /// - protected IEnumerable PerformGetByQuery(IQuery query) - { - var sqlClause = Sql() - .SelectAll() - .From() - .RightJoin() - .On(left => left.Id, right => right.PropertyTypeGroupId) - .InnerJoin() - .On(left => left.DataTypeId, right => right.DataTypeId); - - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate() - .OrderBy(x => x.PropertyTypeGroupId); - - return Database - .FetchOneToMany(x => x.PropertyTypeDtos, sql) - .Select(x => x.ContentTypeNodeId).Distinct(); - } - - protected virtual PropertyType CreatePropertyType(string propertyEditorAlias, DataTypeDatabaseType dbType, string propertyTypeAlias) - { - return new PropertyType(propertyEditorAlias, dbType, propertyTypeAlias); - } - - protected void PersistNewBaseContentType(IContentTypeComposition entity) - { - var factory = new ContentTypeFactory(); - var dto = factory.BuildContentTypeDto(entity); - - //Cannot add a duplicate content type type - var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsContentType -INNER JOIN umbracoNode ON cmsContentType.nodeId = umbracoNode.id -WHERE cmsContentType." + SqlSyntax.GetQuotedColumnName("alias") + @"= @alias -AND umbracoNode.nodeObjectType = @objectType", - new { alias = entity.Alias, objectType = NodeObjectTypeId }); - if (exists > 0) - { - throw new DuplicateNameException("An item with the alias " + entity.Alias + " already exists"); - } - - //Logic for setting Path, Level and SortOrder - var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId }); - int level = parent.Level + 1; - int sortOrder = - Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoNode WHERE parentID = @ParentId AND nodeObjectType = @NodeObjectType", - new { ParentId = entity.ParentId, NodeObjectType = NodeObjectTypeId }); - - //Create the (base) node data - umbracoNode - var nodeDto = dto.NodeDto; - nodeDto.Path = parent.Path; - nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture)); - nodeDto.SortOrder = sortOrder; - var o = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto); - - //Update with new correct path - nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); - Database.Update(nodeDto); - - //Update entity with correct values - entity.Id = nodeDto.NodeId; //Set Id on entity to ensure an Id is set - entity.Path = nodeDto.Path; - entity.SortOrder = sortOrder; - entity.Level = level; - - //Insert new ContentType entry - dto.NodeId = nodeDto.NodeId; - Database.Insert(dto); - - //Insert ContentType composition in new table - foreach (var composition in entity.ContentTypeComposition) - { - if (composition.Id == entity.Id) continue;//Just to ensure that we aren't creating a reference to ourself. - - if (composition.HasIdentity) - { - Database.Insert(new ContentType2ContentTypeDto { ParentId = composition.Id, ChildId = entity.Id }); - } - else - { - //Fallback for ContentTypes with no identity - var contentTypeDto = Database.FirstOrDefault("WHERE alias = @Alias", new { Alias = composition.Alias }); - if (contentTypeDto != null) - { - Database.Insert(new ContentType2ContentTypeDto { ParentId = contentTypeDto.NodeId, ChildId = entity.Id }); - } - } - } - - //Insert collection of allowed content types - foreach (var allowedContentType in entity.AllowedContentTypes) - { - Database.Insert(new ContentTypeAllowedContentTypeDto - { - Id = entity.Id, - AllowedId = allowedContentType.Id.Value, - SortOrder = allowedContentType.SortOrder - }); - } - - var propertyFactory = new PropertyGroupFactory(nodeDto.NodeId); - - //Insert Tabs - foreach (var propertyGroup in entity.PropertyGroups) - { - var tabDto = propertyFactory.BuildGroupDto(propertyGroup); - var primaryKey = Convert.ToInt32(Database.Insert(tabDto)); - propertyGroup.Id = primaryKey;//Set Id on PropertyGroup - - //Ensure that the PropertyGroup's Id is set on the PropertyTypes within a group - //unless the PropertyGroupId has already been changed. - foreach (var propertyType in propertyGroup.PropertyTypes) - { - if (propertyType.IsPropertyDirty("PropertyGroupId") == false) - { - var tempGroup = propertyGroup; - propertyType.PropertyGroupId = new Lazy(() => tempGroup.Id); - } - } - } - - //Insert PropertyTypes - foreach (var propertyType in entity.PropertyTypes) - { - var tabId = propertyType.PropertyGroupId != null ? propertyType.PropertyGroupId.Value : default(int); - //If the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias - if (propertyType.DataTypeDefinitionId == 0 || propertyType.DataTypeDefinitionId == default(int)) - { - AssignDataTypeFromPropertyEditor(propertyType); - } - var propertyTypeDto = propertyFactory.BuildPropertyTypeDto(tabId, propertyType); - int typePrimaryKey = Convert.ToInt32(Database.Insert(propertyTypeDto)); - propertyType.Id = typePrimaryKey; //Set Id on new PropertyType - - //Update the current PropertyType with correct PropertyEditorAlias and DatabaseType - var dataTypeDto = Database.FirstOrDefault("WHERE nodeId = @Id", new { Id = propertyTypeDto.DataTypeId }); - propertyType.PropertyEditorAlias = dataTypeDto.PropertyEditorAlias; - propertyType.DataTypeDatabaseType = dataTypeDto.DbType.EnumParse(true); - } - } - - protected void PersistUpdatedBaseContentType(IContentTypeComposition entity) - { - var factory = new ContentTypeFactory(); - var dto = factory.BuildContentTypeDto(entity); - - // ensure the alias is not used already - var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsContentType -INNER JOIN umbracoNode ON cmsContentType.nodeId = umbracoNode.id -WHERE cmsContentType." + SqlSyntax.GetQuotedColumnName("alias") + @"= @alias -AND umbracoNode.nodeObjectType = @objectType -AND umbracoNode.id <> @id", - new { id = dto.NodeId, alias = dto.Alias, objectType = NodeObjectTypeId }); - if (exists > 0) - throw new DuplicateNameException("An item with the alias " + dto.Alias + " already exists"); - - // handle (update) the node - var nodeDto = dto.NodeDto; - Database.Update(nodeDto); - - // fixme - why? we are UPDATING so we should ALREADY have a PK! - //Look up ContentType entry to get PrimaryKey for updating the DTO - var dtoPk = Database.First("WHERE nodeId = @Id", new { Id = entity.Id }); - dto.PrimaryKey = dtoPk.PrimaryKey; - Database.Update(dto); - - // handle (delete then recreate) compositions - Database.Delete("WHERE childContentTypeId = @Id", new { Id = entity.Id }); - foreach (var composition in entity.ContentTypeComposition) - Database.Insert(new ContentType2ContentTypeDto { ParentId = composition.Id, ChildId = entity.Id }); - - //Removing a ContentType from a composition (U4-1690) - //1. Find content based on the current ContentType: entity.Id - //2. Find all PropertyTypes on the ContentType that was removed - tracked id (key) - //3. Remove properties based on property types from the removed content type where the content ids correspond to those found in step one - var compositionBase = entity as ContentTypeCompositionBase; - if (compositionBase != null && compositionBase.RemovedContentTypeKeyTracker != null && - compositionBase.RemovedContentTypeKeyTracker.Any()) - { - //Find Content based on the current ContentType - var sql = Sql() - .SelectAll() - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(x => x.NodeObjectType == new Guid(Constants.ObjectTypes.Document)) - .Where(x => x.ContentTypeId == entity.Id); - - var contentDtos = Database.Fetch(sql); - //Loop through all tracked keys, which corresponds to the ContentTypes that has been removed from the composition - foreach (var key in compositionBase.RemovedContentTypeKeyTracker) - { - //Find PropertyTypes for the removed ContentType - var propertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new { Id = key }); - //Loop through the Content that is based on the current ContentType in order to remove the Properties that are - //based on the PropertyTypes that belong to the removed ContentType. - foreach (var contentDto in contentDtos) - { - foreach (var propertyType in propertyTypes) - { - var nodeId = contentDto.NodeId; - var propertyTypeId = propertyType.Id; - var propertySql = Sql() - .Select("cmsPropertyData.id") - .From() - .InnerJoin() - .On(left => left.PropertyTypeId, right => right.Id) - .Where(x => x.NodeId == nodeId) - .Where(x => x.Id == propertyTypeId); - - //Finally delete the properties that match our criteria for removing a ContentType from the composition - Database.Delete(new Sql("WHERE id IN (" + propertySql.SQL + ")", propertySql.Arguments)); - } - } - } - } - - //Delete the allowed content type entries before adding the updated collection - Database.Delete("WHERE Id = @Id", new { Id = entity.Id }); - //Insert collection of allowed content types - foreach (var allowedContentType in entity.AllowedContentTypes) - { - Database.Insert(new ContentTypeAllowedContentTypeDto - { - Id = entity.Id, - AllowedId = allowedContentType.Id.Value, - SortOrder = allowedContentType.SortOrder - }); - } - - - if (((ICanBeDirty)entity).IsPropertyDirty("PropertyTypes") || entity.PropertyTypes.Any(x => x.IsDirty())) - { - //Delete PropertyTypes by excepting entries from db with entries from collections - var dbPropertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new { Id = entity.Id }); - var dbPropertyTypeAlias = dbPropertyTypes.Select(x => x.Id); - var entityPropertyTypes = entity.PropertyTypes.Where(x => x.HasIdentity).Select(x => x.Id); - var items = dbPropertyTypeAlias.Except(entityPropertyTypes); - foreach (var item in items) - { - //Before a PropertyType can be deleted, all Properties based on that PropertyType should be deleted. - Database.Delete("WHERE propertyTypeId = @Id", new { Id = item }); - Database.Delete("WHERE propertytypeid = @Id", new { Id = item }); - Database.Delete("WHERE contentTypeId = @Id AND id = @PropertyTypeId", - new { Id = entity.Id, PropertyTypeId = item }); - } - } - - if (entity.IsPropertyDirty("PropertyGroups") || entity.PropertyGroups.Any(x => x.IsDirty())) - { - // todo - // we used to try to propagate tabs renaming downstream, relying on ParentId, but - // 1) ParentId makes no sense (if a tab can be inherited from multiple composition - // types) so we would need to figure things out differently, visiting downstream - // content types and looking for tabs with the same name... - // 2) It was not deployable as changing a content type changes other content types - // that was not deterministic, because it would depend on the order of the changes. - // That last point could be fixed if (1) is fixed, but then it still is an issue with - // deploy because changing a content type changes other content types that are not - // dependencies but dependents, and then what? - // - // So... for the time being, all renaming propagation is disabled. We just don't do it. - - // (all gone) - - // delete tabs that do not exist anymore - // get the tabs that are currently existing (in the db) - // get the tabs that we want, now - // and derive the tabs that we want to delete - var existingPropertyGroups = Database.Fetch("WHERE contentTypeNodeId = @id", new { id = entity.Id }) - .Select(x => x.Id) - .ToList(); - var newPropertyGroups = entity.PropertyGroups.Select(x => x.Id).ToList(); - var tabsToDelete = existingPropertyGroups - .Except(newPropertyGroups) - .ToArray(); - - // move properties to generic properties, and delete the tabs - if (tabsToDelete.Length > 0) - { - Database.Update("SET propertyTypeGroupId=NULL WHERE propertyTypeGroupId IN (@ids)", new { ids = tabsToDelete }); - Database.Delete("WHERE id IN (@ids)", new { ids = tabsToDelete }); - } - } - var propertyGroupFactory = new PropertyGroupFactory(entity.Id); - - //Run through all groups to insert or update entries - foreach (var propertyGroup in entity.PropertyGroups) - { - var tabDto = propertyGroupFactory.BuildGroupDto(propertyGroup); - int groupPrimaryKey = propertyGroup.HasIdentity - ? Database.Update(tabDto) - : Convert.ToInt32(Database.Insert(tabDto)); - if (propertyGroup.HasIdentity == false) - propertyGroup.Id = groupPrimaryKey; //Set Id on new PropertyGroup - - //Ensure that the PropertyGroup's Id is set on the PropertyTypes within a group - //unless the PropertyGroupId has already been changed. - foreach (var propertyType in propertyGroup.PropertyTypes) - { - if (propertyType.IsPropertyDirty("PropertyGroupId") == false) - { - var tempGroup = propertyGroup; - propertyType.PropertyGroupId = new Lazy(() => tempGroup.Id); - } - } - } - - //Run through all PropertyTypes to insert or update entries - foreach (var propertyType in entity.PropertyTypes) - { - var tabId = propertyType.PropertyGroupId != null ? propertyType.PropertyGroupId.Value : default(int); - //If the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias - if (propertyType.DataTypeDefinitionId == 0 || propertyType.DataTypeDefinitionId == default(int)) - { - AssignDataTypeFromPropertyEditor(propertyType); - } - - //validate the alias! - ValidateAlias(propertyType); - - var propertyTypeDto = propertyGroupFactory.BuildPropertyTypeDto(tabId, propertyType); - int typePrimaryKey = propertyType.HasIdentity - ? Database.Update(propertyTypeDto) - : Convert.ToInt32(Database.Insert(propertyTypeDto)); - if (propertyType.HasIdentity == false) - propertyType.Id = typePrimaryKey; //Set Id on new PropertyType - } - } - - protected IEnumerable GetAllowedContentTypeIds(int id) - { - var sql = Sql() - .SelectAll() - .From() - .LeftJoin() - .On(left => left.AllowedId, right => right.NodeId) - .Where(x => x.Id == id); - - var allowedContentTypeDtos = Database.Fetch(sql); - return allowedContentTypeDtos.Select(x => new ContentTypeSort(new Lazy(() => x.AllowedId), x.SortOrder, x.ContentTypeDto.Alias)).ToList(); - } - - protected PropertyGroupCollection GetPropertyGroupCollection(int id, DateTime createDate, DateTime updateDate) - { - var sql = Sql() - .SelectAll() - .From() - .LeftJoin() - .On(left => left.Id, right => right.PropertyTypeGroupId) - .LeftJoin() - .On(left => left.DataTypeId, right => right.DataTypeId) - .Where(x => x.ContentTypeNodeId == id) - .OrderBy(x => x.Id); - - - var dtos = Database - .Fetch(sql); - - var propertyGroupFactory = new PropertyGroupFactory(id, createDate, updateDate, CreatePropertyType); - var propertyGroups = propertyGroupFactory.BuildEntity(dtos); - return new PropertyGroupCollection(propertyGroups); - } - - protected PropertyTypeCollection GetPropertyTypeCollection(int id, DateTime createDate, DateTime updateDate) - { - var sql = Sql() - .SelectAll() - .From() - .InnerJoin() - .On(left => left.DataTypeId, right => right.DataTypeId) - .Where(x => x.ContentTypeId == id); - - var dtos = Database.Fetch(sql); - - //TODO Move this to a PropertyTypeFactory - var list = new List(); - foreach (var dto in dtos.Where(x => (x.PropertyTypeGroupId > 0) == false)) - { - var propType = CreatePropertyType(dto.DataTypeDto.PropertyEditorAlias, dto.DataTypeDto.DbType.EnumParse(true), dto.Alias); - propType.DataTypeDefinitionId = dto.DataTypeId; - propType.Description = dto.Description; - propType.Id = dto.Id; - propType.Key = dto.UniqueId; - propType.Name = dto.Name; - propType.Mandatory = dto.Mandatory; - propType.SortOrder = dto.SortOrder; - propType.ValidationRegExp = dto.ValidationRegExp; - propType.CreateDate = createDate; - propType.UpdateDate = updateDate; - list.Add(propType); - } - //Reset dirty properties - Parallel.ForEach(list, currentFile => currentFile.ResetDirtyProperties(false)); - - return new PropertyTypeCollection(list); - } - - protected void ValidateAlias(PropertyType pt) - { - Mandate.That(string.IsNullOrEmpty(pt.Alias) == false, - () => - { - var message = - string.Format( - "{0} '{1}' cannot have an empty Alias. This is most likely due to invalid characters stripped from the Alias.", - "Property Type", - pt.Name); - var exception = new InvalidOperationException(message); - - Logger.Error>(message, exception); - throw exception; - }); - } - - protected void ValidateAlias(TEntity entity) - { - Mandate.That(string.IsNullOrEmpty(entity.Alias) == false, - () => - { - var message = - string.Format( - "{0} '{1}' cannot have an empty Alias. This is most likely due to invalid characters stripped from the Alias.", - typeof(TEntity).Name, - entity.Name); - var exception = new InvalidOperationException(message); - - Logger.Error>(message, exception); - throw exception; - }); - } - - /// - /// Try to set the data type id based on its ControlId - /// - /// - private void AssignDataTypeFromPropertyEditor(PropertyType propertyType) - { - //we cannot try to assign a data type of it's empty - if (propertyType.PropertyEditorAlias.IsNullOrWhiteSpace() == false) - { - var sql = Sql() - .SelectAll() - .From() - .Where("propertyEditorAlias = @propertyEditorAlias", new { propertyEditorAlias = propertyType.PropertyEditorAlias }) - .OrderBy(typeDto => typeDto.DataTypeId); - var datatype = Database.FirstOrDefault(sql); - //we cannot assign a data type if one was not found - if (datatype != null) - { - propertyType.DataTypeDefinitionId = datatype.DataTypeId; - } - else - { - Logger.Warn>("Could not assign a data type for the property type " + propertyType.Alias + " since no data type was found with a property editor " + propertyType.PropertyEditorAlias); - } - } - } - - internal static class ContentTypeQueryMapper - { - - public class AssociatedTemplate - { - public AssociatedTemplate(int templateId, string @alias, string templateName) - { - TemplateId = templateId; - Alias = alias; - TemplateName = templateName; - } - - public int TemplateId { get; set; } - public string Alias { get; set; } - public string TemplateName { get; set; } - - protected bool Equals(AssociatedTemplate other) - { - return TemplateId == other.TemplateId; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((AssociatedTemplate)obj); - } - - public override int GetHashCode() - { - return TemplateId; - } - } - - public static IEnumerable GetMediaTypes( - Database db, ISqlSyntaxProvider sqlSyntax, - TRepo contentTypeRepository) - where TRepo : IReadRepository - { - IDictionary> allParentMediaTypeIds; - var mediaTypes = MapMediaTypes(db, sqlSyntax, out allParentMediaTypeIds) - .ToArray(); - - MapContentTypeChildren(mediaTypes, db, sqlSyntax, contentTypeRepository, allParentMediaTypeIds); - - return mediaTypes; - } - - public static IEnumerable GetContentTypes( - Database db, ISqlSyntaxProvider sqlSyntax, - TRepo contentTypeRepository, - ITemplateRepository templateRepository) - where TRepo : IReadRepository - { - IDictionary> allAssociatedTemplates; - IDictionary> allParentContentTypeIds; - var contentTypes = MapContentTypes(db, sqlSyntax, out allAssociatedTemplates, out allParentContentTypeIds) - .ToArray(); - - if (contentTypes.Any()) - { - MapContentTypeTemplates( - contentTypes, db, contentTypeRepository, templateRepository, allAssociatedTemplates); - - MapContentTypeChildren( - contentTypes, db, sqlSyntax, contentTypeRepository, allParentContentTypeIds); - } - - return contentTypes; - } - - internal static void MapContentTypeChildren(IContentTypeComposition[] contentTypes, - Database db, ISqlSyntaxProvider sqlSyntax, - TRepo contentTypeRepository, - IDictionary> allParentContentTypeIds) - where TRepo : IReadRepository - { - //NOTE: SQL call #2 - - var ids = contentTypes.Select(x => x.Id).ToArray(); - IDictionary allPropGroups; - IDictionary allPropTypes; - MapGroupsAndProperties(ids, db, sqlSyntax, out allPropTypes, out allPropGroups); - - foreach (var contentType in contentTypes) - { - contentType.PropertyGroups = allPropGroups[contentType.Id]; - contentType.NoGroupPropertyTypes = allPropTypes[contentType.Id]; - } - - //NOTE: SQL call #3++ - - if (allParentContentTypeIds != null) - { - var allParentIdsAsArray = allParentContentTypeIds.SelectMany(x => x.Value).Distinct().ToArray(); - if (allParentIdsAsArray.Any()) - { - var allParentContentTypes = contentTypes.Where(x => allParentIdsAsArray.Contains(x.Id)).ToArray(); - - foreach (var contentType in contentTypes) - { - var entityId = contentType.Id; - - var parentContentTypes = allParentContentTypes.Where(x => - { - var parentEntityId = x.Id; - - return allParentContentTypeIds[entityId].Contains(parentEntityId); - }); - foreach (var parentContentType in parentContentTypes) - { - var result = contentType.AddContentType(parentContentType); - //Do something if adding fails? (Should hopefully not be possible unless someone created a circular reference) - } - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)contentType).ResetDirtyProperties(false); - } - } - } - - - } - - internal static void MapContentTypeTemplates(IContentType[] contentTypes, - Database db, - TRepo contentTypeRepository, - ITemplateRepository templateRepository, - IDictionary> associatedTemplates) - where TRepo : IReadRepository - { - if (associatedTemplates == null || associatedTemplates.Any() == false) return; - - //NOTE: SQL call #3++ - //SEE: http://issues.umbraco.org/issue/U4-5174 to fix this - - var templateIds = associatedTemplates.SelectMany(x => x.Value).Select(x => x.TemplateId) - .Distinct() - .ToArray(); - - var templates = (templateIds.Any() - ? templateRepository.GetAll(templateIds) - : Enumerable.Empty()).ToArray(); - - foreach (var contentType in contentTypes) - { - var entityId = contentType.Id; - - var associatedTemplateIds = associatedTemplates[entityId].Select(x => x.TemplateId) - .Distinct() - .ToArray(); - - contentType.AllowedTemplates = (associatedTemplateIds.Any() - ? templates.Where(x => associatedTemplateIds.Contains(x.Id)) - : Enumerable.Empty()).ToArray(); - } - - - } - - internal static IEnumerable MapMediaTypes(Database db, ISqlSyntaxProvider sqlSyntax, - out IDictionary> parentMediaTypeIds) - { - Mandate.ParameterNotNull(db, "db"); - - var sql = @"SELECT cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, - cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, - AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, - ParentTypes.parentContentTypeId as chtParentId, ParentTypes.parentContentTypeKey as chtParentKey, - umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, - umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed, - umbracoNode.uniqueID as nUniqueId - FROM cmsContentType - INNER JOIN umbracoNode - ON cmsContentType.nodeId = umbracoNode.id - LEFT JOIN ( - SELECT cmsContentTypeAllowedContentType.Id, cmsContentTypeAllowedContentType.AllowedId, cmsContentType.alias, cmsContentTypeAllowedContentType.SortOrder - FROM cmsContentTypeAllowedContentType - INNER JOIN cmsContentType - ON cmsContentTypeAllowedContentType.AllowedId = cmsContentType.nodeId - ) AllowedTypes - ON AllowedTypes.Id = cmsContentType.nodeId - LEFT JOIN ( - SELECT cmsContentType2ContentType.parentContentTypeId, umbracoNode.uniqueID AS parentContentTypeKey, cmsContentType2ContentType.childContentTypeId - FROM cmsContentType2ContentType - INNER JOIN umbracoNode - ON cmsContentType2ContentType.parentContentTypeId = umbracoNode." + sqlSyntax.GetQuotedColumnName("id") + @" - ) ParentTypes - ON ParentTypes.childContentTypeId = cmsContentType.nodeId - WHERE (umbracoNode.nodeObjectType = @nodeObjectType) - ORDER BY ctId"; - - var result = db.Fetch(sql, new { nodeObjectType = new Guid(Constants.ObjectTypes.MediaType) }); - - if (result.Any() == false) - { - parentMediaTypeIds = null; - return Enumerable.Empty(); - } - - parentMediaTypeIds = new Dictionary>(); - var mappedMediaTypes = new List(); - - //loop through each result and fill in our required values, each row will contain different requried data than the rest. - // it is much quicker to iterate each result and populate instead of looking up the values over and over in the result like - // we used to do. - var queue = new Queue(result); - var currAllowedContentTypes = new List(); - - while (queue.Count > 0) - { - var ct = queue.Dequeue(); - - //check for allowed content types - int? allowedCtId = ct.ctaAllowedId; - int? allowedCtSort = ct.ctaSortOrder; - string allowedCtAlias = ct.ctaAlias; - if (allowedCtId.HasValue && allowedCtSort.HasValue && allowedCtAlias != null) - { - var ctSort = new ContentTypeSort(new Lazy(() => allowedCtId.Value), allowedCtSort.Value, allowedCtAlias); - if (currAllowedContentTypes.Contains(ctSort) == false) - { - currAllowedContentTypes.Add(ctSort); - } - } - - //always ensure there's a list for this content type - if (parentMediaTypeIds.ContainsKey(ct.ctId) == false) - parentMediaTypeIds[ct.ctId] = new List(); - - //check for parent ids and assign to the outgoing collection - int? parentId = ct.chtParentId; - if (parentId.HasValue) - { - var associatedParentIds = parentMediaTypeIds[ct.ctId]; - if (associatedParentIds.Contains(parentId.Value) == false) - associatedParentIds.Add(parentId.Value); - } - - if (queue.Count == 0 || queue.Peek().ctId != ct.ctId) - { - //it's the last in the queue or the content type is changing (moving to the next one) - var mediaType = CreateForMapping(ct, currAllowedContentTypes); - mappedMediaTypes.Add(mediaType); - - //Here we need to reset the current variables, we're now collecting data for a different content type - currAllowedContentTypes = new List(); - } - } - - return mappedMediaTypes; - } - - private static IMediaType CreateForMapping(dynamic currCt, List currAllowedContentTypes) - { - // * create the DTO object - // * create the content type object - // * map the allowed content types - // * add to the outgoing list - - var contentTypeDto = new ContentTypeDto - { - Alias = currCt.ctAlias, - AllowAtRoot = currCt.ctAllowAtRoot, - Description = currCt.ctDesc, - Icon = currCt.ctIcon, - IsContainer = currCt.ctIsContainer, - NodeId = currCt.ctId, - PrimaryKey = currCt.ctPk, - Thumbnail = currCt.ctThumb, - //map the underlying node dto - NodeDto = new NodeDto - { - CreateDate = currCt.nCreateDate, - Level = (short)currCt.nLevel, - NodeId = currCt.ctId, - NodeObjectType = currCt.nObjectType, - ParentId = currCt.nParentId, - Path = currCt.nPath, - SortOrder = currCt.nSortOrder, - Text = currCt.nName, - Trashed = currCt.nTrashed, - UniqueId = currCt.nUniqueId, - UserId = currCt.nUser - } - }; - - //now create the content type object - - var factory = new ContentTypeFactory(); - var mediaType = factory.BuildMediaTypeEntity(contentTypeDto); - - //map the allowed content types - mediaType.AllowedContentTypes = currAllowedContentTypes; - - return mediaType; - } - - internal static IEnumerable MapContentTypes(Database db, ISqlSyntaxProvider sqlSyntax, - out IDictionary> associatedTemplates, - out IDictionary> parentContentTypeIds) - { - Mandate.ParameterNotNull(db, "db"); - - var sql = @"SELECT cmsDocumentType.IsDefault as dtIsDefault, cmsDocumentType.templateNodeId as dtTemplateId, - cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, - cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, - AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, - ParentTypes.parentContentTypeId as chtParentId,ParentTypes.parentContentTypeKey as chtParentKey, - umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, - umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed, - umbracoNode.uniqueID as nUniqueId, - Template.alias as tAlias, Template.nodeId as tId,Template.text as tText - FROM cmsContentType - INNER JOIN umbracoNode - ON cmsContentType.nodeId = umbracoNode.id - LEFT JOIN cmsDocumentType - ON cmsDocumentType.contentTypeNodeId = cmsContentType.nodeId - LEFT JOIN ( - SELECT cmsContentTypeAllowedContentType.Id, cmsContentTypeAllowedContentType.AllowedId, cmsContentType.alias, cmsContentTypeAllowedContentType.SortOrder - FROM cmsContentTypeAllowedContentType - INNER JOIN cmsContentType - ON cmsContentTypeAllowedContentType.AllowedId = cmsContentType.nodeId - ) AllowedTypes - ON AllowedTypes.Id = cmsContentType.nodeId - LEFT JOIN ( - SELECT * FROM cmsTemplate - INNER JOIN umbracoNode - ON cmsTemplate.nodeId = umbracoNode.id - ) as Template - ON Template.nodeId = cmsDocumentType.templateNodeId - LEFT JOIN ( - SELECT cmsContentType2ContentType.parentContentTypeId, umbracoNode.uniqueID AS parentContentTypeKey, cmsContentType2ContentType.childContentTypeId - FROM cmsContentType2ContentType - INNER JOIN umbracoNode - ON cmsContentType2ContentType.parentContentTypeId = umbracoNode." + sqlSyntax.GetQuotedColumnName("id") + @" - ) ParentTypes - ON ParentTypes.childContentTypeId = cmsContentType.nodeId - WHERE (umbracoNode.nodeObjectType = @nodeObjectType) - ORDER BY ctId"; - - var result = db.Fetch(sql, new { nodeObjectType = new Guid(Constants.ObjectTypes.DocumentType)}); - - if (result.Any() == false) - { - parentContentTypeIds = null; - associatedTemplates = null; - return Enumerable.Empty(); - } - - parentContentTypeIds = new Dictionary>(); - associatedTemplates = new Dictionary>(); - var mappedContentTypes = new List(); - - var queue = new Queue(result); - var currDefaultTemplate = -1; - var currAllowedContentTypes = new List(); - while (queue.Count > 0) - { - var ct = queue.Dequeue(); - - //check for default templates - bool? isDefaultTemplate = Convert.ToBoolean(ct.dtIsDefault); - int? templateId = ct.dtTemplateId; - if (currDefaultTemplate == -1 && isDefaultTemplate.HasValue && isDefaultTemplate.Value && templateId.HasValue) - { - currDefaultTemplate = templateId.Value; - } - - //always ensure there's a list for this content type - if (associatedTemplates.ContainsKey(ct.ctId) == false) - associatedTemplates[ct.ctId] = new List(); - - //check for associated templates and assign to the outgoing collection - if (ct.tId != null) - { - var associatedTemplate = new AssociatedTemplate(ct.tId, ct.tAlias, ct.tText); - var associatedList = associatedTemplates[ct.ctId]; - - if (associatedList.Contains(associatedTemplate) == false) - associatedList.Add(associatedTemplate); - } - - //check for allowed content types - int? allowedCtId = ct.ctaAllowedId; - int? allowedCtSort = ct.ctaSortOrder; - string allowedCtAlias = ct.ctaAlias; - if (allowedCtId.HasValue && allowedCtSort.HasValue && allowedCtAlias != null) - { - var ctSort = new ContentTypeSort(new Lazy(() => allowedCtId.Value), allowedCtSort.Value, allowedCtAlias); - if (currAllowedContentTypes.Contains(ctSort) == false) - { - currAllowedContentTypes.Add(ctSort); - } - } - - //always ensure there's a list for this content type - if (parentContentTypeIds.ContainsKey(ct.ctId) == false) - parentContentTypeIds[ct.ctId] = new List(); - - //check for parent ids and assign to the outgoing collection - int? parentId = ct.chtParentId; - if (parentId.HasValue) - { - var associatedParentIds = parentContentTypeIds[ct.ctId]; - - if (associatedParentIds.Contains(parentId.Value) == false) - associatedParentIds.Add(parentId.Value); - } - - if (queue.Count == 0 || queue.Peek().ctId != ct.ctId) - { - //it's the last in the queue or the content type is changing (moving to the next one) - var contentType = CreateForMapping(ct, currAllowedContentTypes, currDefaultTemplate); - mappedContentTypes.Add(contentType); - - //Here we need to reset the current variables, we're now collecting data for a different content type - currDefaultTemplate = -1; - currAllowedContentTypes = new List(); - } - } - - return mappedContentTypes; - } - - private static IContentType CreateForMapping(dynamic currCt, List currAllowedContentTypes, int currDefaultTemplate) - { - // * set the default template to the first one if a default isn't found - // * create the DTO object - // * create the content type object - // * map the allowed content types - // * add to the outgoing list - - var dtDto = new ContentTypeTemplateDto - { - //create the content type dto - ContentTypeDto = new ContentTypeDto - { - Alias = currCt.ctAlias, - AllowAtRoot = currCt.ctAllowAtRoot, - Description = currCt.ctDesc, - Icon = currCt.ctIcon, - IsContainer = currCt.ctIsContainer, - NodeId = currCt.ctId, - PrimaryKey = currCt.ctPk, - Thumbnail = currCt.ctThumb, - //map the underlying node dto - NodeDto = new NodeDto - { - CreateDate = currCt.nCreateDate, - Level = (short)currCt.nLevel, - NodeId = currCt.ctId, - NodeObjectType = currCt.nObjectType, - ParentId = currCt.nParentId, - Path = currCt.nPath, - SortOrder = currCt.nSortOrder, - Text = currCt.nName, - Trashed = currCt.nTrashed, - UniqueId = currCt.nUniqueId, - UserId = currCt.nUser - } - }, - ContentTypeNodeId = currCt.ctId, - IsDefault = currDefaultTemplate != -1, - TemplateNodeId = currDefaultTemplate != -1 ? currDefaultTemplate : 0, - }; - - //now create the content type object - - var factory = new ContentTypeFactory(); - var contentType = factory.BuildContentTypeEntity(dtDto.ContentTypeDto); - - // NOTE - // that was done by the factory but makes little sense, moved here, so - // now we have to reset dirty props again (as the factory does it) and yet, - // we are not managing allowed templates... the whole thing is weird. - ((ContentType)contentType).DefaultTemplateId = dtDto.TemplateNodeId; - contentType.ResetDirtyProperties(false); - - //map the allowed content types - contentType.AllowedContentTypes = currAllowedContentTypes; - - return contentType; - } - - internal static void MapGroupsAndProperties(int[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax, - out IDictionary allPropertyTypeCollection, - out IDictionary allPropertyGroupCollection) - { - allPropertyGroupCollection = new Dictionary(); - allPropertyTypeCollection = new Dictionary(); - - // query below is not safe + pointless if array is empty - if (contentTypeIds.Length == 0) return; - - // first part Gets all property groups including property type data even when no property type exists on the group - // second part Gets all property types including ones that are not on a group - // therefore the union of the two contains all of the property type and property group information we need - // NOTE: MySQL requires a SELECT * FROM the inner union in order to be able to sort . lame. - - var sqlBuilder = new StringBuilder(@"SELECT PG.contenttypeNodeId as contentTypeId, - PT.ptUniqueId as ptUniqueID, PT.ptId, PT.ptAlias, PT.ptDesc,PT.ptMandatory,PT.ptName,PT.ptSortOrder,PT.ptRegExp, - PT.dtId,PT.dtDbType,PT.dtPropEdAlias, - PG.id as pgId, PG.uniqueID as pgKey, PG.sortorder as pgSortOrder, PG." + sqlSyntax.GetQuotedColumnName("text") + @" as pgText - FROM cmsPropertyTypeGroup as PG - LEFT JOIN - ( - SELECT PT.uniqueID as ptUniqueId, PT.id as ptId, PT.Alias as ptAlias, PT." + sqlSyntax.GetQuotedColumnName("Description") + @" as ptDesc, - PT.mandatory as ptMandatory, PT.Name as ptName, PT.sortOrder as ptSortOrder, PT.validationRegExp as ptRegExp, - PT.propertyTypeGroupId as ptGroupId, - DT.dbType as dtDbType, DT.nodeId as dtId, DT.propertyEditorAlias as dtPropEdAlias - FROM cmsPropertyType as PT - INNER JOIN cmsDataType as DT - ON PT.dataTypeId = DT.nodeId - ) as PT - ON PT.ptGroupId = PG.id - WHERE (PG.contenttypeNodeId in (@contentTypeIds)) - - UNION - - SELECT PT.contentTypeId as contentTypeId, - PT.uniqueID as ptUniqueID, PT.id as ptId, PT.Alias as ptAlias, PT." + sqlSyntax.GetQuotedColumnName("Description") + @" as ptDesc, - PT.mandatory as ptMandatory, PT.Name as ptName, PT.sortOrder as ptSortOrder, PT.validationRegExp as ptRegExp, - DT.nodeId as dtId, DT.dbType as dtDbType, DT.propertyEditorAlias as dtPropEdAlias, - PG.id as pgId, PG.uniqueID as pgKey, PG.sortorder as pgSortOrder, PG." + sqlSyntax.GetQuotedColumnName("text") + @" as pgText - FROM cmsPropertyType as PT - INNER JOIN cmsDataType as DT - ON PT.dataTypeId = DT.nodeId - LEFT JOIN cmsPropertyTypeGroup as PG - ON PG.id = PT.propertyTypeGroupId - WHERE (PT.contentTypeId in (@contentTypeIds))"); - - sqlBuilder.AppendLine(" ORDER BY (pgId)"); - - //NOTE: we are going to assume there's not going to be more than 2100 content type ids since that is the max SQL param count! - // Since there are 2 groups of params, it will be half! - if (((contentTypeIds.Length / 2) - 1) > 2000) - throw new InvalidOperationException("Cannot perform this lookup, too many sql parameters"); - - var result = db.Fetch(sqlBuilder.ToString(), new { contentTypeIds = contentTypeIds }); - - foreach (var contentTypeId in contentTypeIds) - { - //from this we need to make : - // * PropertyGroupCollection - Contains all property groups along with all property types associated with a group - // * PropertyTypeCollection - Contains all property types that do not belong to a group - - //create the property group collection first, this means all groups (even empty ones) and all groups with properties - - int currId = contentTypeId; - - var propertyGroupCollection = new PropertyGroupCollection(result - //get all rows that have a group id - .Where(x => x.pgId != null) - //filter based on the current content type - .Where(x => x.contentTypeId == currId) - //turn that into a custom object containing only the group info - .Select(x => new { GroupId = x.pgId, SortOrder = x.pgSortOrder, Text = x.pgText, Key = x.pgKey }) - //get distinct data by id - .DistinctBy(x => (int)x.GroupId) - //for each of these groups, create a group object with it's associated properties - .Select(group => new PropertyGroup(new PropertyTypeCollection( - result - .Where(row => row.pgId == group.GroupId && row.ptId != null) - .Select(row => new PropertyType(row.dtPropEdAlias, Enum.Parse(row.dtDbType), row.ptAlias) - { - //fill in the rest of the property type properties - Description = row.ptDesc, - DataTypeDefinitionId = row.dtId, - Id = row.ptId, - Key = row.ptUniqueID, - Mandatory = Convert.ToBoolean(row.ptMandatory), - Name = row.ptName, - PropertyGroupId = new Lazy(() => group.GroupId, false), - SortOrder = row.ptSortOrder, - ValidationRegExp = row.ptRegExp - }))) - { - //fill in the rest of the group properties - Id = group.GroupId, - Name = group.Text, - SortOrder = group.SortOrder, - Key = group.Key - }).ToArray()); - - allPropertyGroupCollection[currId] = propertyGroupCollection; - - //Create the property type collection now (that don't have groups) - - var propertyTypeCollection = new PropertyTypeCollection(result - .Where(x => x.pgId == null) - //filter based on the current content type - .Where(x => x.contentTypeId == currId) - .Select(row => new PropertyType(row.dtPropEdAlias, Enum.Parse(row.dtDbType), row.ptAlias) - { - //fill in the rest of the property type properties - Description = row.ptDesc, - DataTypeDefinitionId = row.dtId, - Id = row.ptId, - Key = row.ptUniqueID, - Mandatory = Convert.ToBoolean(row.ptMandatory), - Name = row.ptName, - PropertyGroupId = null, - SortOrder = row.ptSortOrder, - ValidationRegExp = row.ptRegExp - }).ToArray()); - - allPropertyTypeCollection[currId] = propertyTypeCollection; - } - - - } - - } - - protected abstract TEntity PerformGet(Guid id); - protected abstract TEntity PerformGet(string alias); - protected abstract IEnumerable PerformGetAll(params Guid[] ids); - protected abstract bool PerformExists(Guid id); - - /// - /// Gets an Entity by alias - /// - /// - /// - public TEntity Get(string alias) - { - return PerformGet(alias); - } - - /// - /// Gets an Entity by Id - /// - /// - /// - public TEntity Get(Guid id) - { - return PerformGet(id); - } - - /// - /// Gets all entities of the spefified type - /// - /// - /// - /// - /// Ensure explicit implementation, we don't want to have any accidental calls to this since it is essentially the same signature as the main GetAll when there are no parameters - /// - IEnumerable IReadRepository.GetAll(params Guid[] ids) - { - return PerformGetAll(ids); - } - - /// - /// Boolean indicating whether an Entity with the specified Id exists - /// - /// - /// - public bool Exists(Guid id) - { - return PerformExists(id); - } - - public string GetUniqueAlias(string alias) - { - // alias is unique accross ALL content types! - var aliasColumn = SqlSyntax.GetQuotedColumnName("alias"); - var aliases = Database.Fetch(@"SELECT cmsContentType." + aliasColumn + @" FROM cmsContentType -INNER JOIN umbracoNode ON cmsContentType.nodeId = umbracoNode.id -WHERE cmsContentType." + aliasColumn + @" LIKE @pattern", - new { pattern = alias + "%", objectType = NodeObjectTypeId }); - var i = 1; - string test; - while (aliases.Contains(test = alias + i)) i++; - return test; - } - } -} +using System; +using System.Collections.Generic; +using System.Data; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NPoco; +using Umbraco.Core.Events; +using Umbraco.Core.Exceptions; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Rdbms; + +using Umbraco.Core.Persistence.Factories; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Services; + +namespace Umbraco.Core.Persistence.Repositories +{ + /// + /// Represent an abstract Repository for ContentType based repositories + /// + /// Exposes shared functionality + /// + internal abstract class ContentTypeRepositoryBase : NPocoRepositoryBase, IReadRepository + where TEntity : class, IContentTypeComposition + { + protected ContentTypeRepositoryBase(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, IMappingResolver mappingResolver) + : base(work, cache, logger, mappingResolver) + { + } + + public IEnumerable> Move(TEntity moving, EntityContainer container) + { + var parentId = Constants.System.Root; + if (container != null) + { + // check path + if ((string.Format(",{0},", container.Path)).IndexOf(string.Format(",{0},", moving.Id), StringComparison.Ordinal) > -1) + throw new DataOperationException(MoveOperationStatusType.FailedNotAllowedByPath); + + parentId = container.Id; + } + + // track moved entities + var moveInfo = new List> + { + new MoveEventInfo(moving, moving.Path, parentId) + }; + + + // get the level delta (old pos to new pos) + var levelDelta = container == null + ? 1 - moving.Level + : container.Level + 1 - moving.Level; + + // move to parent (or -1), update path, save + moving.ParentId = parentId; + var movingPath = moving.Path + ","; // save before changing + moving.Path = (container == null ? Constants.System.Root.ToString() : container.Path) + "," + moving.Id; + moving.Level = container == null ? 1 : container.Level + 1; + AddOrUpdate(moving); + + //update all descendants, update in order of level + var descendants = GetByQuery(Query.Where(type => type.Path.StartsWith(movingPath))); + var paths = new Dictionary(); + paths[moving.Id] = moving.Path; + + foreach (var descendant in descendants.OrderBy(x => x.Level)) + { + moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); + + descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id; + descendant.Level += levelDelta; + + AddOrUpdate(descendant); + } + + return moveInfo; + } + /// + /// Returns the content type ids that match the query + /// + /// + /// + protected IEnumerable PerformGetByQuery(IQuery query) + { + // used by DataTypeDefinitionRepository to remove properties + // from content types if they have a deleted data type - see + // notes in DataTypeDefinitionRepository.Delete as it's a bit + // weird + + var sqlClause = Sql() + .SelectAll() + .From() + .RightJoin() + .On(left => left.Id, right => right.PropertyTypeGroupId) + .InnerJoin() + .On(left => left.DataTypeId, right => right.DataTypeId); + + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate() + .OrderBy(x => x.PropertyTypeGroupId); + + return Database + .FetchOneToMany(x => x.PropertyTypeDtos, sql) + .Select(x => x.ContentTypeNodeId).Distinct(); + } + + protected virtual PropertyType CreatePropertyType(string propertyEditorAlias, DataTypeDatabaseType dbType, string propertyTypeAlias) + { + return new PropertyType(propertyEditorAlias, dbType, propertyTypeAlias); + } + + protected void PersistNewBaseContentType(IContentTypeComposition entity) + { + var factory = new ContentTypeFactory(); + var dto = factory.BuildContentTypeDto(entity); + + //Cannot add a duplicate content type type + var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsContentType +INNER JOIN umbracoNode ON cmsContentType.nodeId = umbracoNode.id +WHERE cmsContentType." + SqlSyntax.GetQuotedColumnName("alias") + @"= @alias +AND umbracoNode.nodeObjectType = @objectType", + new { alias = entity.Alias, objectType = NodeObjectTypeId }); + if (exists > 0) + { + throw new DuplicateNameException("An item with the alias " + entity.Alias + " already exists"); + } + + //Logic for setting Path, Level and SortOrder + var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId }); + int level = parent.Level + 1; + int sortOrder = + Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoNode WHERE parentID = @ParentId AND nodeObjectType = @NodeObjectType", + new { ParentId = entity.ParentId, NodeObjectType = NodeObjectTypeId }); + + //Create the (base) node data - umbracoNode + var nodeDto = dto.NodeDto; + nodeDto.Path = parent.Path; + nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture)); + nodeDto.SortOrder = sortOrder; + var o = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto); + + //Update with new correct path + nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); + Database.Update(nodeDto); + + //Update entity with correct values + entity.Id = nodeDto.NodeId; //Set Id on entity to ensure an Id is set + entity.Path = nodeDto.Path; + entity.SortOrder = sortOrder; + entity.Level = level; + + //Insert new ContentType entry + dto.NodeId = nodeDto.NodeId; + Database.Insert(dto); + + //Insert ContentType composition in new table + foreach (var composition in entity.ContentTypeComposition) + { + if (composition.Id == entity.Id) continue;//Just to ensure that we aren't creating a reference to ourself. + + if (composition.HasIdentity) + { + Database.Insert(new ContentType2ContentTypeDto { ParentId = composition.Id, ChildId = entity.Id }); + } + else + { + //Fallback for ContentTypes with no identity + var contentTypeDto = Database.FirstOrDefault("WHERE alias = @Alias", new { Alias = composition.Alias }); + if (contentTypeDto != null) + { + Database.Insert(new ContentType2ContentTypeDto { ParentId = contentTypeDto.NodeId, ChildId = entity.Id }); + } + } + } + + //Insert collection of allowed content types + foreach (var allowedContentType in entity.AllowedContentTypes) + { + Database.Insert(new ContentTypeAllowedContentTypeDto + { + Id = entity.Id, + AllowedId = allowedContentType.Id.Value, + SortOrder = allowedContentType.SortOrder + }); + } + + var propertyFactory = new PropertyGroupFactory(nodeDto.NodeId); + + //Insert Tabs + foreach (var propertyGroup in entity.PropertyGroups) + { + var tabDto = propertyFactory.BuildGroupDto(propertyGroup); + var primaryKey = Convert.ToInt32(Database.Insert(tabDto)); + propertyGroup.Id = primaryKey;//Set Id on PropertyGroup + + //Ensure that the PropertyGroup's Id is set on the PropertyTypes within a group + //unless the PropertyGroupId has already been changed. + foreach (var propertyType in propertyGroup.PropertyTypes) + { + if (propertyType.IsPropertyDirty("PropertyGroupId") == false) + { + var tempGroup = propertyGroup; + propertyType.PropertyGroupId = new Lazy(() => tempGroup.Id); + } + } + } + + //Insert PropertyTypes + foreach (var propertyType in entity.PropertyTypes) + { + var tabId = propertyType.PropertyGroupId != null ? propertyType.PropertyGroupId.Value : default(int); + //If the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias + if (propertyType.DataTypeDefinitionId == 0 || propertyType.DataTypeDefinitionId == default(int)) + { + AssignDataTypeFromPropertyEditor(propertyType); + } + var propertyTypeDto = propertyFactory.BuildPropertyTypeDto(tabId, propertyType); + int typePrimaryKey = Convert.ToInt32(Database.Insert(propertyTypeDto)); + propertyType.Id = typePrimaryKey; //Set Id on new PropertyType + + //Update the current PropertyType with correct PropertyEditorAlias and DatabaseType + var dataTypeDto = Database.FirstOrDefault("WHERE nodeId = @Id", new { Id = propertyTypeDto.DataTypeId }); + propertyType.PropertyEditorAlias = dataTypeDto.PropertyEditorAlias; + propertyType.DataTypeDatabaseType = dataTypeDto.DbType.EnumParse(true); + } + } + + protected void PersistUpdatedBaseContentType(IContentTypeComposition entity) + { + var factory = new ContentTypeFactory(); + var dto = factory.BuildContentTypeDto(entity); + + // ensure the alias is not used already + var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsContentType +INNER JOIN umbracoNode ON cmsContentType.nodeId = umbracoNode.id +WHERE cmsContentType." + SqlSyntax.GetQuotedColumnName("alias") + @"= @alias +AND umbracoNode.nodeObjectType = @objectType +AND umbracoNode.id <> @id", + new { id = dto.NodeId, alias = dto.Alias, objectType = NodeObjectTypeId }); + if (exists > 0) + { + throw new DuplicateNameException("An item with the alias " + dto.Alias + " already exists"); + } + + // repository should be write-locked when doing this, so we are safe from race-conds + // handle (update) the node + var nodeDto = dto.NodeDto; + Database.Update(nodeDto); + + // we NEED this: updating, so the .PrimaryKey already exists, but the entity does + // not carry it and therefore the dto does not have it yet - must get it from db + //Look up ContentType entry to get PrimaryKey for updating the DTO + var dtoPk = Database.First("WHERE nodeId = @Id", new { Id = entity.Id }); + dto.PrimaryKey = dtoPk.PrimaryKey; + Database.Update(dto); + + // handle (delete then recreate) compositions + Database.Delete("WHERE childContentTypeId = @Id", new { Id = entity.Id }); + foreach (var composition in entity.ContentTypeComposition) + Database.Insert(new ContentType2ContentTypeDto { ParentId = composition.Id, ChildId = entity.Id }); + + //Removing a ContentType from a composition (U4-1690) + //1. Find content based on the current ContentType: entity.Id + //2. Find all PropertyTypes on the ContentType that was removed - tracked id (key) + //3. Remove properties based on property types from the removed content type where the content ids correspond to those found in step one + var compositionBase = entity as ContentTypeCompositionBase; + if (compositionBase != null && compositionBase.RemovedContentTypeKeyTracker != null && + compositionBase.RemovedContentTypeKeyTracker.Any()) + { + //Find Content based on the current ContentType + var sql = Sql() + .SelectAll() + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == new Guid(Constants.ObjectTypes.Document)) + .Where(x => x.ContentTypeId == entity.Id); + + var contentDtos = Database.Fetch(sql); + //Loop through all tracked keys, which corresponds to the ContentTypes that has been removed from the composition + foreach (var key in compositionBase.RemovedContentTypeKeyTracker) + { + //Find PropertyTypes for the removed ContentType + var propertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new { Id = key }); + //Loop through the Content that is based on the current ContentType in order to remove the Properties that are + //based on the PropertyTypes that belong to the removed ContentType. + foreach (var contentDto in contentDtos) + { + foreach (var propertyType in propertyTypes) + { + var nodeId = contentDto.NodeId; + var propertyTypeId = propertyType.Id; + var propertySql = Sql() + .Select("cmsPropertyData.id") + .From() + .InnerJoin() + .On(left => left.PropertyTypeId, right => right.Id) + .Where(x => x.NodeId == nodeId) + .Where(x => x.Id == propertyTypeId); + + //Finally delete the properties that match our criteria for removing a ContentType from the composition + Database.Delete(new Sql("WHERE id IN (" + propertySql.SQL + ")", propertySql.Arguments)); + } + } + } + } + + //Delete the allowed content type entries before adding the updated collection + Database.Delete("WHERE Id = @Id", new { Id = entity.Id }); + //Insert collection of allowed content types + foreach (var allowedContentType in entity.AllowedContentTypes) + { + Database.Insert(new ContentTypeAllowedContentTypeDto + { + Id = entity.Id, + AllowedId = allowedContentType.Id.Value, + SortOrder = allowedContentType.SortOrder + }); + } + + + if (((ICanBeDirty)entity).IsPropertyDirty("PropertyTypes") || entity.PropertyTypes.Any(x => x.IsDirty())) + { + //Delete PropertyTypes by excepting entries from db with entries from collections + var dbPropertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new { Id = entity.Id }); + var dbPropertyTypeAlias = dbPropertyTypes.Select(x => x.Id); + var entityPropertyTypes = entity.PropertyTypes.Where(x => x.HasIdentity).Select(x => x.Id); + var items = dbPropertyTypeAlias.Except(entityPropertyTypes); + foreach (var item in items) + { + //Before a PropertyType can be deleted, all Properties based on that PropertyType should be deleted. + Database.Delete("WHERE propertyTypeId = @Id", new { Id = item }); + Database.Delete("WHERE propertytypeid = @Id", new { Id = item }); + Database.Delete("WHERE contentTypeId = @Id AND id = @PropertyTypeId", + new { Id = entity.Id, PropertyTypeId = item }); + } + } + + if (entity.IsPropertyDirty("PropertyGroups") || entity.PropertyGroups.Any(x => x.IsDirty())) + { + // todo + // we used to try to propagate tabs renaming downstream, relying on ParentId, but + // 1) ParentId makes no sense (if a tab can be inherited from multiple composition + // types) so we would need to figure things out differently, visiting downstream + // content types and looking for tabs with the same name... + // 2) It was not deployable as changing a content type changes other content types + // that was not deterministic, because it would depend on the order of the changes. + // That last point could be fixed if (1) is fixed, but then it still is an issue with + // deploy because changing a content type changes other content types that are not + // dependencies but dependents, and then what? + // + // So... for the time being, all renaming propagation is disabled. We just don't do it. + + // (all gone) + + // delete tabs that do not exist anymore + // get the tabs that are currently existing (in the db) + // get the tabs that we want, now + // and derive the tabs that we want to delete + var existingPropertyGroups = Database.Fetch("WHERE contentTypeNodeId = @id", new { id = entity.Id }) + .Select(x => x.Id) + .ToList(); + var newPropertyGroups = entity.PropertyGroups.Select(x => x.Id).ToList(); + var tabsToDelete = existingPropertyGroups + .Except(newPropertyGroups) + .ToArray(); + + // move properties to generic properties, and delete the tabs + if (tabsToDelete.Length > 0) + { + Database.Update("SET propertyTypeGroupId=NULL WHERE propertyTypeGroupId IN (@ids)", new { ids = tabsToDelete }); + Database.Delete("WHERE id IN (@ids)", new { ids = tabsToDelete }); + } + } + var propertyGroupFactory = new PropertyGroupFactory(entity.Id); + + //Run through all groups to insert or update entries + foreach (var propertyGroup in entity.PropertyGroups) + { + var tabDto = propertyGroupFactory.BuildGroupDto(propertyGroup); + int groupPrimaryKey = propertyGroup.HasIdentity + ? Database.Update(tabDto) + : Convert.ToInt32(Database.Insert(tabDto)); + if (propertyGroup.HasIdentity == false) + propertyGroup.Id = groupPrimaryKey; //Set Id on new PropertyGroup + + //Ensure that the PropertyGroup's Id is set on the PropertyTypes within a group + //unless the PropertyGroupId has already been changed. + foreach (var propertyType in propertyGroup.PropertyTypes) + { + if (propertyType.IsPropertyDirty("PropertyGroupId") == false) + { + var tempGroup = propertyGroup; + propertyType.PropertyGroupId = new Lazy(() => tempGroup.Id); + } + } + } + + //Run through all PropertyTypes to insert or update entries + foreach (var propertyType in entity.PropertyTypes) + { + var tabId = propertyType.PropertyGroupId != null ? propertyType.PropertyGroupId.Value : default(int); + //If the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias + if (propertyType.DataTypeDefinitionId == 0 || propertyType.DataTypeDefinitionId == default(int)) + { + AssignDataTypeFromPropertyEditor(propertyType); + } + + //validate the alias! + ValidateAlias(propertyType); + + var propertyTypeDto = propertyGroupFactory.BuildPropertyTypeDto(tabId, propertyType); + int typePrimaryKey = propertyType.HasIdentity + ? Database.Update(propertyTypeDto) + : Convert.ToInt32(Database.Insert(propertyTypeDto)); + if (propertyType.HasIdentity == false) + propertyType.Id = typePrimaryKey; //Set Id on new PropertyType + } + } + + protected IEnumerable GetAllowedContentTypeIds(int id) + { + var sql = Sql() + .SelectAll() + .From() + .LeftJoin() + .On(left => left.AllowedId, right => right.NodeId) + .Where(x => x.Id == id); + + var allowedContentTypeDtos = Database.Fetch(sql); + return allowedContentTypeDtos.Select(x => new ContentTypeSort(new Lazy(() => x.AllowedId), x.SortOrder, x.ContentTypeDto.Alias)).ToList(); + } + + protected PropertyGroupCollection GetPropertyGroupCollection(int id, DateTime createDate, DateTime updateDate) + { + var sql = Sql() + .SelectAll() + .From() + .LeftJoin() + .On(left => left.Id, right => right.PropertyTypeGroupId) + .LeftJoin() + .On(left => left.DataTypeId, right => right.DataTypeId) + .Where(x => x.ContentTypeNodeId == id) + .OrderBy(x => x.Id); + + + var dtos = Database + .Fetch(sql); + + var propertyGroupFactory = new PropertyGroupFactory(id, createDate, updateDate, CreatePropertyType); + var propertyGroups = propertyGroupFactory.BuildEntity(dtos); + return new PropertyGroupCollection(propertyGroups); + } + + protected PropertyTypeCollection GetPropertyTypeCollection(int id, DateTime createDate, DateTime updateDate) + { + var sql = Sql() + .SelectAll() + .From() + .InnerJoin() + .On(left => left.DataTypeId, right => right.DataTypeId) + .Where(x => x.ContentTypeId == id); + + var dtos = Database.Fetch(sql); + + //TODO Move this to a PropertyTypeFactory + var list = new List(); + foreach (var dto in dtos.Where(x => (x.PropertyTypeGroupId > 0) == false)) + { + var propType = CreatePropertyType(dto.DataTypeDto.PropertyEditorAlias, dto.DataTypeDto.DbType.EnumParse(true), dto.Alias); + propType.DataTypeDefinitionId = dto.DataTypeId; + propType.Description = dto.Description; + propType.Id = dto.Id; + propType.Key = dto.UniqueId; + propType.Name = dto.Name; + propType.Mandatory = dto.Mandatory; + propType.SortOrder = dto.SortOrder; + propType.ValidationRegExp = dto.ValidationRegExp; + propType.CreateDate = createDate; + propType.UpdateDate = updateDate; + list.Add(propType); + } + //Reset dirty properties + Parallel.ForEach(list, currentFile => currentFile.ResetDirtyProperties(false)); + + return new PropertyTypeCollection(list); + } + + protected void ValidateAlias(PropertyType pt) + { + Mandate.That(string.IsNullOrEmpty(pt.Alias) == false, + () => + { + var message = + string.Format( + "{0} '{1}' cannot have an empty Alias. This is most likely due to invalid characters stripped from the Alias.", + "Property Type", + pt.Name); + var exception = new InvalidOperationException(message); + + Logger.Error>(message, exception); + throw exception; + }); + } + + protected void ValidateAlias(TEntity entity) + { + Mandate.That(string.IsNullOrEmpty(entity.Alias) == false, + () => + { + var message = + string.Format( + "{0} '{1}' cannot have an empty Alias. This is most likely due to invalid characters stripped from the Alias.", + typeof(TEntity).Name, + entity.Name); + var exception = new InvalidOperationException(message); + + Logger.Error>(message, exception); + throw exception; + }); + } + + /// + /// Try to set the data type id based on its ControlId + /// + /// + private void AssignDataTypeFromPropertyEditor(PropertyType propertyType) + { + //we cannot try to assign a data type of it's empty + if (propertyType.PropertyEditorAlias.IsNullOrWhiteSpace() == false) + { + var sql = Sql() + .SelectAll() + .From() + .Where("propertyEditorAlias = @propertyEditorAlias", new { propertyEditorAlias = propertyType.PropertyEditorAlias }) + .OrderBy(typeDto => typeDto.DataTypeId); + var datatype = Database.FirstOrDefault(sql); + //we cannot assign a data type if one was not found + if (datatype != null) + { + propertyType.DataTypeDefinitionId = datatype.DataTypeId; + } + else + { + Logger.Warn>("Could not assign a data type for the property type " + propertyType.Alias + " since no data type was found with a property editor " + propertyType.PropertyEditorAlias); + } + } + } + + public IEnumerable GetTypesDirectlyComposedOf(int id) + { + var sql = Sql() + .SelectAll() + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.ChildId) + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.ParentId == id); + var dtos = Database.Fetch(sql); + return dtos.Any() + ? GetAll(dtos.DistinctBy(x => x.NodeId).Select(x => x.NodeId).ToArray()) + : Enumerable.Empty(); + } + + internal static class ContentTypeQueryMapper + { + + public class AssociatedTemplate + { + public AssociatedTemplate(int templateId, string @alias, string templateName) + { + TemplateId = templateId; + Alias = alias; + TemplateName = templateName; + } + + public int TemplateId { get; set; } + public string Alias { get; set; } + public string TemplateName { get; set; } + + protected bool Equals(AssociatedTemplate other) + { + return TemplateId == other.TemplateId; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((AssociatedTemplate)obj); + } + + public override int GetHashCode() + { + return TemplateId; + } + } + + public static IEnumerable GetMediaTypes( + Database db, ISqlSyntaxProvider sqlSyntax, + TRepo contentTypeRepository) + where TRepo : IReadRepository + { + IDictionary> allParentMediaTypeIds; + var mediaTypes = MapMediaTypes(db, sqlSyntax, out allParentMediaTypeIds) + .ToArray(); + + MapContentTypeChildren(mediaTypes, db, sqlSyntax, contentTypeRepository, allParentMediaTypeIds); + + return mediaTypes; + } + + public static IEnumerable GetContentTypes( + Database db, ISqlSyntaxProvider sqlSyntax, + TRepo contentTypeRepository, + ITemplateRepository templateRepository) + where TRepo : IReadRepository + { + IDictionary> allAssociatedTemplates; + IDictionary> allParentContentTypeIds; + var contentTypes = MapContentTypes(db, sqlSyntax, out allAssociatedTemplates, out allParentContentTypeIds) + .ToArray(); + + if (contentTypes.Any()) + { + MapContentTypeTemplates( + contentTypes, db, contentTypeRepository, templateRepository, allAssociatedTemplates); + + MapContentTypeChildren( + contentTypes, db, sqlSyntax, contentTypeRepository, allParentContentTypeIds); + } + + return contentTypes; + } + + internal static void MapContentTypeChildren(IContentTypeComposition[] contentTypes, + Database db, ISqlSyntaxProvider sqlSyntax, + TRepo contentTypeRepository, + IDictionary> allParentContentTypeIds) + where TRepo : IReadRepository + { + //NOTE: SQL call #2 + + var ids = contentTypes.Select(x => x.Id).ToArray(); + IDictionary allPropGroups; + IDictionary allPropTypes; + MapGroupsAndProperties(ids, db, sqlSyntax, out allPropTypes, out allPropGroups); + + foreach (var contentType in contentTypes) + { + contentType.PropertyGroups = allPropGroups[contentType.Id]; + contentType.NoGroupPropertyTypes = allPropTypes[contentType.Id]; + } + + //NOTE: SQL call #3++ + + if (allParentContentTypeIds != null) + { + var allParentIdsAsArray = allParentContentTypeIds.SelectMany(x => x.Value).Distinct().ToArray(); + if (allParentIdsAsArray.Any()) + { + var allParentContentTypes = contentTypes.Where(x => allParentIdsAsArray.Contains(x.Id)).ToArray(); + + foreach (var contentType in contentTypes) + { + var entityId = contentType.Id; + + var parentContentTypes = allParentContentTypes.Where(x => + { + var parentEntityId = x.Id; + + return allParentContentTypeIds[entityId].Contains(parentEntityId); + }); + foreach (var parentContentType in parentContentTypes) + { + var result = contentType.AddContentType(parentContentType); + //Do something if adding fails? (Should hopefully not be possible unless someone created a circular reference) + } + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity)contentType).ResetDirtyProperties(false); + } + } + } + + + } + + internal static void MapContentTypeTemplates(IContentType[] contentTypes, + Database db, + TRepo contentTypeRepository, + ITemplateRepository templateRepository, + IDictionary> associatedTemplates) + where TRepo : IReadRepository + { + if (associatedTemplates == null || associatedTemplates.Any() == false) return; + + //NOTE: SQL call #3++ + //SEE: http://issues.umbraco.org/issue/U4-5174 to fix this + + var templateIds = associatedTemplates.SelectMany(x => x.Value).Select(x => x.TemplateId) + .Distinct() + .ToArray(); + + var templates = (templateIds.Any() + ? templateRepository.GetAll(templateIds) + : Enumerable.Empty()).ToArray(); + + foreach (var contentType in contentTypes) + { + var entityId = contentType.Id; + + var associatedTemplateIds = associatedTemplates[entityId].Select(x => x.TemplateId) + .Distinct() + .ToArray(); + + contentType.AllowedTemplates = (associatedTemplateIds.Any() + ? templates.Where(x => associatedTemplateIds.Contains(x.Id)) + : Enumerable.Empty()).ToArray(); + } + + + } + + internal static IEnumerable MapMediaTypes(Database db, ISqlSyntaxProvider sqlSyntax, + out IDictionary> parentMediaTypeIds) + { + Mandate.ParameterNotNull(db, "db"); + + var sql = @"SELECT cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, + cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, + AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, + ParentTypes.parentContentTypeId as chtParentId, ParentTypes.parentContentTypeKey as chtParentKey, + umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, + umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed, + umbracoNode.uniqueID as nUniqueId + FROM cmsContentType + INNER JOIN umbracoNode + ON cmsContentType.nodeId = umbracoNode.id + LEFT JOIN ( + SELECT cmsContentTypeAllowedContentType.Id, cmsContentTypeAllowedContentType.AllowedId, cmsContentType.alias, cmsContentTypeAllowedContentType.SortOrder + FROM cmsContentTypeAllowedContentType + INNER JOIN cmsContentType + ON cmsContentTypeAllowedContentType.AllowedId = cmsContentType.nodeId + ) AllowedTypes + ON AllowedTypes.Id = cmsContentType.nodeId + LEFT JOIN ( + SELECT cmsContentType2ContentType.parentContentTypeId, umbracoNode.uniqueID AS parentContentTypeKey, cmsContentType2ContentType.childContentTypeId + FROM cmsContentType2ContentType + INNER JOIN umbracoNode + ON cmsContentType2ContentType.parentContentTypeId = umbracoNode." + sqlSyntax.GetQuotedColumnName("id") + @" + ) ParentTypes + ON ParentTypes.childContentTypeId = cmsContentType.nodeId + WHERE (umbracoNode.nodeObjectType = @nodeObjectType) + ORDER BY ctId"; + + var result = db.Fetch(sql, new { nodeObjectType = new Guid(Constants.ObjectTypes.MediaType) }); + + if (result.Any() == false) + { + parentMediaTypeIds = null; + return Enumerable.Empty(); + } + + parentMediaTypeIds = new Dictionary>(); + var mappedMediaTypes = new List(); + + //loop through each result and fill in our required values, each row will contain different requried data than the rest. + // it is much quicker to iterate each result and populate instead of looking up the values over and over in the result like + // we used to do. + var queue = new Queue(result); + var currAllowedContentTypes = new List(); + + while (queue.Count > 0) + { + var ct = queue.Dequeue(); + + //check for allowed content types + int? allowedCtId = ct.ctaAllowedId; + int? allowedCtSort = ct.ctaSortOrder; + string allowedCtAlias = ct.ctaAlias; + if (allowedCtId.HasValue && allowedCtSort.HasValue && allowedCtAlias != null) + { + var ctSort = new ContentTypeSort(new Lazy(() => allowedCtId.Value), allowedCtSort.Value, allowedCtAlias); + if (currAllowedContentTypes.Contains(ctSort) == false) + { + currAllowedContentTypes.Add(ctSort); + } + } + + //always ensure there's a list for this content type + if (parentMediaTypeIds.ContainsKey(ct.ctId) == false) + parentMediaTypeIds[ct.ctId] = new List(); + + //check for parent ids and assign to the outgoing collection + int? parentId = ct.chtParentId; + if (parentId.HasValue) + { + var associatedParentIds = parentMediaTypeIds[ct.ctId]; + if (associatedParentIds.Contains(parentId.Value) == false) + associatedParentIds.Add(parentId.Value); + } + + if (queue.Count == 0 || queue.Peek().ctId != ct.ctId) + { + //it's the last in the queue or the content type is changing (moving to the next one) + var mediaType = CreateForMapping(ct, currAllowedContentTypes); + mappedMediaTypes.Add(mediaType); + + //Here we need to reset the current variables, we're now collecting data for a different content type + currAllowedContentTypes = new List(); + } + } + + return mappedMediaTypes; + } + + private static IMediaType CreateForMapping(dynamic currCt, List currAllowedContentTypes) + { + // * create the DTO object + // * create the content type object + // * map the allowed content types + // * add to the outgoing list + + var contentTypeDto = new ContentTypeDto + { + Alias = currCt.ctAlias, + AllowAtRoot = currCt.ctAllowAtRoot, + Description = currCt.ctDesc, + Icon = currCt.ctIcon, + IsContainer = currCt.ctIsContainer, + NodeId = currCt.ctId, + PrimaryKey = currCt.ctPk, + Thumbnail = currCt.ctThumb, + //map the underlying node dto + NodeDto = new NodeDto + { + CreateDate = currCt.nCreateDate, + Level = (short)currCt.nLevel, + NodeId = currCt.ctId, + NodeObjectType = currCt.nObjectType, + ParentId = currCt.nParentId, + Path = currCt.nPath, + SortOrder = currCt.nSortOrder, + Text = currCt.nName, + Trashed = currCt.nTrashed, + UniqueId = currCt.nUniqueId, + UserId = currCt.nUser + } + }; + + //now create the content type object + + var factory = new ContentTypeFactory(); + var mediaType = factory.BuildMediaTypeEntity(contentTypeDto); + + //map the allowed content types + mediaType.AllowedContentTypes = currAllowedContentTypes; + + return mediaType; + } + + internal static IEnumerable MapContentTypes(Database db, ISqlSyntaxProvider sqlSyntax, + out IDictionary> associatedTemplates, + out IDictionary> parentContentTypeIds) + { + Mandate.ParameterNotNull(db, "db"); + + var sql = @"SELECT cmsDocumentType.IsDefault as dtIsDefault, cmsDocumentType.templateNodeId as dtTemplateId, + cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, + cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, + AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, + ParentTypes.parentContentTypeId as chtParentId,ParentTypes.parentContentTypeKey as chtParentKey, + umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, + umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed, + umbracoNode.uniqueID as nUniqueId, + Template.alias as tAlias, Template.nodeId as tId,Template.text as tText + FROM cmsContentType + INNER JOIN umbracoNode + ON cmsContentType.nodeId = umbracoNode.id + LEFT JOIN cmsDocumentType + ON cmsDocumentType.contentTypeNodeId = cmsContentType.nodeId + LEFT JOIN ( + SELECT cmsContentTypeAllowedContentType.Id, cmsContentTypeAllowedContentType.AllowedId, cmsContentType.alias, cmsContentTypeAllowedContentType.SortOrder + FROM cmsContentTypeAllowedContentType + INNER JOIN cmsContentType + ON cmsContentTypeAllowedContentType.AllowedId = cmsContentType.nodeId + ) AllowedTypes + ON AllowedTypes.Id = cmsContentType.nodeId + LEFT JOIN ( + SELECT * FROM cmsTemplate + INNER JOIN umbracoNode + ON cmsTemplate.nodeId = umbracoNode.id + ) as Template + ON Template.nodeId = cmsDocumentType.templateNodeId + LEFT JOIN ( + SELECT cmsContentType2ContentType.parentContentTypeId, umbracoNode.uniqueID AS parentContentTypeKey, cmsContentType2ContentType.childContentTypeId + FROM cmsContentType2ContentType + INNER JOIN umbracoNode + ON cmsContentType2ContentType.parentContentTypeId = umbracoNode." + sqlSyntax.GetQuotedColumnName("id") + @" + ) ParentTypes + ON ParentTypes.childContentTypeId = cmsContentType.nodeId + WHERE (umbracoNode.nodeObjectType = @nodeObjectType) + ORDER BY ctId"; + + var result = db.Fetch(sql, new { nodeObjectType = new Guid(Constants.ObjectTypes.DocumentType)}); + + if (result.Any() == false) + { + parentContentTypeIds = null; + associatedTemplates = null; + return Enumerable.Empty(); + } + + parentContentTypeIds = new Dictionary>(); + associatedTemplates = new Dictionary>(); + var mappedContentTypes = new List(); + + var queue = new Queue(result); + var currDefaultTemplate = -1; + var currAllowedContentTypes = new List(); + while (queue.Count > 0) + { + var ct = queue.Dequeue(); + + //check for default templates + bool? isDefaultTemplate = Convert.ToBoolean(ct.dtIsDefault); + int? templateId = ct.dtTemplateId; + if (currDefaultTemplate == -1 && isDefaultTemplate.HasValue && isDefaultTemplate.Value && templateId.HasValue) + { + currDefaultTemplate = templateId.Value; + } + + //always ensure there's a list for this content type + if (associatedTemplates.ContainsKey(ct.ctId) == false) + associatedTemplates[ct.ctId] = new List(); + + //check for associated templates and assign to the outgoing collection + if (ct.tId != null) + { + var associatedTemplate = new AssociatedTemplate(ct.tId, ct.tAlias, ct.tText); + var associatedList = associatedTemplates[ct.ctId]; + + if (associatedList.Contains(associatedTemplate) == false) + associatedList.Add(associatedTemplate); + } + + //check for allowed content types + int? allowedCtId = ct.ctaAllowedId; + int? allowedCtSort = ct.ctaSortOrder; + string allowedCtAlias = ct.ctaAlias; + if (allowedCtId.HasValue && allowedCtSort.HasValue && allowedCtAlias != null) + { + var ctSort = new ContentTypeSort(new Lazy(() => allowedCtId.Value), allowedCtSort.Value, allowedCtAlias); + if (currAllowedContentTypes.Contains(ctSort) == false) + { + currAllowedContentTypes.Add(ctSort); + } + } + + //always ensure there's a list for this content type + if (parentContentTypeIds.ContainsKey(ct.ctId) == false) + parentContentTypeIds[ct.ctId] = new List(); + + //check for parent ids and assign to the outgoing collection + int? parentId = ct.chtParentId; + if (parentId.HasValue) + { + var associatedParentIds = parentContentTypeIds[ct.ctId]; + + if (associatedParentIds.Contains(parentId.Value) == false) + associatedParentIds.Add(parentId.Value); + } + + if (queue.Count == 0 || queue.Peek().ctId != ct.ctId) + { + //it's the last in the queue or the content type is changing (moving to the next one) + var contentType = CreateForMapping(ct, currAllowedContentTypes, currDefaultTemplate); + mappedContentTypes.Add(contentType); + + //Here we need to reset the current variables, we're now collecting data for a different content type + currDefaultTemplate = -1; + currAllowedContentTypes = new List(); + } + } + + return mappedContentTypes; + } + + private static IContentType CreateForMapping(dynamic currCt, List currAllowedContentTypes, int currDefaultTemplate) + { + // * set the default template to the first one if a default isn't found + // * create the DTO object + // * create the content type object + // * map the allowed content types + // * add to the outgoing list + + var dtDto = new ContentTypeTemplateDto + { + //create the content type dto + ContentTypeDto = new ContentTypeDto + { + Alias = currCt.ctAlias, + AllowAtRoot = currCt.ctAllowAtRoot, + Description = currCt.ctDesc, + Icon = currCt.ctIcon, + IsContainer = currCt.ctIsContainer, + NodeId = currCt.ctId, + PrimaryKey = currCt.ctPk, + Thumbnail = currCt.ctThumb, + //map the underlying node dto + NodeDto = new NodeDto + { + CreateDate = currCt.nCreateDate, + Level = (short)currCt.nLevel, + NodeId = currCt.ctId, + NodeObjectType = currCt.nObjectType, + ParentId = currCt.nParentId, + Path = currCt.nPath, + SortOrder = currCt.nSortOrder, + Text = currCt.nName, + Trashed = currCt.nTrashed, + UniqueId = currCt.nUniqueId, + UserId = currCt.nUser + } + }, + ContentTypeNodeId = currCt.ctId, + IsDefault = currDefaultTemplate != -1, + TemplateNodeId = currDefaultTemplate != -1 ? currDefaultTemplate : 0, + }; + + //now create the content type object + + var factory = new ContentTypeFactory(); + var contentType = factory.BuildContentTypeEntity(dtDto.ContentTypeDto); + + // NOTE + // that was done by the factory but makes little sense, moved here, so + // now we have to reset dirty props again (as the factory does it) and yet, + // we are not managing allowed templates... the whole thing is weird. + ((ContentType)contentType).DefaultTemplateId = dtDto.TemplateNodeId; + contentType.ResetDirtyProperties(false); + + //map the allowed content types + contentType.AllowedContentTypes = currAllowedContentTypes; + + return contentType; + } + + internal static void MapGroupsAndProperties(int[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax, + out IDictionary allPropertyTypeCollection, + out IDictionary allPropertyGroupCollection) + { + allPropertyGroupCollection = new Dictionary(); + allPropertyTypeCollection = new Dictionary(); + + // query below is not safe + pointless if array is empty + if (contentTypeIds.Length == 0) return; + + // first part Gets all property groups including property type data even when no property type exists on the group + // second part Gets all property types including ones that are not on a group + // therefore the union of the two contains all of the property type and property group information we need + // NOTE: MySQL requires a SELECT * FROM the inner union in order to be able to sort . lame. + + var sqlBuilder = new StringBuilder(@"SELECT PG.contenttypeNodeId as contentTypeId, + PT.ptUniqueId as ptUniqueID, PT.ptId, PT.ptAlias, PT.ptDesc,PT.ptMandatory,PT.ptName,PT.ptSortOrder,PT.ptRegExp, + PT.dtId,PT.dtDbType,PT.dtPropEdAlias, + PG.id as pgId, PG.uniqueID as pgKey, PG.sortorder as pgSortOrder, PG." + sqlSyntax.GetQuotedColumnName("text") + @" as pgText + FROM cmsPropertyTypeGroup as PG + LEFT JOIN + ( + SELECT PT.uniqueID as ptUniqueId, PT.id as ptId, PT.Alias as ptAlias, PT." + sqlSyntax.GetQuotedColumnName("Description") + @" as ptDesc, + PT.mandatory as ptMandatory, PT.Name as ptName, PT.sortOrder as ptSortOrder, PT.validationRegExp as ptRegExp, + PT.propertyTypeGroupId as ptGroupId, + DT.dbType as dtDbType, DT.nodeId as dtId, DT.propertyEditorAlias as dtPropEdAlias + FROM cmsPropertyType as PT + INNER JOIN cmsDataType as DT + ON PT.dataTypeId = DT.nodeId + ) as PT + ON PT.ptGroupId = PG.id + WHERE (PG.contenttypeNodeId in (@contentTypeIds)) + + UNION + + SELECT PT.contentTypeId as contentTypeId, + PT.uniqueID as ptUniqueID, PT.id as ptId, PT.Alias as ptAlias, PT." + sqlSyntax.GetQuotedColumnName("Description") + @" as ptDesc, + PT.mandatory as ptMandatory, PT.Name as ptName, PT.sortOrder as ptSortOrder, PT.validationRegExp as ptRegExp, + DT.nodeId as dtId, DT.dbType as dtDbType, DT.propertyEditorAlias as dtPropEdAlias, + PG.id as pgId, PG.uniqueID as pgKey, PG.sortorder as pgSortOrder, PG." + sqlSyntax.GetQuotedColumnName("text") + @" as pgText + FROM cmsPropertyType as PT + INNER JOIN cmsDataType as DT + ON PT.dataTypeId = DT.nodeId + LEFT JOIN cmsPropertyTypeGroup as PG + ON PG.id = PT.propertyTypeGroupId + WHERE (PT.contentTypeId in (@contentTypeIds))"); + + sqlBuilder.AppendLine(" ORDER BY (pgId)"); + + //NOTE: we are going to assume there's not going to be more than 2100 content type ids since that is the max SQL param count! + // Since there are 2 groups of params, it will be half! + if (((contentTypeIds.Length / 2) - 1) > 2000) + throw new InvalidOperationException("Cannot perform this lookup, too many sql parameters"); + + var result = db.Fetch(sqlBuilder.ToString(), new { contentTypeIds = contentTypeIds }); + + foreach (var contentTypeId in contentTypeIds) + { + //from this we need to make : + // * PropertyGroupCollection - Contains all property groups along with all property types associated with a group + // * PropertyTypeCollection - Contains all property types that do not belong to a group + + //create the property group collection first, this means all groups (even empty ones) and all groups with properties + + int currId = contentTypeId; + + var propertyGroupCollection = new PropertyGroupCollection(result + //get all rows that have a group id + .Where(x => x.pgId != null) + //filter based on the current content type + .Where(x => x.contentTypeId == currId) + //turn that into a custom object containing only the group info + .Select(x => new { GroupId = x.pgId, SortOrder = x.pgSortOrder, Text = x.pgText, Key = x.pgKey }) + //get distinct data by id + .DistinctBy(x => (int)x.GroupId) + //for each of these groups, create a group object with it's associated properties + .Select(group => new PropertyGroup(new PropertyTypeCollection( + result + .Where(row => row.pgId == group.GroupId && row.ptId != null) + .Select(row => new PropertyType(row.dtPropEdAlias, Enum.Parse(row.dtDbType), row.ptAlias) + { + //fill in the rest of the property type properties + Description = row.ptDesc, + DataTypeDefinitionId = row.dtId, + Id = row.ptId, + Key = row.ptUniqueID, + Mandatory = Convert.ToBoolean(row.ptMandatory), + Name = row.ptName, + PropertyGroupId = new Lazy(() => group.GroupId, false), + SortOrder = row.ptSortOrder, + ValidationRegExp = row.ptRegExp + }))) + { + //fill in the rest of the group properties + Id = group.GroupId, + Name = group.Text, + SortOrder = group.SortOrder, + Key = group.Key + }).ToArray()); + + allPropertyGroupCollection[currId] = propertyGroupCollection; + + //Create the property type collection now (that don't have groups) + + var propertyTypeCollection = new PropertyTypeCollection(result + .Where(x => x.pgId == null) + //filter based on the current content type + .Where(x => x.contentTypeId == currId) + .Select(row => new PropertyType(row.dtPropEdAlias, Enum.Parse(row.dtDbType), row.ptAlias) + { + //fill in the rest of the property type properties + Description = row.ptDesc, + DataTypeDefinitionId = row.dtId, + Id = row.ptId, + Key = row.ptUniqueID, + Mandatory = Convert.ToBoolean(row.ptMandatory), + Name = row.ptName, + PropertyGroupId = null, + SortOrder = row.ptSortOrder, + ValidationRegExp = row.ptRegExp + }).ToArray()); + + allPropertyTypeCollection[currId] = propertyTypeCollection; + } + + + } + + } + + protected abstract TEntity PerformGet(Guid id); + protected abstract TEntity PerformGet(string alias); + protected abstract IEnumerable PerformGetAll(params Guid[] ids); + protected abstract bool PerformExists(Guid id); + + /// + /// Gets an Entity by alias + /// + /// + /// + public TEntity Get(string alias) + { + return PerformGet(alias); + } + + /// + /// Gets an Entity by Id + /// + /// + /// + public TEntity Get(Guid id) + { + return PerformGet(id); + } + + /// + /// Gets all entities of the spefified type + /// + /// + /// + /// + /// Ensure explicit implementation, we don't want to have any accidental calls to this since it is essentially the same signature as the main GetAll when there are no parameters + /// + IEnumerable IReadRepository.GetAll(params Guid[] ids) + { + return PerformGetAll(ids); + } + + /// + /// Boolean indicating whether an Entity with the specified Id exists + /// + /// + /// + public bool Exists(Guid id) + { + return PerformExists(id); + } + + public string GetUniqueAlias(string alias) + { + // alias is unique accross ALL content types! + var aliasColumn = SqlSyntax.GetQuotedColumnName("alias"); + var aliases = Database.Fetch(@"SELECT cmsContentType." + aliasColumn + @" FROM cmsContentType +INNER JOIN umbracoNode ON cmsContentType.nodeId = umbracoNode.id +WHERE cmsContentType." + aliasColumn + @" LIKE @pattern", + new { pattern = alias + "%", objectType = NodeObjectTypeId }); + var i = 1; + string test; + while (aliases.Contains(test = alias + i)) i++; + return test; + } + + protected override IEnumerable GetDeleteClauses() + { + // in theory, services should have ensured that content items of the given content type + // have been deleted and therefore cmsPropertyData has been cleared, so cmsPropertyData + // is included here just to be 100% sure since it has a FK on cmsPropertyType. + + var list = new List + { + "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", + "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", + "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", + "DELETE FROM cmsContentTypeAllowedContentType WHERE Id = @Id", + "DELETE FROM cmsContentTypeAllowedContentType WHERE AllowedId = @Id", + "DELETE FROM cmsContentType2ContentType WHERE parentContentTypeId = @Id", + "DELETE FROM cmsContentType2ContentType WHERE childContentTypeId = @Id", + "DELETE FROM cmsPropertyData WHERE propertyTypeId IN (SELECT id FROM cmsPropertyType WHERE contentTypeId = @Id)", + "DELETE FROM cmsPropertyType WHERE contentTypeId = @Id", + "DELETE FROM cmsPropertyTypeGroup WHERE contenttypeNodeId = @Id", + }; + return list; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index 309f14c52e..691d579401 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -103,6 +103,14 @@ namespace Umbraco.Core.Persistence.Repositories } } + // so... we are modifying content types here. the service will trigger Deleted event, + // which will propagate to DataTypeCacheRefresher which will clear almost every cache + // there is to clear... and in addition facade caches will clear themselves too, so + // this is probably safe alghough it looks... weird. + // + // what IS weird is that a content type is losing a property and we do NOT raise any + // content type event... so ppl better listen on the data type events too. + _contentTypeRepository.AddOrUpdate(contentType); } @@ -282,8 +290,6 @@ AND umbracoNode.id <> @id", #endregion - - public PreValueCollection GetPreValuesCollectionByDataTypeId(int dataTypeId) { var cached = RuntimeCache.GetCacheItemsByKeySearch(GetPrefixedCacheKey(dataTypeId)); @@ -485,8 +491,6 @@ AND umbracoNode.id <> @id", private string EnsureUniqueNodeName(string nodeName, int id = 0) { - - var sql = Sql() .SelectAll() .From() diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs index d0bc046055..6807b80c90 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq.Expressions; using System.Xml.Linq; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; @@ -9,7 +8,7 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IContentRepository : IRepositoryVersionable, IRecycleBinRepository, IDeleteMediaFilesRepository + public interface IContentRepository : IRepositoryVersionable, IRecycleBinRepository { /// /// Get the count of published items @@ -18,7 +17,9 @@ namespace Umbraco.Core.Persistence.Repositories /// /// We require this on the repo because the IQuery{IContent} cannot supply the 'newest' parameter /// - int CountPublished(); + int CountPublished(string contentTypeAlias = null); + + bool IsPathPublished(IContent content); /// /// Used to bulk update the permissions set for a content item. This will replace all permissions @@ -31,7 +32,7 @@ namespace Umbraco.Core.Persistence.Repositories /// Clears the published flag for a content. /// /// - void ClearPublished(IContent content); + void ClearPublishedFlag(IContent content); /// /// Gets all published Content by the specified query diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeCompositionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeCompositionRepository.cs deleted file mode 100644 index 649726b100..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeCompositionRepository.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Persistence.Repositories -{ - public interface IContentTypeCompositionRepository : IRepositoryQueryable, IReadRepository - where TEntity : IContentTypeComposition - { - TEntity Get(string alias); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs index 61d83645b3..c0c23ee0ed 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IContentTypeRepository : IContentTypeCompositionRepository + public interface IContentTypeRepository : IContentTypeRepositoryBase { /// /// Gets all entities of the specified query @@ -21,8 +21,6 @@ namespace Umbraco.Core.Persistence.Repositories /// IEnumerable GetAllPropertyTypeAliases(); - IEnumerable> Move(IContentType toMove, EntityContainer container); - /// /// Gets all content type aliases /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepositoryBase.cs new file mode 100644 index 0000000000..8d49698e6b --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepositoryBase.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Events; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IContentTypeRepositoryBase : IRepositoryQueryable, IReadRepository + where TItem : IContentTypeComposition + { + TItem Get(string alias); + IEnumerable> Move(TItem moving, EntityContainer container); + IEnumerable GetTypesDirectlyComposedOf(int id); + + /// + /// Derives a unique alias from an existing alias. + /// + /// The original alias. + /// The original alias with a number appended to it, so that it is unique. + /// Unique accross all content, media and member types. + string GetUniqueAlias(string alias); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs index 7f2f76e541..a601723494 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IMediaTypeRepository : IContentTypeCompositionRepository + public interface IMediaTypeRepository : IContentTypeRepositoryBase { /// /// Gets all entities of the specified query @@ -13,15 +13,5 @@ namespace Umbraco.Core.Persistence.Repositories /// /// An enumerable list of objects IEnumerable GetByQuery(IQuery query); - - IEnumerable> Move(IMediaType toMove, EntityContainer container); - - /// - /// Derives a unique alias from an existing alias. - /// - /// The original alias. - /// The original alias with a number appended to it, so that it is unique. - /// Unique accross all content, media and member types. - string GetUniqueAlias(string alias); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs index fc877d5227..9d55df0d02 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs @@ -1,10 +1,7 @@ -using System; -using Umbraco.Core.Models; +using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories { - public interface IMemberTypeRepository : IContentTypeCompositionRepository - { - - } + public interface IMemberTypeRepository : IContentTypeRepositoryBase + { } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IServerRegistrationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IServerRegistrationRepository.cs index 498082461e..5db9c6087e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IServerRegistrationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IServerRegistrationRepository.cs @@ -6,8 +6,5 @@ namespace Umbraco.Core.Persistence.Repositories public interface IServerRegistrationRepository : IRepositoryQueryable { void DeactiveStaleServers(TimeSpan staleTimeout); - - void ReadLockServers(); - void WriteLockServers(); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 8b3e1b52d6..ebbbe4db3a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -2,14 +2,10 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Text; using System.Text.RegularExpressions; using System.Xml.Linq; using NPoco; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Dynamics; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -21,7 +17,6 @@ using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories { @@ -38,8 +33,8 @@ namespace Umbraco.Core.Persistence.Repositories public MediaRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, IContentSection contentSection, IMappingResolver mappingResolver) : base(work, cache, logger, contentSection, mappingResolver) { - if (mediaTypeRepository == null) throw new ArgumentNullException("mediaTypeRepository"); - if (tagRepository == null) throw new ArgumentNullException("tagRepository"); + if (mediaTypeRepository == null) throw new ArgumentNullException(nameof(mediaTypeRepository)); + if (tagRepository == null) throw new ArgumentNullException(nameof(tagRepository)); _mediaTypeRepository = mediaTypeRepository; _tagRepository = tagRepository; _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, mappingResolver); @@ -47,7 +42,7 @@ namespace Umbraco.Core.Persistence.Repositories EnsureUniqueNaming = contentSection.EnsureUniqueNaming; } - public bool EnsureUniqueNaming { get; private set; } + public bool EnsureUniqueNaming { get; } #region Overrides of RepositoryBase @@ -62,7 +57,7 @@ namespace Umbraco.Core.Persistence.Repositories if (dto == null) return null; - var content = CreateMediaFromDto(dto, dto.VersionId, sql); + var content = CreateMediaFromDto(dto, dto.VersionId); return content; } @@ -72,7 +67,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); if (ids.Any()) { - sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); + sql.Where("umbracoNode.id in (@ids)", new { /*ids =*/ ids }); } return ProcessQuery(sql); @@ -139,10 +134,7 @@ namespace Umbraco.Core.Persistence.Repositories return list; } - protected override Guid NodeObjectTypeId - { - get { return new Guid(Constants.ObjectTypes.Media); } - } + protected override Guid NodeObjectTypeId => new Guid(Constants.ObjectTypes.Media); #endregion @@ -174,94 +166,11 @@ namespace Umbraco.Core.Persistence.Repositories return media; } - public void RebuildXmlStructures(Func serializer, int groupSize = 5000, IEnumerable contentTypeIds = null) - { - - //Ok, now we need to remove the data and re-insert it, we'll do this all in one transaction too. - using (var tr = Database.GetTransaction()) - { - //Remove all the data first, if anything fails after this it's no problem the transaction will be reverted - if (contentTypeIds == null) - { - var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media); - var subQuery = Sql() - .Select("DISTINCT cmsContentXml.nodeId") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(dto => dto.NodeObjectType == mediaObjectType); - - var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); - Database.Execute(deleteSql); - } - else - { - foreach (var id in contentTypeIds) - { - var id1 = id; - var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media); - var subQuery = Sql() - .Select("DISTINCT cmsContentXml.nodeId") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(dto => dto.NodeObjectType == mediaObjectType) - .Where(dto => dto.ContentTypeId == id1); - - var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); - Database.Execute(deleteSql); - } - } - - //now insert the data, again if something fails here, the whole transaction is reversed - if (contentTypeIds == null) - { - RebuildXmlStructuresProcessQuery(serializer, Query, tr, groupSize); - } - else - { - foreach (var contentTypeId in contentTypeIds) - { - //copy local - var id = contentTypeId; - var query = Query.Where(x => x.ContentTypeId == id && x.Trashed == false); - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); - } - } - - tr.Complete(); - } - } - - private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, ITransaction tr, int pageSize) - { - var pageIndex = 0; - var total = long.MinValue; - var processed = 0; - do - { - var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending, true); - - var xmlItems = (from descendant in descendants - let xml = serializer(descendant) - select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }).ToArray(); - - //bulk insert it into the database - Database.BulkInsertRecords(SqlSyntax, xmlItems, tr); - - processed += xmlItems.Length; - - pageIndex++; - } while (processed < total); - } - public IMedia GetMediaByPath(string mediaPath) { var umbracoFileValue = mediaPath; - const string Pattern = ".*[_][0-9]+[x][0-9]+[.].*"; - var isResized = Regex.IsMatch(mediaPath, Pattern); + const string pattern = ".*[_][0-9]+[x][0-9]+[.].*"; + var isResized = Regex.IsMatch(mediaPath, pattern); // If the image has been resized we strip the "_403x328" of the original "/media/1024/koala_403x328.jpg" url. if (isResized) @@ -292,17 +201,6 @@ namespace Umbraco.Core.Persistence.Repositories return propertyDataDto == null ? null : Get(propertyDataDto.NodeId); } - - public void AddOrUpdateContentXml(IMedia content, Func xml) - { - _contentXmlRepository.AddOrUpdate(new ContentXmlEntity(content, xml)); - } - - public void AddOrUpdatePreviewXml(IMedia content, Func xml) - { - _contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity(content, xml)); - } - protected override void PerformDeleteVersion(int id, Guid versionId) { Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); @@ -329,10 +227,10 @@ namespace Umbraco.Core.Persistence.Repositories //NOTE Should the logic below have some kind of fallback for empty parent ids ? //Logic for setting Path, Level and SortOrder - var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId }); + var parent = Database.First("WHERE id = @ParentId", new { /*ParentId =*/ entity.ParentId }); var level = parent.Level + 1; var maxSortOrder = Database.ExecuteScalar( - "SELECT coalesce(max(sortOrder),-1) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", + "SELECT coalesce(max(sortOrder),-1) FROM umbracoNode WHERE parentId = @ParentId AND nodeObjectType = @NodeObjectType", new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId }); var sortOrder = maxSortOrder + 1; @@ -341,7 +239,7 @@ namespace Umbraco.Core.Persistence.Repositories nodeDto.Path = parent.Path; nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture)); nodeDto.SortOrder = sortOrder; - var o = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto); + var o = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto); //Update with new correct path nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); @@ -381,7 +279,7 @@ namespace Umbraco.Core.Persistence.Repositories property.Id = keyDictionary[property.PropertyTypeId]; } - UpdatePropertyTags(entity, _tagRepository); + UpdateEntityTags(entity, _tagRepository); entity.ResetDirtyProperties(); } @@ -400,19 +298,19 @@ namespace Umbraco.Core.Persistence.Repositories //Look up parent to get and set the correct Path and update SortOrder if ParentId has changed if (entity.IsPropertyDirty("ParentId")) { - var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId }); + var parent = Database.First("WHERE id = @ParentId", new { /*ParentId =*/ entity.ParentId }); entity.Path = string.Concat(parent.Path, ",", entity.Id); entity.Level = parent.Level + 1; var maxSortOrder = Database.ExecuteScalar( "SELECT coalesce(max(sortOrder),0) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", - new { ParentId = entity.ParentId, NodeObjectType = NodeObjectTypeId }); + new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId }); entity.SortOrder = maxSortOrder + 1; } var factory = new MediaFactory(NodeObjectTypeId, entity.Id); //Look up Content entry to get Primary for updating the DTO - var contentDto = Database.SingleOrDefault("WHERE nodeId = @Id", new { Id = entity.Id }); + var contentDto = Database.SingleOrDefault("WHERE nodeId = @Id", new { /*Id =*/ entity.Id }); factory.SetPrimaryKey(contentDto.PrimaryKey); var dto = factory.BuildDto(entity); @@ -429,7 +327,7 @@ namespace Umbraco.Core.Persistence.Repositories } //In order to update the ContentVersion we need to retrieve its primary key id - var contentVerDto = Database.SingleOrDefault("WHERE VersionId = @Version", new { Version = entity.Version }); + var contentVerDto = Database.SingleOrDefault("WHERE VersionId = @Version", new { /*Version =*/ entity.Version }); dto.Id = contentVerDto.Id; //Updates the current version - cmsContentVersion //Assumes a Version guid exists and Version date (modified date) has been set/updated @@ -463,7 +361,7 @@ namespace Umbraco.Core.Persistence.Repositories } } - UpdatePropertyTags(entity, _tagRepository); + UpdateEntityTags(entity, _tagRepository); entity.ResetDirtyProperties(); } @@ -472,10 +370,7 @@ namespace Umbraco.Core.Persistence.Repositories #region IRecycleBinRepository members - protected override int RecycleBinId - { - get { return Constants.System.RecycleBinMedia; } - } + protected override int RecycleBinId => Constants.System.RecycleBinMedia; #endregion @@ -544,7 +439,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Private method to create a media object from a ContentDto /// - /// + /// /// /// /// @@ -566,11 +461,10 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Private method to create a media object from a ContentDto /// - /// + /// /// - /// /// - private IMedia CreateMediaFromDto(ContentVersionDto dto, Guid versionId, Sql docSql) + private IMedia CreateMediaFromDto(ContentVersionDto dto, Guid versionId) { var contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId); @@ -612,7 +506,7 @@ namespace Umbraco.Core.Persistence.Repositories if (dto.Text.ToLowerInvariant().Equals(currentName.ToLowerInvariant())) { - currentName = nodeName + string.Format(" ({0})", uniqueNumber); + currentName = $"{nodeName} ({uniqueNumber})"; uniqueNumber++; } } @@ -620,5 +514,103 @@ namespace Umbraco.Core.Persistence.Repositories return currentName; } + + #region Xml - Should Move! + + public void RebuildXmlStructures(Func serializer, int groupSize = 5000, IEnumerable contentTypeIds = null) + { + + //Ok, now we need to remove the data and re-insert it, we'll do this all in one transaction too. + using (var tr = Database.GetTransaction()) + { + //Remove all the data first, if anything fails after this it's no problem the transaction will be reverted + if (contentTypeIds == null) + { + var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media); + var subQuery = Sql() + .Select("DISTINCT cmsContentXml.nodeId") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(dto => dto.NodeObjectType == mediaObjectType); + + var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); + Database.Execute(deleteSql); + } + else + { + foreach (var id in contentTypeIds) + { + var id1 = id; + var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media); + var subQuery = Sql() + .Select("DISTINCT cmsContentXml.nodeId") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(dto => dto.NodeObjectType == mediaObjectType) + .Where(dto => dto.ContentTypeId == id1); + + var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); + Database.Execute(deleteSql); + } + } + + //now insert the data, again if something fails here, the whole transaction is reversed + if (contentTypeIds == null) + { + RebuildXmlStructuresProcessQuery(serializer, Query, tr, groupSize); + } + else + { + foreach (var contentTypeId in contentTypeIds) + { + //copy local + var id = contentTypeId; + var query = Query.Where(x => x.ContentTypeId == id && x.Trashed == false); + RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); + } + } + + tr.Complete(); + } + } + + private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, ITransaction tr, int pageSize) + { + var pageIndex = 0; + long total; + var processed = 0; + do + { + var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending, true); + + var xmlItems = (from descendant in descendants + let xml = serializer(descendant) + select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }).ToArray(); + + //bulk insert it into the database + Database.BulkInsertRecords(SqlSyntax, xmlItems, tr); + + processed += xmlItems.Length; + + pageIndex++; + } while (processed < total); + } + + + public void AddOrUpdateContentXml(IMedia content, Func xml) + { + _contentXmlRepository.AddOrUpdate(new ContentXmlEntity(content, xml)); + } + + public void AddOrUpdatePreviewXml(IMedia content, Func xml) + { + _contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity(content, xml)); + } + + #endregion } } diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs index 68f3dcfd05..546b90e107 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs @@ -3,32 +3,23 @@ using System.Collections.Generic; using System.Linq; using NPoco; using Umbraco.Core.Cache; -using Umbraco.Core.Events; -using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; - -using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories { /// /// Represents a repository for doing CRUD operations for /// - internal class MediaTypeRepository : ContentTypeBaseRepository, IMediaTypeRepository + internal class MediaTypeRepository : ContentTypeRepositoryBase, IMediaTypeRepository { - public MediaTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, IMappingResolver mappingResolver) : base(work, cache, logger, mappingResolver) - { - } + { } private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; protected override IRepositoryCachePolicyFactory CachePolicyFactory @@ -49,6 +40,23 @@ namespace Umbraco.Core.Persistence.Repositories return GetAll().FirstOrDefault(x => x.Id == id); } + protected override IMediaType PerformGet(Guid id) + { + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Key == id); + } + + protected override bool PerformExists(Guid id) + { + return GetAll().FirstOrDefault(x => x.Key == id) != null; + } + + protected override IMediaType PerformGet(string alias) + { + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); + } + protected override IEnumerable PerformGetAll(params int[] ids) { if (ids.Any()) @@ -61,6 +69,20 @@ namespace Umbraco.Core.Persistence.Repositories return ContentTypeQueryMapper.GetMediaTypes(Database, SqlSyntax, this); } + protected override IEnumerable PerformGetAll(params Guid[] ids) + { + //use the underlying GetAll which will force cache all content types + + if (ids.Any()) + { + return GetAll().Where(x => ids.Contains(x.Key)); + } + else + { + return GetAll(); + } + } + protected override IEnumerable PerformGetByQuery(IQuery query) { var sqlClause = GetBaseQuery(false); @@ -82,7 +104,7 @@ namespace Umbraco.Core.Persistence.Repositories /// Gets all entities of the specified query /// /// - /// An enumerable list of objects + /// An enumerable list of objects public IEnumerable GetByQuery(IQuery query) { var ints = PerformGetByQuery(query).ToArray(); @@ -116,28 +138,14 @@ namespace Umbraco.Core.Persistence.Repositories protected override IEnumerable GetDeleteClauses() { - var list = new List - { - "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", - "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", - "DELETE FROM cmsContentTypeAllowedContentType WHERE Id = @Id", - "DELETE FROM cmsContentTypeAllowedContentType WHERE AllowedId = @Id", - "DELETE FROM cmsContentType2ContentType WHERE parentContentTypeId = @Id", - "DELETE FROM cmsContentType2ContentType WHERE childContentTypeId = @Id", - "DELETE FROM cmsPropertyType WHERE contentTypeId = @Id", - "DELETE FROM cmsPropertyTypeGroup WHERE contenttypeNodeId = @Id", - "DELETE FROM cmsContentType WHERE nodeId = @Id", - "DELETE FROM umbracoNode WHERE id = @Id" - }; - return list; + var l = (List) base.GetDeleteClauses(); // we know it's a list + l.Add("DELETE FROM cmsContentType WHERE nodeId = @Id"); + l.Add("DELETE FROM umbracoNode WHERE id = @Id"); + return l; } - protected override Guid NodeObjectTypeId - { - get { return new Guid(Constants.ObjectTypes.MediaType); } - } - + protected override Guid NodeObjectTypeId => Constants.ObjectTypes.MediaTypeGuid; + protected override void PersistNewItem(IMediaType entity) { ((MediaType)entity).AddingEntity(); @@ -171,36 +179,5 @@ namespace Umbraco.Core.Persistence.Repositories entity.ResetDirtyProperties(); } - - protected override IMediaType PerformGet(Guid id) - { - //use the underlying GetAll which will force cache all content types - return GetAll().FirstOrDefault(x => x.Key == id); - } - - protected override IEnumerable PerformGetAll(params Guid[] ids) - { - //use the underlying GetAll which will force cache all content types - - if (ids.Any()) - { - return GetAll().Where(x => ids.Contains(x.Key)); - } - else - { - return GetAll(); - } - } - - protected override bool PerformExists(Guid id) - { - return GetAll().FirstOrDefault(x => x.Key == id) != null; - } - - protected override IMediaType PerformGet(string alias) - { - //use the underlying GetAll which will force cache all content types - return GetAll().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index a266b71eea..74a82efa08 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -2,13 +2,9 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Linq.Expressions; -using System.Text; using System.Xml.Linq; using NPoco; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models; @@ -19,7 +15,6 @@ using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Dynamics; using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Persistence.Repositories @@ -38,8 +33,8 @@ namespace Umbraco.Core.Persistence.Repositories public MemberRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, IContentSection contentSection, IMappingResolver mappingResolver) : base(work, cache, logger, contentSection, mappingResolver) { - if (memberTypeRepository == null) throw new ArgumentNullException("memberTypeRepository"); - if (tagRepository == null) throw new ArgumentNullException("tagRepository"); + if (memberTypeRepository == null) throw new ArgumentNullException(nameof(memberTypeRepository)); + if (tagRepository == null) throw new ArgumentNullException(nameof(tagRepository)); _memberTypeRepository = memberTypeRepository; _tagRepository = tagRepository; _memberGroupRepository = memberGroupRepository; @@ -60,7 +55,7 @@ namespace Umbraco.Core.Persistence.Repositories if (dto == null) return null; - var content = CreateMemberFromDto(dto, dto.ContentVersionDto.VersionId, sql); + var content = CreateMemberFromDto(dto, dto.ContentVersionDto.VersionId); return content; @@ -71,7 +66,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); if (ids.Any()) { - sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); + sql.Where("umbracoNode.id in (@ids)", new { /*ids =*/ ids }); } return MapQueryDtos(Database.Fetch(sql)); @@ -117,8 +112,8 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = Sql(); - sql = isCount - ? sql.SelectCount() + sql = isCount + ? sql.SelectCount() : sql.Select(r => r.Select(rr => rr.Select(rrr => @@ -184,10 +179,7 @@ namespace Umbraco.Core.Persistence.Repositories return list; } - protected override Guid NodeObjectTypeId - { - get { return new Guid(Constants.ObjectTypes.Member); } - } + protected override Guid NodeObjectTypeId => new Guid(Constants.ObjectTypes.Member); #endregion @@ -205,18 +197,18 @@ namespace Umbraco.Core.Persistence.Repositories //NOTE Should the logic below have some kind of fallback for empty parent ids ? //Logic for setting Path, Level and SortOrder - var parent = Database.First("WHERE id = @ParentId", new { ParentId = ((IUmbracoEntity)entity).ParentId }); - int level = parent.Level + 1; - int sortOrder = + var parent = Database.First("WHERE id = @ParentId", new { /*ParentId =*/ entity.ParentId }); + var level = parent.Level + 1; + var sortOrder = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoNode WHERE parentID = @ParentId AND nodeObjectType = @NodeObjectType", - new { ParentId = ((IUmbracoEntity)entity).ParentId, NodeObjectType = NodeObjectTypeId }); + new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId }); //Create the (base) node data - umbracoNode var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; nodeDto.Path = parent.Path; nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture)); nodeDto.SortOrder = sortOrder; - var o = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto); + var o = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto); //Update with new correct path nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); @@ -265,7 +257,7 @@ namespace Umbraco.Core.Persistence.Repositories property.Id = keyDictionary[property.PropertyTypeId]; } - UpdatePropertyTags(entity, _tagRepository); + UpdateEntityTags(entity, _tagRepository); ((Member)entity).ResetDirtyProperties(); } @@ -283,19 +275,19 @@ namespace Umbraco.Core.Persistence.Repositories //Look up parent to get and set the correct Path and update SortOrder if ParentId has changed if (dirtyEntity.IsPropertyDirty("ParentId")) { - var parent = Database.First("WHERE id = @ParentId", new { ParentId = ((IUmbracoEntity)entity).ParentId }); - ((IUmbracoEntity)entity).Path = string.Concat(parent.Path, ",", entity.Id); - ((IUmbracoEntity)entity).Level = parent.Level + 1; + var parent = Database.First("WHERE id = @ParentId", new { /*ParentId =*/ entity.ParentId }); + entity.Path = string.Concat(parent.Path, ",", entity.Id); + entity.Level = parent.Level + 1; var maxSortOrder = Database.ExecuteScalar( "SELECT coalesce(max(sortOrder),0) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", - new { ParentId = ((IUmbracoEntity)entity).ParentId, NodeObjectType = NodeObjectTypeId }); - ((IUmbracoEntity)entity).SortOrder = maxSortOrder + 1; + new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId }); + entity.SortOrder = maxSortOrder + 1; } var factory = new MemberFactory(NodeObjectTypeId, entity.Id); //Look up Content entry to get Primary for updating the DTO - var contentDto = Database.SingleOrDefault("WHERE nodeId = @Id", new { Id = entity.Id }); + var contentDto = Database.SingleOrDefault("WHERE nodeId = @Id", new { /*Id =*/ entity.Id }); factory.SetPrimaryKey(contentDto.PrimaryKey); var dto = factory.BuildDto(entity); @@ -312,7 +304,7 @@ namespace Umbraco.Core.Persistence.Repositories } //In order to update the ContentVersion we need to retrieve its primary key id - var contentVerDto = Database.SingleOrDefault("WHERE VersionId = @Version", new { Version = entity.Version }); + var contentVerDto = Database.SingleOrDefault("WHERE VersionId = @Version", new { /*Version =*/ entity.Version }); dto.ContentVersionDto.Id = contentVerDto.Id; //Updates the current version - cmsContentVersion //Assumes a Version guid exists and Version date (modified date) has been set/updated @@ -378,7 +370,7 @@ namespace Umbraco.Core.Persistence.Repositories } } - UpdatePropertyTags(entity, _tagRepository); + UpdateEntityTags(entity, _tagRepository); dirtyEntity.ResetDirtyProperties(); } @@ -387,90 +379,6 @@ namespace Umbraco.Core.Persistence.Repositories #region Overrides of VersionableRepositoryBase - public void RebuildXmlStructures(Func serializer, int groupSize = 5000, IEnumerable contentTypeIds = null) - { - - //Ok, now we need to remove the data and re-insert it, we'll do this all in one transaction too. - using (var tr = Database.GetTransaction()) - { - //Remove all the data first, if anything fails after this it's no problem the transaction will be reverted - if (contentTypeIds == null) - { - var memberObjectType = Guid.Parse(Constants.ObjectTypes.Member); - var subQuery = Sql() - .Select("DISTINCT cmsContentXml.nodeId") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(dto => dto.NodeObjectType == memberObjectType); - - var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); - Database.Execute(deleteSql); - } - else - { - foreach (var id in contentTypeIds) - { - var id1 = id; - var memberObjectType = Guid.Parse(Constants.ObjectTypes.Member); - var subQuery = Sql() - .Select("DISTINCT cmsContentXml.nodeId") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(dto => dto.NodeObjectType == memberObjectType) - .Where( dto => dto.ContentTypeId == id1); - - var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); - Database.Execute(deleteSql); - } - } - - //now insert the data, again if something fails here, the whole transaction is reversed - if (contentTypeIds == null) - { - var query = Query; - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); - } - else - { - foreach (var contentTypeId in contentTypeIds) - { - //copy local - var id = contentTypeId; - var query = Query.Where(x => x.ContentTypeId == id && x.Trashed == false); - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); - } - } - - tr.Complete(); - } - } - - private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, ITransaction tr, int pageSize) - { - var pageIndex = 0; - var total = long.MinValue; - var processed = 0; - do - { - var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending, true); - - var xmlItems = (from descendant in descendants - let xml = serializer(descendant) - select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }).ToArray(); - - //bulk insert it into the database - Database.BulkInsertRecords(SqlSyntax, xmlItems, tr); - - processed += xmlItems.Length; - - pageIndex++; - } while (processed < total); - } - public override IMember GetByVersion(Guid versionId) { var sql = GetBaseQuery(false); @@ -534,7 +442,7 @@ namespace Umbraco.Core.Persistence.Repositories query.Where(member => member.Username.SqlWildcard(usernameToMatch, TextColumnType.NVarchar)); break; default: - throw new ArgumentOutOfRangeException("matchType"); + throw new ArgumentOutOfRangeException(nameof(matchType)); } var matchedMembers = GetByQuery(query).ToArray(); @@ -635,16 +543,6 @@ namespace Umbraco.Core.Persistence.Repositories filterSql); } - public void AddOrUpdateContentXml(IMember content, Func xml) - { - _contentXmlRepository.AddOrUpdate(new ContentXmlEntity(content, xml)); - } - - public void AddOrUpdatePreviewXml(IMember content, Func xml) - { - _contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity(content, xml)); - } - protected override string GetDatabaseFieldNameForOrderBy(string orderBy) { //Some custom ones @@ -716,9 +614,8 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// - /// /// - private IMember CreateMemberFromDto(MemberDto dto, Guid versionId, Sql docSql) + private IMember CreateMemberFromDto(MemberDto dto, Guid versionId) { var memberType = _memberTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); @@ -736,5 +633,103 @@ namespace Umbraco.Core.Persistence.Repositories ((Entity)member).ResetDirtyProperties(false); return member; } + + #region Xml - Should Move! + + public void AddOrUpdateContentXml(IMember content, Func xml) + { + _contentXmlRepository.AddOrUpdate(new ContentXmlEntity(content, xml)); + } + + public void AddOrUpdatePreviewXml(IMember content, Func xml) + { + _contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity(content, xml)); + } + + public void RebuildXmlStructures(Func serializer, int groupSize = 5000, IEnumerable contentTypeIds = null) + { + + //Ok, now we need to remove the data and re-insert it, we'll do this all in one transaction too. + using (var tr = Database.GetTransaction()) + { + //Remove all the data first, if anything fails after this it's no problem the transaction will be reverted + if (contentTypeIds == null) + { + var memberObjectType = Guid.Parse(Constants.ObjectTypes.Member); + var subQuery = Sql() + .Select("DISTINCT cmsContentXml.nodeId") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(dto => dto.NodeObjectType == memberObjectType); + + var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); + Database.Execute(deleteSql); + } + else + { + foreach (var id in contentTypeIds) + { + var id1 = id; + var memberObjectType = Guid.Parse(Constants.ObjectTypes.Member); + var subQuery = Sql() + .Select("DISTINCT cmsContentXml.nodeId") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(dto => dto.NodeObjectType == memberObjectType) + .Where(dto => dto.ContentTypeId == id1); + + var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); + Database.Execute(deleteSql); + } + } + + //now insert the data, again if something fails here, the whole transaction is reversed + if (contentTypeIds == null) + { + var query = Query; + RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); + } + else + { + foreach (var contentTypeId in contentTypeIds) + { + //copy local + var id = contentTypeId; + var query = Query.Where(x => x.ContentTypeId == id && x.Trashed == false); + RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); + } + } + + tr.Complete(); + } + } + + private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, ITransaction tr, int pageSize) + { + var pageIndex = 0; + long total; + var processed = 0; + do + { + var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending, true); + + var xmlItems = (from descendant in descendants + let xml = serializer(descendant) + select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }).ToArray(); + + //bulk insert it into the database + Database.BulkInsertRecords(SqlSyntax, xmlItems, tr); + + processed += xmlItems.Length; + + pageIndex++; + } while (processed < total); + } + + #endregion } } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 95f26447c4..81cc28b05c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -1,18 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; -using log4net; using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; -using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories @@ -20,13 +17,11 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Represents a repository for doing CRUD operations for /// - internal class MemberTypeRepository : ContentTypeBaseRepository, IMemberTypeRepository + internal class MemberTypeRepository : ContentTypeRepositoryBase, IMemberTypeRepository { - public MemberTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, IMappingResolver mappingResolver) : base(work, cache, logger, mappingResolver) - { - } + { } private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; protected override IRepositoryCachePolicyFactory CachePolicyFactory @@ -47,6 +42,37 @@ namespace Umbraco.Core.Persistence.Repositories return GetAll().FirstOrDefault(x => x.Id == id); } + protected override IMemberType PerformGet(Guid id) + { + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Key == id); + } + + protected override IEnumerable PerformGetAll(params Guid[] ids) + { + //use the underlying GetAll which will force cache all content types + + if (ids.Any()) + { + return GetAll().Where(x => ids.Contains(x.Key)); + } + else + { + return GetAll(); + } + } + + protected override bool PerformExists(Guid id) + { + return GetAll().FirstOrDefault(x => x.Key == id) != null; + } + + protected override IMemberType PerformGet(string alias) + { + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); + } + protected override IEnumerable PerformGetAll(params int[] ids) { var sql = GetBaseQuery(false); @@ -165,28 +191,14 @@ namespace Umbraco.Core.Persistence.Repositories protected override IEnumerable GetDeleteClauses() { - var list = new List - { - "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", - "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", - "DELETE FROM cmsContentTypeAllowedContentType WHERE Id = @Id", - "DELETE FROM cmsContentTypeAllowedContentType WHERE AllowedId = @Id", - "DELETE FROM cmsContentType2ContentType WHERE parentContentTypeId = @Id", - "DELETE FROM cmsContentType2ContentType WHERE childContentTypeId = @Id", - "DELETE FROM cmsPropertyType WHERE contentTypeId = @Id", - "DELETE FROM cmsPropertyTypeGroup WHERE contenttypeNodeId = @Id", - "DELETE FROM cmsMemberType WHERE NodeId = @Id", - "DELETE FROM cmsContentType WHERE nodeId = @Id", - "DELETE FROM umbracoNode WHERE id = @Id" - }; - return list; + var l = (List)base.GetDeleteClauses(); // we know it's a list + l.Add("DELETE FROM cmsMemberType WHERE NodeId = @Id"); + l.Add("DELETE FROM cmsContentType WHERE nodeId = @Id"); + l.Add("DELETE FROM umbracoNode WHERE id = @Id"); + return l; } - protected override Guid NodeObjectTypeId - { - get { return new Guid(Constants.ObjectTypes.MemberType); } - } + protected override Guid NodeObjectTypeId => Constants.ObjectTypes.MemberTypeGuid; protected override void PersistNewItem(IMemberType entity) { @@ -278,37 +290,6 @@ namespace Umbraco.Core.Persistence.Repositories propertyTypeAlias); } - protected override IMemberType PerformGet(Guid id) - { - //use the underlying GetAll which will force cache all content types - return GetAll().FirstOrDefault(x => x.Key == id); - } - - protected override IEnumerable PerformGetAll(params Guid[] ids) - { - //use the underlying GetAll which will force cache all content types - - if (ids.Any()) - { - return GetAll().Where(x => ids.Contains(x.Key)); - } - else - { - return GetAll(); - } - } - - protected override bool PerformExists(Guid id) - { - return GetAll().FirstOrDefault(x => x.Key == id) != null; - } - - protected override IMemberType PerformGet(string alias) - { - //use the underlying GetAll which will force cache all content types - return GetAll().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); - } - /// /// Ensure that all the built-in membership provider properties have their correct data type /// and property editors assigned. This occurs prior to saving so that the correct values are persisted. diff --git a/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs index 265e7c2857..37f249df6e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs @@ -18,7 +18,6 @@ namespace Umbraco.Core.Persistence.Repositories internal class ServerRegistrationRepository : NPocoRepositoryBase, IServerRegistrationRepository { private readonly ICacheProvider _staticCache; - private readonly int[] _lockIds = { Constants.System.ServersLock }; public ServerRegistrationRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper, ILogger logger, IMappingResolver mappingResolver) : base(work, CacheHelper.CreateDisabledCacheHelper(), logger, mappingResolver) @@ -147,15 +146,5 @@ namespace Umbraco.Core.Persistence.Repositories Database.Update("SET isActive=0, isMaster=0 WHERE lastNotifiedDate < @timeoutDate", new { /*timeoutDate =*/ timeoutDate }); ReloadCache(); } - - public void ReadLockServers() - { - UnitOfWork.ReadLockNodes(_lockIds); - } - - public void WriteLockServers() - { - UnitOfWork.WriteLockNodes(_lockIds); - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 0e6fb58dea..3b0550a8a6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -230,7 +230,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// - protected void UpdatePropertyTags(IContentBase entity, ITagRepository tagRepo) + protected void UpdateEntityTags(IContentBase entity, ITagRepository tagRepo) { foreach (var tagProp in entity.Properties.Where(x => x.TagSupport.Enable)) { @@ -254,6 +254,10 @@ namespace Umbraco.Core.Persistence.Repositories } } + protected bool HasTagProperty(IContentBase entity) + { + return entity.Properties.Any(x => x.TagSupport.Enable); + } private Sql PrepareSqlForPagedResults(Sql sql, Sql filterSql, string orderBy, Direction orderDirection, bool orderBySystemField) { @@ -315,14 +319,14 @@ namespace Umbraco.Core.Persistence.Repositories END AS CustomPropVal, cd.nodeId AS CustomPropValContentId FROM cmsDocument cd - INNER JOIN cmsPropertyData cpd ON cpd.contentNodeId = cd.nodeId AND cpd.versionId = cd.versionId + INNER JOIN cmsPropertyData cpd ON cpd.contentNodeId = cd.nodeId AND cpd.versionId = cd.versionId INNER JOIN cmsPropertyType cpt ON cpt.Id = cpd.propertytypeId WHERE cpt.Alias = @2 AND cd.newest = 1) AS CustomPropData ON CustomPropData.CustomPropValContentId = umbracoNode.id ", sortedInt, sortedDecimal, sortedDate, sortedString); //insert this just above the LEFT OUTER JOIN - var newSql = psql.SQL.Insert(psql.SQL.IndexOf("LEFT OUTER JOIN"), innerJoinTempTable); + var newSql = psql.SQL.Insert(psql.SQL.IndexOf("LEFT OUTER JOIN"), innerJoinTempTable); var newArgs = psql.Arguments.ToList(); newArgs.Add(orderBy); @@ -522,7 +526,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// - public virtual bool DeleteMediaFiles(IEnumerable files) + public virtual bool DeleteMediaFiles(IEnumerable files) // fixme kill eventually { //ensure duplicates are removed files = files.Distinct(); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index fc26bcd7c9..90e01b88fb 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -17,6 +17,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax string GetWildcardPlaceholder(); string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType); string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType); + string GetConcat(params string[] args); [Obsolete("Use the overload with the parameter index instead")] string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index 479d575fc1..4d2200b44c 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// /// Represents an SqlSyntaxProvider for MySql /// - [SqlSyntaxProviderAttribute(Constants.DbProviderNames.MySql)] + [SqlSyntaxProvider(Constants.DbProviderNames.MySql)] public class MySqlSyntaxProvider : SqlSyntaxProviderBase { private readonly ILogger _logger; diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index 81db82a890..b169943b80 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -11,14 +11,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// /// Represents an SqlSyntaxProvider for Sql Ce /// - [SqlSyntaxProviderAttribute(Constants.DbProviderNames.SqlCe)] + [SqlSyntaxProvider(Constants.DbProviderNames.SqlCe)] public class SqlCeSyntaxProvider : MicrosoftSqlSyntaxProviderBase { - public SqlCeSyntaxProvider() - { - - } - public override bool SupportsClustered() { return false; @@ -64,7 +59,10 @@ namespace Umbraco.Core.Persistence.SqlSyntax } } - + public override string GetConcat(params string[] args) + { + return "(" + string.Join("+", args) + ")"; + } public override string FormatColumnRename(string tableName, string oldName, string newName) { diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index a992cdd231..e194c7c79c 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -71,7 +71,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax public string DateTimeColumnDefinition = "DATETIME"; public string TimeColumnDefinition = "DATETIME"; - protected IList> ClauseOrder { get; set; } + protected IList> ClauseOrder { get; } protected DbTypes DbTypeMap = new DbTypes(); protected void InitColumnTypeMap() @@ -172,6 +172,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax return string.Format("upper({0}) LIKE '{1}'", column, value.ToUpper()); } + public virtual string GetConcat(params string[] args) + { + return "concat(" + string.Join(",", args) + ")"; + } + public virtual string GetQuotedTableName(string tableName) { return string.Format("\"{0}\"", tableName); diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/IDatabaseUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/IDatabaseUnitOfWork.cs index 9bced69f52..42aeeefdd5 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/IDatabaseUnitOfWork.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/IDatabaseUnitOfWork.cs @@ -7,7 +7,7 @@ namespace Umbraco.Core.Persistence.UnitOfWork { UmbracoDatabase Database { get; } - void ReadLockNodes(params int[] lockIds); - void WriteLockNodes(params int[] lockIds); + void ReadLock(params int[] lockIds); + void WriteLock(params int[] lockIds); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWork.cs index 0f8d775020..e59473436e 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWork.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWork.cs @@ -63,6 +63,9 @@ namespace Umbraco.Core.Persistence.UnitOfWork /// If any operation is added to the unit of work after it has been completed, then its completion /// status is resetted. So in a way it could be possible to always complete and never flush, but flush /// is preferred when appropriate to indicate that you understand what you are doing. + /// Every units of work should be completed, unless a rollback is required. That is, even if the unit of + /// work contains only read operations, that do not need to be "commited", the unit of work should be + /// properly completed, else it may force an unexpected rollback of a higher-level transaction. /// void Complete(); diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/NPocoUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/NPocoUnitOfWork.cs index 08550c2b76..cfa530c76e 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/NPocoUnitOfWork.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/NPocoUnitOfWork.cs @@ -70,7 +70,7 @@ namespace Umbraco.Core.Persistence.UnitOfWork _transaction = null; } - public void ReadLockNodes(params int[] lockIds) + public void ReadLock(params int[] lockIds) { Begin(); // we need a transaction @@ -78,11 +78,15 @@ namespace Umbraco.Core.Persistence.UnitOfWork throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required."); // *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks foreach (var lockId in lockIds) - Database.ExecuteScalar("SELECT sortOrder FROM umbracoNode WHERE id=@id", + { + var i = Database.ExecuteScalar("SELECT value FROM umbracoLock WHERE id=@id", new { @id = lockId }); + if (i == null) // ensure we are actually locking! + throw new Exception($"LockObject with id={lockId} does not exist."); + } } - public void WriteLockNodes(params int[] lockIds) + public void WriteLock(params int[] lockIds) { Begin(); // we need a transaction @@ -90,8 +94,12 @@ namespace Umbraco.Core.Persistence.UnitOfWork throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required."); // *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks foreach (var lockId in lockIds) - Database.Execute("UPDATE umbracoNode SET sortOrder = (CASE WHEN (sortOrder=1) THEN -1 ELSE 1 END) WHERE id=@id", + { + var i = Database.Execute("UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { @id = lockId }); + if (i == 0) // ensure we are actually locking! + throw new Exception($"LockObject with id={lockId} does not exist."); + } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Publishing/PublishStatus.cs b/src/Umbraco.Core/Publishing/PublishStatus.cs index 3436e9070e..f348367f22 100644 --- a/src/Umbraco.Core/Publishing/PublishStatus.cs +++ b/src/Umbraco.Core/Publishing/PublishStatus.cs @@ -6,30 +6,36 @@ using Umbraco.Core.Services; namespace Umbraco.Core.Publishing { /// - /// The result of publishing a content item + /// Represents the result of publishing a content item. /// - public class PublishStatus : OperationStatus + public class PublishStatus : OperationStatus { - public PublishStatus(IContent content, PublishStatusType statusType, EventMessages eventMessages) - : base(content, statusType, eventMessages) - { - } - /// - /// Creates a successful publish status + /// Creates a new instance of the class with a status type, event messages, and a content item. /// - public PublishStatus(IContent content, EventMessages eventMessages) - : this(content, PublishStatusType.Success, eventMessages) - { - } - - public IContent ContentItem - { - get { return Entity; } - } + /// The status of the operation. + /// Event messages produced by the operation. + /// The content item. + public PublishStatus(PublishStatusType statusType, EventMessages eventMessages, IContent content) + : base(statusType, eventMessages, content) + { } /// - /// Gets sets the invalid properties if the status failed due to validation. + /// Creates a new successful instance of the class with a event messages, and a content item. + /// + /// Event messages produced by the operation. + /// The content item. + public PublishStatus(IContent content, EventMessages eventMessages) + : base(PublishStatusType.Success, eventMessages, content) + { } + + /// + /// Gets the content item. + /// + public IContent ContentItem => Value; + + /// + /// Gets or sets the invalid properties, if the status failed due to validation. /// public IEnumerable InvalidProperties { get; set; } } diff --git a/src/Umbraco.Core/Publishing/PublishStatusType.cs b/src/Umbraco.Core/Publishing/PublishStatusType.cs index 0d9ffcfa02..e68b31c887 100644 --- a/src/Umbraco.Core/Publishing/PublishStatusType.cs +++ b/src/Umbraco.Core/Publishing/PublishStatusType.cs @@ -1,10 +1,11 @@ namespace Umbraco.Core.Publishing { /// - /// A status type of the result of publishing a content item + /// A value indicating the result of publishing a content item. /// - /// - /// Anything less than 10 = Success! + /// Do NOT compare against a hard-coded numeric value to check for success or failure, + /// but instead use the IsSuccess() extension method defined below - which should be the unique + /// place where the numeric test should take place. /// public enum PublishStatusType { @@ -14,40 +15,70 @@ namespace Umbraco.Core.Publishing Success = 0, /// - /// The item was already published + /// The item was already published. /// SuccessAlreadyPublished = 1, + // Values below this value indicate a success, values above it indicate a failure. + // This value is considered a failure. + //Reserved = 10, + /// - /// The content could not be published because it's ancestor path isn't published + /// The content could not be published because it's ancestor path isn't published. /// - FailedPathNotPublished = 10, + FailedPathNotPublished = 11, /// /// The content item was scheduled to be un-published and it has expired so we cannot force it to be /// published again as part of a bulk publish operation. /// - FailedHasExpired = 11, + FailedHasExpired = 12, /// /// The content item is scheduled to be released in the future and therefore we cannot force it to /// be published during a bulk publish operation. /// - FailedAwaitingRelease = 12, + FailedAwaitingRelease = 13, /// - /// The content item is in the trash, it cannot be published + /// The content item could not be published because it is in the trash. /// - FailedIsTrashed = 13, + FailedIsTrashed = 14, /// - /// The publish action has been cancelled by an event handler + /// The publish action has been cancelled by an event handler. /// - FailedCancelledByEvent = 14, + FailedCancelledByEvent = 15, /// - /// The content item contains invalid data (has not passed validation requirements) + /// The content item could not be published because it contains invalid data (has not passed validation requirements). /// - FailedContentInvalid = 15 + FailedContentInvalid = 16 + } + + /// + /// Provides extension methods for the enum. + /// + public static class PublicStatusTypeExtensions + { + /// + /// Gets a value indicating whether the status indicates a success. + /// + /// The status. + /// A value indicating whether the status indicates a success. + public static bool IsSuccess(this PublishStatusType status) + { + return (int) status < 10; + } + + /// + /// Gets a value indicating whether the status indicates a failure. + /// + /// The status. + /// A value indicating whether the status indicates a failure. + public static bool IsFailure(this PublishStatusType status) + { + return (int) status >= 10; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Publishing/PublishingStrategy.cs b/src/Umbraco.Core/Publishing/PublishingStrategy.cs deleted file mode 100644 index c6d4f19baf..0000000000 --- a/src/Umbraco.Core/Publishing/PublishingStrategy.cs +++ /dev/null @@ -1,469 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Events; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Publishing -{ - //TODO: Do we need this anymore?? - /// - /// Currently acts as an interconnection between the new public api and the legacy api for publishing - /// - public class PublishingStrategy : BasePublishingStrategy - { - private readonly IEventMessagesFactory _eventMessagesFactory; - private readonly ILogger _logger; - - public PublishingStrategy(IEventMessagesFactory eventMessagesFactory, ILogger logger) - { - if (eventMessagesFactory == null) throw new ArgumentNullException("eventMessagesFactory"); - if (logger == null) throw new ArgumentNullException("logger"); - _eventMessagesFactory = eventMessagesFactory; - _logger = logger; - } - - /// - /// Publishes a single piece of Content - /// - /// to publish - /// Id of the User issueing the publish operation - internal Attempt PublishInternal(IContent content, int userId) - { - var evtMsgs = _eventMessagesFactory.Get(); - - if (Publishing.IsRaisedEventCancelled( - new PublishEventArgs(content, evtMsgs), this)) - { - _logger.Info( - string.Format("Content '{0}' with Id '{1}' will not be published, the event was cancelled.", content.Name, content.Id)); - return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent, evtMsgs)); - } - - //Check if the Content is Expired to verify that it can in fact be published - if (content.Status == ContentStatus.Expired) - { - _logger.Info( - string.Format("Content '{0}' with Id '{1}' has expired and could not be published.", - content.Name, content.Id)); - return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedHasExpired, evtMsgs)); - } - - //Check if the Content is Awaiting Release to verify that it can in fact be published - if (content.Status == ContentStatus.AwaitingRelease) - { - _logger.Info( - string.Format("Content '{0}' with Id '{1}' is awaiting release and could not be published.", - content.Name, content.Id)); - return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedAwaitingRelease, evtMsgs)); - } - - //Check if the Content is Trashed to verify that it can in fact be published - if (content.Status == ContentStatus.Trashed) - { - _logger.Info( - string.Format("Content '{0}' with Id '{1}' is trashed and could not be published.", - content.Name, content.Id)); - return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedIsTrashed, evtMsgs)); - } - - content.ChangePublishedState(PublishedState.Published); - - _logger.Info( - string.Format("Content '{0}' with Id '{1}' has been published.", - content.Name, content.Id)); - - return Attempt.Succeed(new PublishStatus(content, evtMsgs)); - } - - /// - /// Publishes a single piece of Content - /// - /// to publish - /// Id of the User issueing the publish operation - /// True if the publish operation was successfull and not cancelled, otherwise false - public override bool Publish(IContent content, int userId) - { - return PublishInternal(content, userId).Success; - } - - /// - /// Publishes a list of content items - /// - /// - /// - /// - /// By default this is set to true which means that it will publish any content item in the list that is completely unpublished and - /// not visible on the front-end. If set to false, this will only publish content that is live on the front-end but has new versions - /// that have yet to be published. - /// - /// - /// - /// - /// This method becomes complex once we start to be able to cancel events or stop publishing a content item in any way because if a - /// content item is not published then it's children shouldn't be published either. This rule will apply for the following conditions: - /// * If a document fails to be published, do not proceed to publish it's children if: - /// ** The document does not have a publish version - /// ** The document does have a published version but the includeUnpublishedDocuments = false - /// - /// In order to do this, we will order the content by level and begin by publishing each item at that level, then proceed to the next - /// level and so on. If we detect that the above rule applies when the document publishing is cancelled we'll add it to the list of - /// parentsIdsCancelled so that it's children don't get published. - /// - /// Its important to note that all 'root' documents included in the list *will* be published regardless of the rules mentioned - /// above (unless it is invalid)!! By 'root' documents we are referring to documents in the list with the minimum value for their 'level'. - /// In most cases the 'root' documents will only be one document since under normal circumstance we only publish one document and - /// its children. The reason we have to do this is because if a user is publishing a document and it's children, it is implied that - /// the user definitely wants to publish it even if it has never been published before. - /// - /// - internal IEnumerable> PublishWithChildrenInternal( - IEnumerable content, int userId, bool includeUnpublishedDocuments = true) - { - var statuses = new List>(); - - //a list of all document ids that had their publishing cancelled during these iterations. - //this helps us apply the rule listed in the notes above by checking if a document's parent id - //matches one in this list. - var parentsIdsCancelled = new List(); - - //group by levels and iterate over the sorted ascending level. - //TODO: This will cause all queries to execute, they will not be lazy but I'm not really sure being lazy actually made - // much difference because we iterate over them all anyways?? Morten? - // Because we're grouping I think this will execute all the queries anyways so need to fetch it all first. - var fetchedContent = content.ToArray(); - - var evtMsgs = _eventMessagesFactory.Get(); - - //We're going to populate the statuses with all content that is already published because below we are only going to iterate over - // content that is not published. We'll set the status to "AlreadyPublished" - statuses.AddRange(fetchedContent.Where(x => x.Published) - .Select(x => Attempt.Succeed(new PublishStatus(x, PublishStatusType.SuccessAlreadyPublished, evtMsgs)))); - - int? firstLevel = null; - - //group by level and iterate over each level (sorted ascending) - var levelGroups = fetchedContent.GroupBy(x => x.Level); - foreach (var level in levelGroups.OrderBy(x => x.Key)) - { - //set the first level flag, used to ensure that all documents at the first level will - //be published regardless of the rules mentioned in the remarks. - if (!firstLevel.HasValue) - { - firstLevel = level.Key; - } - - /* Only update content thats not already been published - we want to loop through - * all unpublished content to write skipped content (expired and awaiting release) to log. - */ - foreach (var item in level.Where(x => x.Published == false)) - { - //Check if this item should be excluded because it's parent's publishing has failed/cancelled - if (parentsIdsCancelled.Contains(item.ParentId)) - { - _logger.Info( - string.Format("Content '{0}' with Id '{1}' will not be published because it's parent's publishing action failed or was cancelled.", item.Name, item.Id)); - //if this cannot be published, ensure that it's children can definitely not either! - parentsIdsCancelled.Add(item.Id); - continue; - } - - //Check if this item has never been published (and that it is not at the root level) - if (item.Level != firstLevel && !includeUnpublishedDocuments && !item.HasPublishedVersion()) - { - //this item does not have a published version and the flag is set to not include them - parentsIdsCancelled.Add(item.Id); - continue; - } - - //Fire Publishing event - if (Publishing.IsRaisedEventCancelled( - new PublishEventArgs(item, evtMsgs), this)) - { - //the publishing has been cancelled. - _logger.Info( - string.Format("Content '{0}' with Id '{1}' will not be published, the event was cancelled.", item.Name, item.Id)); - statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedCancelledByEvent, evtMsgs))); - - //Does this document apply to our rule to cancel it's children being published? - CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments); - - continue; - } - - //Check if the content is valid if the flag is set to check - if (item.IsValid() == false) - { - _logger.Info( - string.Format("Content '{0}' with Id '{1}' will not be published because some of it's content is not passing validation rules.", - item.Name, item.Id)); - statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedContentInvalid, evtMsgs))); - - //Does this document apply to our rule to cancel it's children being published? - CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments); - - continue; - } - - //Check if the Content is Expired to verify that it can in fact be published - if (item.Status == ContentStatus.Expired) - { - _logger.Info( - string.Format("Content '{0}' with Id '{1}' has expired and could not be published.", - item.Name, item.Id)); - statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedHasExpired, evtMsgs))); - - //Does this document apply to our rule to cancel it's children being published? - CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments); - - continue; - } - - //Check if the Content is Awaiting Release to verify that it can in fact be published - if (item.Status == ContentStatus.AwaitingRelease) - { - _logger.Info( - string.Format("Content '{0}' with Id '{1}' is awaiting release and could not be published.", - item.Name, item.Id)); - statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedAwaitingRelease, evtMsgs))); - - //Does this document apply to our rule to cancel it's children being published? - CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments); - - continue; - } - - //Check if the Content is Trashed to verify that it can in fact be published - if (item.Status == ContentStatus.Trashed) - { - _logger.Info( - string.Format("Content '{0}' with Id '{1}' is trashed and could not be published.", - item.Name, item.Id)); - statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedIsTrashed, evtMsgs))); - - //Does this document apply to our rule to cancel it's children being published? - CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments); - - continue; - } - - item.ChangePublishedState(PublishedState.Published); - - _logger.Info( - string.Format("Content '{0}' with Id '{1}' has been published.", - item.Name, item.Id)); - - statuses.Add(Attempt.Succeed(new PublishStatus(item, evtMsgs))); - } - - } - - return statuses; - } - - /// - /// Based on the information provider we'll check if we should cancel the publishing of this document's children - /// - /// - /// - /// - /// - /// See remarks on method: PublishWithChildrenInternal - /// - private void CheckCancellingOfChildPublishing(IContent content, List parentsIdsCancelled, bool includeUnpublishedDocuments) - { - //Does this document apply to our rule to cancel it's children being published? - //TODO: We're going back to the service layer here... not sure how to avoid this? And this will add extra overhead to - // any document that fails to publish... - var hasPublishedVersion = ApplicationContext.Current.Services.ContentService.HasPublishedVersion(content.Id); - - if (hasPublishedVersion && !includeUnpublishedDocuments) - { - //it has a published version but our flag tells us to not include un-published documents and therefore we should - // not be forcing decendant/child documents to be published if their parent fails. - parentsIdsCancelled.Add(content.Id); - } - else if (!hasPublishedVersion) - { - //it doesn't have a published version so we certainly cannot publish it's children. - parentsIdsCancelled.Add(content.Id); - } - } - - /// - /// Publishes a list of Content - /// - /// An enumerable list of - /// Id of the User issueing the publish operation - /// True if the publish operation was successfull and not cancelled, otherwise false - public override bool PublishWithChildren(IEnumerable content, int userId) - { - var result = PublishWithChildrenInternal(content, userId); - - //NOTE: This previously always returned true so I've left it that way. It returned true because (from Morten)... - // ... if one item couldn't be published it wouldn't be correct to return false. - // in retrospect it should have returned a list of with Ids and Publish Status - // come to think of it ... the cache would still be updated for a failed item or at least tried updated. - // It would call the Published event for the entire list, but if the Published property isn't set to True it - // wouldn't actually update the cache for that item. But not really ideal nevertheless... - return true; - } - - /// - /// Unpublishes a single piece of Content - /// - /// to unpublish - /// Id of the User issueing the unpublish operation - /// True if the unpublish operation was successfull and not cancelled, otherwise false - public override bool UnPublish(IContent content, int userId) - { - return UnPublishInternal(content, userId).Success; - } - - /// - /// Unpublishes a list of Content - /// - /// An enumerable list of - /// Id of the User issueing the unpublish operation - /// A list of publish statuses - private IEnumerable> UnPublishInternal(IEnumerable content, int userId) - { - return content.Select(x => UnPublishInternal(x, userId)); - } - - private Attempt UnPublishInternal(IContent content, int userId) - { - // content should (is assumed to ) be the newest version, which may not be published - // don't know how to test this, so it's not verified - // NOTE - // if published != newest, then the published flags need to be reseted by whoever is calling that method - // at the moment it's done by the content service - - var evtMsgs = _eventMessagesFactory.Get(); - - //Fire UnPublishing event - if (UnPublishing.IsRaisedEventCancelled( - new PublishEventArgs(content, evtMsgs), this)) - { - _logger.Info( - string.Format("Content '{0}' with Id '{1}' will not be unpublished, the event was cancelled.", content.Name, content.Id)); - return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent, evtMsgs)); - } - - //If Content has a release date set to before now, it should be removed so it doesn't interrupt an unpublish - //Otherwise it would remain released == published - if (content.ReleaseDate.HasValue && content.ReleaseDate.Value <= DateTime.Now) - { - content.ReleaseDate = null; - - _logger.Info( - string.Format("Content '{0}' with Id '{1}' had its release date removed, because it was unpublished.", - content.Name, content.Id)); - } - - // if newest is published, unpublish - if (content.Published) - content.ChangePublishedState(PublishedState.Unpublished); - - _logger.Info( - string.Format("Content '{0}' with Id '{1}' has been unpublished.", - content.Name, content.Id)); - - return Attempt.Succeed(new PublishStatus(content, evtMsgs)); - } - - /// - /// Unpublishes a list of Content - /// - /// An enumerable list of - /// Id of the User issueing the unpublish operation - /// True if the unpublish operation was successfull and not cancelled, otherwise false - public override bool UnPublish(IEnumerable content, int userId) - { - var result = UnPublishInternal(content, userId); - - //NOTE: This previously always returned true so I've left it that way. It returned true because (from Morten)... - // ... if one item couldn't be published it wouldn't be correct to return false. - // in retrospect it should have returned a list of with Ids and Publish Status - // come to think of it ... the cache would still be updated for a failed item or at least tried updated. - // It would call the Published event for the entire list, but if the Published property isn't set to True it - // wouldn't actually update the cache for that item. But not really ideal nevertheless... - return true; - } - - /// - /// Call to fire event that updating the published content has finalized. - /// - /// - /// This seperation of the OnPublished event is done to ensure that the Content - /// has been properly updated (committed unit of work) and xml saved in the db. - /// - /// thats being published - public override void PublishingFinalized(IContent content) - { - var evtMsgs = _eventMessagesFactory.Get(); - Published.RaiseEvent( - new PublishEventArgs(content, false, false, evtMsgs), this); - } - - /// - /// Call to fire event that updating the published content has finalized. - /// - /// An enumerable list of thats being published - /// Boolean indicating whether its all content that is republished - public override void PublishingFinalized(IEnumerable content, bool isAllRepublished) - { - var evtMsgs = _eventMessagesFactory.Get(); - Published.RaiseEvent( - new PublishEventArgs(content, false, isAllRepublished, evtMsgs), this); - - } - - /// - /// Call to fire event that updating the unpublished content has finalized. - /// - /// thats being unpublished - public override void UnPublishingFinalized(IContent content) - { - var evtMsgs = _eventMessagesFactory.Get(); - UnPublished.RaiseEvent( - new PublishEventArgs(content, false, false, evtMsgs), this); - } - - /// - /// Call to fire event that updating the unpublished content has finalized. - /// - /// An enumerable list of thats being unpublished - public override void UnPublishingFinalized(IEnumerable content) - { - var evtMsgs = _eventMessagesFactory.Get(); - UnPublished.RaiseEvent( - new PublishEventArgs(content, false, false, evtMsgs), this); - } - - /// - /// Occurs before publish - /// - public static event TypedEventHandler> Publishing; - - /// - /// Occurs after publish - /// - public static event TypedEventHandler> Published; - - /// - /// Occurs before unpublish - /// - public static event TypedEventHandler> UnPublishing; - - /// - /// Occurs after unpublish - /// - public static event TypedEventHandler> UnPublished; - - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Publishing/UnPublishStatus.cs b/src/Umbraco.Core/Publishing/UnPublishStatus.cs index 80ab786e1a..91f242cc6f 100644 --- a/src/Umbraco.Core/Publishing/UnPublishStatus.cs +++ b/src/Umbraco.Core/Publishing/UnPublishStatus.cs @@ -5,26 +5,32 @@ using Umbraco.Core.Services; namespace Umbraco.Core.Publishing { /// - /// The result of unpublishing a content item + /// Represents the result of unpublishing a content item. /// - public class UnPublishStatus : OperationStatus + public class UnPublishStatus : OperationStatus { - public UnPublishStatus(IContent content, UnPublishedStatusType statusType, EventMessages eventMessages) - : base(content, statusType, eventMessages) - { - } + /// + /// Creates a new instance of the class with a status type, event messages, and a content item. + /// + /// The status of the operation. + /// Event messages produced by the operation. + /// The content item. + public UnPublishStatus(UnPublishedStatusType statusType, EventMessages eventMessages, IContent content) + : base(statusType, eventMessages, content) + { } /// - /// Creates a successful unpublish status + /// Creates a new successful instance of the class with a event messages, and a content item. /// + /// Event messages produced by the operation. + /// The content item. public UnPublishStatus(IContent content, EventMessages eventMessages) - : this(content, UnPublishedStatusType.Success, eventMessages) - { - } + : base(UnPublishedStatusType.Success, eventMessages, content) + { } - public IContent ContentItem - { - get { return Entity; } - } + /// + /// Gets the content item. + /// + public IContent ContentItem => Value; } } \ No newline at end of file diff --git a/src/Umbraco.Core/ServiceContextExtensions.cs b/src/Umbraco.Core/ServiceContextExtensions.cs new file mode 100644 index 0000000000..b1ca021173 --- /dev/null +++ b/src/Umbraco.Core/ServiceContextExtensions.cs @@ -0,0 +1,21 @@ +using System; +using Umbraco.Core.Models; +using Umbraco.Core.Services; + +namespace Umbraco.Core +{ + public static class ServiceContextExtensions + { + public static IContentTypeServiceBase GetContentTypeService(this ServiceContext services) + where T : IContentTypeComposition + { + if (typeof(T).Implements()) + return services.ContentTypeService as IContentTypeServiceBase; + if (typeof(T).Implements()) + return services.MediaTypeService as IContentTypeServiceBase; + if (typeof(T).Implements()) + return services.MemberTypeService as IContentTypeServiceBase; + throw new ArgumentException("Type " + typeof(T).FullName + " does not have a service."); + } + } +} diff --git a/src/Umbraco.Core/Services/AuditService.cs b/src/Umbraco.Core/Services/AuditService.cs index 24a950a0b4..614d44ac98 100644 --- a/src/Umbraco.Core/Services/AuditService.cs +++ b/src/Umbraco.Core/Services/AuditService.cs @@ -33,6 +33,7 @@ namespace Umbraco.Core.Services { var repo = uow.CreateRepository(); var result = repo.GetByQuery(repo.Query.Where(x => x.Id == objectId)); + uow.Complete(); return result; } } @@ -45,6 +46,7 @@ namespace Umbraco.Core.Services var result = sinceDate.HasValue == false ? repo.GetByQuery(repo.Query.Where(x => x.UserId == userId && x.AuditType == type)) : repo.GetByQuery(repo.Query.Where(x => x.UserId == userId && x.AuditType == type && x.CreateDate >= sinceDate.Value)); + uow.Complete(); return result; } } @@ -57,6 +59,7 @@ namespace Umbraco.Core.Services var result = sinceDate.HasValue == false ? repo.GetByQuery(repo.Query.Where(x => x.AuditType == type)) : repo.GetByQuery(repo.Query.Where(x => x.AuditType == type && x.CreateDate >= sinceDate.Value)); + uow.Complete(); return result; } } @@ -67,6 +70,7 @@ namespace Umbraco.Core.Services { var repo = uow.CreateRepository(); repo.CleanLogs(maximumAgeOfLogsInMinutes); + uow.Complete(); } } } diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index eb50815957..ce9c9ad323 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1,22 +1,16 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Globalization; using System.Linq; -using System.Threading; using System.Xml.Linq; -using Umbraco.Core.Configuration; using Umbraco.Core.Events; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence; - using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Publishing; using Umbraco.Core.Strings; @@ -28,42 +22,58 @@ namespace Umbraco.Core.Services /// public class ContentService : RepositoryService, IContentService, IContentServiceOperations { - private readonly IPublishingStrategy _publishingStrategy; private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); private readonly IDataTypeService _dataTypeService; private readonly IUserService _userService; private readonly IEnumerable _urlSegmentProviders; + private IContentTypeService _contentTypeService; - //Support recursive locks because some of the methods that require locking call other methods that require locking. - //for example, the Move method needs to be locked but this calls the Save method which also needs to be locked. - private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + #region Constructors public ContentService( IDatabaseUnitOfWorkProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, - IPublishingStrategy publishingStrategy, IDataTypeService dataTypeService, IUserService userService, IEnumerable urlSegmentProviders) : base(provider, logger, eventMessagesFactory) { - if (publishingStrategy == null) throw new ArgumentNullException("publishingStrategy"); - if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); - if (userService == null) throw new ArgumentNullException("userService"); - if (urlSegmentProviders == null) throw new ArgumentNullException("urlSegmentProviders"); - _publishingStrategy = publishingStrategy; + if (dataTypeService == null) throw new ArgumentNullException(nameof(dataTypeService)); + if (userService == null) throw new ArgumentNullException(nameof(userService)); + if (urlSegmentProviders == null) throw new ArgumentNullException(nameof(urlSegmentProviders)); _dataTypeService = dataTypeService; _userService = userService; _urlSegmentProviders = urlSegmentProviders; } + // don't change or remove this, will need it later + private IContentTypeService ContentTypeService => _contentTypeService; + //// handle circular dependencies + //internal IContentTypeService ContentTypeService + //{ + // get + // { + // if (_contentTypeService == null) + // throw new InvalidOperationException("ContentService.ContentTypeService has not been initialized."); + // return _contentTypeService; + // } + // set { _contentTypeService = value; } + //} + + #endregion + + #region Count + public int CountPublished(string contentTypeAlias = null) { using (var uow = UowProvider.CreateUnitOfWork()) { - var repository = uow.CreateRepository(); - return repository.CountPublished(); + uow.ReadLock(Constants.Locks.ContentTree); + var repo = uow.CreateRepository(); + var count = repo.CountPublished(); + uow.Complete(); + return count; } } @@ -71,8 +81,11 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { - var repository = uow.CreateRepository(); - return repository.Count(contentTypeAlias); + uow.ReadLock(Constants.Locks.ContentTree); + var repo = uow.CreateRepository(); + var count = repo.Count(contentTypeAlias); + uow.Complete(); + return count; } } @@ -80,8 +93,11 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { - var repository = uow.CreateRepository(); - return repository.CountChildren(parentId, contentTypeAlias); + uow.ReadLock(Constants.Locks.ContentTree); + var repo = uow.CreateRepository(); + var count = repo.CountChildren(parentId, contentTypeAlias); + uow.Complete(); + return count; } } @@ -89,11 +105,18 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { - var repository = uow.CreateRepository(); - return repository.CountDescendants(parentId, contentTypeAlias); + uow.ReadLock(Constants.Locks.ContentTree); + var repo = uow.CreateRepository(); + var count = repo.CountDescendants(parentId, contentTypeAlias); + uow.Complete(); + return count; } } + #endregion + + #region Permissions + /// /// Used to bulk update the permissions set for a content item. This will replace all permissions /// assigned to an entity with a list of user id & permission pairs. @@ -103,8 +126,10 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { - var repository = uow.CreateRepository(); - repository.ReplaceContentPermissions(permissionSet); + uow.WriteLock(Constants.Locks.ContentTree); + var repo = uow.CreateRepository(); + repo.ReplaceContentPermissions(permissionSet); + uow.Complete(); } } @@ -118,8 +143,10 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { - var repository = uow.CreateRepository(); - repository.AssignEntityPermission(entity, permission, userIds); + uow.WriteLock(Constants.Locks.ContentTree); + var repo = uow.CreateRepository(); + repo.AssignEntityPermission(entity, permission, userIds); + uow.Complete(); } } @@ -132,194 +159,208 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { - var repository = uow.CreateRepository(); - return repository.GetPermissionsForEntity(content.Id); + uow.ReadLock(Constants.Locks.ContentTree); + var repo = uow.CreateRepository(); + var perms = repo.GetPermissionsForEntity(content.Id); + uow.Complete(); + return perms; } } + #endregion + + #region Create + /// - /// Creates an object using the alias of the - /// that this Content should based on. + /// Creates an object of a specified content type. /// - /// - /// Note that using this method will simply return a new IContent without any identity - /// as it has not yet been persisted. It is intended as a shortcut to creating new content objects - /// that does not invoke a save operation against the database. + /// This method simply returns a new, non-persisted, IContent without any identity. It + /// is intended as a shortcut to creating new content objects that does not invoke a save + /// operation against the database. /// - /// Name of the Content object - /// Id of Parent for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// + /// The name of the content object. + /// The identifier of the parent, or -1. + /// The alias of the content type. + /// The optional id of the user creating the content. + /// The content object. public IContent CreateContent(string name, int parentId, string contentTypeAlias, int userId = 0) { - var contentType = FindContentTypeByAlias(contentTypeAlias); + var contentType = GetContentType(contentTypeAlias); + if (contentType == null) + throw new ArgumentException("No content type with that alias.", nameof(contentTypeAlias)); + var parent = parentId > 0 ? GetById(parentId) : null; + if (parentId > 0 && parent == null) + throw new ArgumentException("No content with that id.", nameof(parentId)); + var content = new Content(name, parentId, contentType); - var parent = GetById(content.ParentId); - content.Path = string.Concat(parent.IfNotNull(x => x.Path, content.ParentId.ToString()), ",", content.Id); - - - if (Creating.IsRaisedEventCancelled(new NewEventArgs(content, contentTypeAlias, parentId), this)) - { - content.WasCancelled = true; - return content; - } - - content.CreatorId = userId; - content.WriterId = userId; - - Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parentId), this); - - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repo = uow.CreateRepository(); - repo.AddOrUpdate(new AuditItem(content.Id, $"Content '{name}' was created", AuditType.New, content.CreatorId)); - uow.Complete(); - } + CreateContent(null, content, parent, userId, false); return content; } /// - /// Creates an object using the alias of the - /// that this Content should based on. + /// Creates an object of a specified content type, at root. /// - /// - /// Note that using this method will simply return a new IContent without any identity - /// as it has not yet been persisted. It is intended as a shortcut to creating new content objects - /// that does not invoke a save operation against the database. + /// This method simply returns a new, non-persisted, IContent without any identity. It + /// is intended as a shortcut to creating new content objects that does not invoke a save + /// operation against the database. /// - /// Name of the Content object - /// Parent object for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// + /// The name of the content object. + /// The alias of the content type. + /// The optional id of the user creating the content. + /// The content object. + public IContent CreateContent(string name, string contentTypeAlias, int userId = 0) + { + // not locking since not saving anything + + var contentType = GetContentType(contentTypeAlias); + if (contentType == null) + throw new ArgumentException("No content type with that alias.", nameof(contentTypeAlias)); + + var content = new Content(name, -1, contentType); + CreateContent(null, content, null, userId, false); + + return content; + } + + /// + /// Creates an object of a specified content type, under a parent. + /// + /// This method simply returns a new, non-persisted, IContent without any identity. It + /// is intended as a shortcut to creating new content objects that does not invoke a save + /// operation against the database. + /// + /// The name of the content object. + /// The parent content object. + /// The alias of the content type. + /// The optional id of the user creating the content. + /// The content object. public IContent CreateContent(string name, IContent parent, string contentTypeAlias, int userId = 0) { - if (parent == null) throw new ArgumentNullException("parent"); + if (parent == null) throw new ArgumentNullException(nameof(parent)); - var contentType = FindContentTypeByAlias(contentTypeAlias); - var content = new Content(name, parent, contentType); - content.Path = string.Concat(parent.Path, ",", content.Id); - - if (Creating.IsRaisedEventCancelled(new NewEventArgs(content, contentTypeAlias, parent), this)) + using (var uow = UowProvider.CreateUnitOfWork()) { - content.WasCancelled = true; + // not locking since not saving anything + + var contentType = GetContentType(contentTypeAlias); + if (contentType == null) + throw new ArgumentException("No content type with that alias.", nameof(contentTypeAlias)); // causes rollback + + var content = new Content(name, parent, contentType); + CreateContent(uow, content, parent, userId, false); + + uow.Complete(); return content; } - - content.CreatorId = userId; - content.WriterId = userId; - - Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parent), this); - - Audit(AuditType.New, string.Format("Content '{0}' was created", name), content.CreatorId, content.Id); - - return content; } /// - /// Creates and saves an object using the alias of the - /// that this Content should based on. + /// Creates an object of a specified content type. /// - /// - /// This method returns an object that has been persisted to the database - /// and therefor has an identity. - /// - /// Name of the Content object - /// Id of Parent for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// + /// This method returns a new, persisted, IContent with an identity. + /// The name of the content object. + /// The identifier of the parent, or -1. + /// The alias of the content type. + /// The optional id of the user creating the content. + /// The content object. public IContent CreateContentWithIdentity(string name, int parentId, string contentTypeAlias, int userId = 0) { - var contentType = FindContentTypeByAlias(contentTypeAlias); - var content = new Content(name, parentId, contentType); - - //NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found - // out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now. - if (Creating.IsRaisedEventCancelled(new NewEventArgs(content, contentTypeAlias, parentId), this)) - { - content.WasCancelled = true; - return content; - } - - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(content), this)) - { - content.WasCancelled = true; - return content; - } - - content.CreatorId = userId; - content.WriterId = userId; - using (var uow = UowProvider.CreateUnitOfWork()) { - var repository = uow.CreateRepository(); - repository.AddOrUpdate(content); - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); + // locking the content tree secures content types too + uow.WriteLock(Constants.Locks.ContentTree); + + var contentType = GetContentType(contentTypeAlias); // + locks + if (contentType == null) + throw new ArgumentException("No content type with that alias.", nameof(contentTypeAlias)); // causes rollback + + var parent = parentId > 0 ? GetById(parentId) : null; // + locks + if (parentId > 0 && parent == null) + throw new ArgumentException("No content with that id.", nameof(parentId)); // causes rollback + + var content = parentId > 0 ? new Content(name, parent, contentType) : new Content(name, parentId, contentType); + CreateContent(uow, content, parent, userId, true); + uow.Complete(); + return content; } - - Saved.RaiseEvent(new SaveEventArgs(content, false), this); - Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parentId), this); - Audit(AuditType.New, $"Content '{name}' was created with Id {content.Id}", content.CreatorId, content.Id); - - return content; } /// - /// Creates and saves an object using the alias of the - /// that this Content should based on. + /// Creates an object of a specified content type, under a parent. /// - /// - /// This method returns an object that has been persisted to the database - /// and therefor has an identity. - /// - /// Name of the Content object - /// Parent object for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// + /// This method returns a new, persisted, IContent with an identity. + /// The name of the content object. + /// The parent content object. + /// The alias of the content type. + /// The optional id of the user creating the content. + /// The content object. public IContent CreateContentWithIdentity(string name, IContent parent, string contentTypeAlias, int userId = 0) { - if (parent == null) throw new ArgumentNullException("parent"); + if (parent == null) throw new ArgumentNullException(nameof(parent)); - var contentType = FindContentTypeByAlias(contentTypeAlias); - var content = new Content(name, parent, contentType); - - //NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found - // out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now. - if (Creating.IsRaisedEventCancelled(new NewEventArgs(content, contentTypeAlias, parent), this)) + using (var uow = UowProvider.CreateUnitOfWork()) { - content.WasCancelled = true; + // locking the content tree secures content types too + uow.WriteLock(Constants.Locks.ContentTree); + + var contentType = GetContentType(contentTypeAlias); // + locks + if (contentType == null) + throw new ArgumentException("No content type with that alias.", nameof(contentTypeAlias)); // causes rollback + + var content = new Content(name, parent, contentType); + CreateContent(uow, content, parent, userId, true); + + uow.Complete(); return content; } + } - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(content), this)) + private void CreateContent(IDatabaseUnitOfWork uow, Content content, IContent parent, int userId, bool withIdentity) + { + // NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found + // out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now. + var newArgs = parent != null + ? new NewEventArgs(content, content.ContentType.Alias, parent) + : new NewEventArgs(content, content.ContentType.Alias, -1); + + if (Creating.IsRaisedEventCancelled(newArgs, this)) { content.WasCancelled = true; - return content; + return; } content.CreatorId = userId; content.WriterId = userId; - using (var uow = UowProvider.CreateUnitOfWork()) + if (withIdentity) { - var repository = uow.CreateRepository(); - repository.AddOrUpdate(content); - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); - uow.Complete(); + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(content), this)) + { + content.WasCancelled = true; + return; + } + + var repo = uow.CreateRepository(); + repo.AddOrUpdate(content); + repo.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); + + Saved.RaiseEvent(new SaveEventArgs(content, false), this); } - Saved.RaiseEvent(new SaveEventArgs(content, false), this); - Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parent), this); - Audit(AuditType.New, $"Content '{name}' was created with Id {content.Id}", content.CreatorId, content.Id); + Created.RaiseEvent(new NewEventArgs(content, false, content.ContentType.Alias, parent), this); - return content; + var msg = withIdentity + ? "Content '{0}' was created with Id {1}" + : "Content '{0}' was created"; + Audit(AuditType.New, string.Format(msg, content.Name, content.Id), content.CreatorId, content.Id); } + #endregion + + #region Get, Has, Is + /// /// Gets an object by Id /// @@ -329,8 +370,11 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); - return repository.Get(id); + var content = repository.Get(id); + uow.Complete(); + return content; } } @@ -341,12 +385,16 @@ namespace Umbraco.Core.Services /// public IEnumerable GetByIds(IEnumerable ids) { - if (ids.Any() == false) return Enumerable.Empty(); + var idsA = ids.ToArray(); + if (idsA.Length == 0) return Enumerable.Empty(); using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); - return repository.GetAll(ids.ToArray()); + var content = repository.GetAll(idsA); + uow.Complete(); + return content; } } @@ -359,10 +407,13 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.Key == key); var contents = repository.GetByQuery(query); - return contents.SingleOrDefault(); + var content = contents.SingleOrDefault(); + uow.Complete(); + return content; } } @@ -375,11 +426,12 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.ContentTypeId == id); - var contents = repository.GetByQuery(query); - - return contents; + var content = repository.GetByQuery(query); + uow.Complete(); + return content; } } @@ -387,11 +439,12 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.ContentTypeId == id); - var contents = repository.GetByPublishedVersion(query); - - return contents; + var content = repository.GetByPublishedVersion(query); + uow.Complete(); + return content; } } @@ -400,15 +453,17 @@ namespace Umbraco.Core.Services /// /// The level to retrieve Content from /// An Enumerable list of objects + /// Contrary to most methods, this method filters out trashed content items. public IEnumerable GetByLevel(int level) { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); - var query = repository.Query.Where(x => x.Level == level && !x.Path.StartsWith(Constants.System.RecycleBinContent.ToInvariantString())); - var contents = repository.GetByQuery(query); - - return contents; + var query = repository.Query.Where(x => x.Level == level && x.Trashed == false); + var content = repository.GetByQuery(query); + uow.Complete(); + return content; } } @@ -421,12 +476,14 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); - return repository.GetByVersion(versionId); + var content = repository.GetByVersion(versionId); + uow.Complete(); + return content; } } - /// /// Gets a collection of an objects versions by Id /// @@ -436,9 +493,11 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); - var versions = repository.GetAllVersions(id); - return versions; + var content = repository.GetAllVersions(id); + uow.Complete(); + return content; } } @@ -449,6 +508,7 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetAncestors(int id) { + // intentionnaly not locking var content = GetById(id); return GetAncestors(content); } @@ -463,14 +523,19 @@ namespace Umbraco.Core.Services //null check otherwise we get exceptions if (content.Path.IsNullOrWhiteSpace()) return Enumerable.Empty(); - var ids = content.Path.Split(',').Where(x => x != Constants.System.Root.ToInvariantString() && x != content.Id.ToString(CultureInfo.InvariantCulture)).Select(int.Parse).ToArray(); + var rootId = Constants.System.Root.ToInvariantString(); + var ids = content.Path.Split(',') + .Where(x => x != rootId && x != content.Id.ToString(CultureInfo.InvariantCulture)).Select(int.Parse).ToArray(); if (ids.Any() == false) return new List(); using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); - return repository.GetAll(ids); + var ancestors = repository.GetAll(ids); + uow.Complete(); + return ancestors; } } @@ -483,11 +548,30 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.ParentId == id); - var contents = repository.GetByQuery(query).OrderBy(x => x.SortOrder); + var children = repository.GetByQuery(query).OrderBy(x => x.SortOrder); + uow.Complete(); + return children; + } + } - return contents; + /// + /// Gets a collection of published objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// An Enumerable list of published objects + public IEnumerable GetPublishedChildren(int id) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.ContentTree); + var repository = uow.CreateRepository(); + var query = repository.Query.Where(x => x.ParentId == id && x.Published); + var children = repository.GetByQuery(query).OrderBy(x => x.SortOrder); + uow.Complete(); + return children; } } @@ -525,19 +609,19 @@ namespace Umbraco.Core.Services { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); + using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); var query = repository.Query; //if the id is System Root, then just get all if (id != Constants.System.Root) - { query.Where(x => x.ParentId == id); - } - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); - - return contents; + var children = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + uow.Complete(); + return children; } } @@ -571,20 +655,20 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, string filter) { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); + Mandate.ParameterCondition(pageIndex >= 0, nameof(pageIndex)); + Mandate.ParameterCondition(pageSize > 0, nameof(pageSize)); + using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); var query = repository.Query; //if the id is System Root, then just get all if (id != Constants.System.Root) - { - query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); - } + query.Where(x => x.Path.SqlContains($",{id},", TextColumnType.NVarchar)); var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); - + uow.Complete(); return contents; } } @@ -599,11 +683,12 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.ParentId == parentId && x.Name.Contains(name)); - var contents = repository.GetByQuery(query); - - return contents; + var children = repository.GetByQuery(query); + uow.Complete(); + return children; } } @@ -614,12 +699,22 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetDescendants(int id) { - var content = GetById(id); - if (content == null) + using (var uow = UowProvider.CreateUnitOfWork()) { - return Enumerable.Empty(); + uow.ReadLock(Constants.Locks.ContentTree); + var repository = uow.CreateRepository(); + var content = GetById(id); + if (content == null) + { + uow.Complete(); // else causes rollback + return Enumerable.Empty(); + } + var pathMatch = content.Path + ","; + var query = repository.Query.Where(x => x.Id != content.Id && x.Path.StartsWith(pathMatch)); + var descendants = repository.GetByQuery(query); + uow.Complete(); + return descendants; } - return GetDescendants(content); } /// @@ -631,12 +726,13 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); var pathMatch = content.Path + ","; - var query = repository.Query.Where(x => x.Path.StartsWith(pathMatch) && x.Id != content.Id); - var contents = repository.GetByQuery(query); - - return contents; + var query = repository.Query.Where(x => x.Id != content.Id && x.Path.StartsWith(pathMatch)); + var descendants = repository.GetByQuery(query); + uow.Complete(); + return descendants; } } @@ -647,6 +743,7 @@ namespace Umbraco.Core.Services /// Parent object public IContent GetParent(int id) { + // intentionnaly not locking var content = GetById(id); return GetParent(content); } @@ -672,7 +769,7 @@ namespace Umbraco.Core.Services public IContent GetPublishedVersion(int id) { var version = GetVersions(id); - return version.FirstOrDefault(x => x.Published == true); + return version.FirstOrDefault(x => x.Published); } /// @@ -696,11 +793,12 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.ParentId == Constants.System.Root); - var contents = repository.GetByQuery(query); - - return contents; + var content = repository.GetByQuery(query); + uow.Complete(); + return content; } } @@ -712,9 +810,12 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.Trashed == false); - return repository.GetByPublishedVersion(query); + var content = repository.GetByPublishedVersion(query); + uow.Complete(); + return content; } } @@ -726,11 +827,12 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); - var query = repository.Query.Where(x => x.Published == true && x.ExpireDate <= DateTime.Now); - var contents = repository.GetByQuery(query); - - return contents; + var query = repository.Query.Where(x => x.Published && x.ExpireDate <= DateTime.Now); + var content = repository.GetByQuery(query); + uow.Complete(); + return content; } } @@ -742,11 +844,12 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.Published == false && x.ReleaseDate <= DateTime.Now); - var contents = repository.GetByQuery(query); - - return contents; + var content = repository.GetByQuery(query); + uow.Complete(); + return content; } } @@ -758,16 +861,15 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.Path.Contains(Constants.System.RecycleBinContent.ToInvariantString())); - var contents = repository.GetByQuery(query); - - return contents; + var content = repository.GetByQuery(query); + uow.Complete(); + return content; } } - - /// /// Checks whether an item has any children /// @@ -778,17 +880,6 @@ namespace Umbraco.Core.Services return CountChildren(id) > 0; } - internal int CountChildren(int id) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var query = repository.Query.Where(x => x.ParentId == id); - var count = repository.Count(query); - return count; - } - } - /// /// Checks whether an item has any published versions /// @@ -798,9 +889,11 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); - var query = repository.Query.Where(x => x.Published == true && x.Id == id && x.Trashed == false); - int count = repository.Count(query); + var query = repository.Query.Where(x => x.Published && x.Id == id && x.Trashed == false); + var count = repository.Count(query); + uow.Complete(); return count > 0; } } @@ -812,17 +905,40 @@ namespace Umbraco.Core.Services /// True if the Content can be published, otherwise False public bool IsPublishable(IContent content) { - //If the passed in content has yet to be saved we "fallback" to checking the Parent - //because if the Parent is publishable then the current content can be Saved and Published - if (content.HasIdentity == false) - { - IContent parent = GetById(content.ParentId); - return IsPublishable(parent, true); - } + // fast + if (content.ParentId == Constants.System.Root) return true; // root content is always publishable + if (content.Trashed) return false; // trashed content is never publishable - return IsPublishable(content, false); + // not trashed and has a parent: publishable if the parent is path-published + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.ContentTree); + var repo = uow.CreateRepository(); + var parent = repo.Get(content.ParentId); + if (parent == null) + throw new Exception("Out of sync."); // causes rollback + var isPublishable = repo.IsPathPublished(parent); + uow.Complete(); + return isPublishable; + } } + public bool IsPathPublished(IContent content) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.ContentTree); + var repo = uow.CreateRepository(); + var isPathPublished = repo.IsPathPublished(content); + uow.Complete(); + return isPathPublished; + } + } + + #endregion + + #region Save, Publish, Unpublish + /// /// This will rebuild the xml structures for content in the database. /// @@ -866,172 +982,113 @@ namespace Umbraco.Core.Services } /// - /// Publishes a single object + /// Saves a single object /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - public bool Publish(IContent content, int userId = 0) + /// The to save + /// Optional Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + public void Save(IContent content, int userId = 0, bool raiseEvents = true) { - var result = SaveAndPublishDo(content, userId); - Logger.Info("Call was made to ContentService.Publish, use PublishWithStatus instead since that method will provide more detailed information on the outcome"); - return result.Success; + ((IContentServiceOperations) this).Save(content, userId, raiseEvents); } /// - /// Publishes a object and all its children + /// Saves a single object /// - /// The to publish along with its children - /// Optional Id of the User issueing the publishing - /// - /// The list of statuses for all published items - IEnumerable> IContentServiceOperations.PublishWithChildren(IContent content, int userId, bool includeUnpublished) - { - return PublishWithChildrenDo(content, userId, includeUnpublished); - } - - /// - /// Saves and Publishes a single object - /// - /// The to save and publish - /// Optional Id of the User issueing the publishing - /// Optional boolean indicating whether or not to raise save events. - /// True if publishing succeeded, otherwise False - Attempt IContentServiceOperations.SaveAndPublish(IContent content, int userId, bool raiseEvents) - { - return SaveAndPublishDo(content, userId, raiseEvents); - } - - /// - /// Deletes an object by moving it to the Recycle Bin - /// - /// Move an item to the Recycle Bin will result in the item being unpublished - /// The to delete - /// Optional Id of the User deleting the Content - Attempt IContentServiceOperations.MoveToRecycleBin(IContent content, int userId) + /// The to save + /// Optional Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + Attempt IContentServiceOperations.Save(IContent content, int userId, bool raiseEvents) { var evtMsgs = EventMessagesFactory.Get(); - using (new WriteLock(Locker)) + if (raiseEvents && Saving.IsRaisedEventCancelled(new SaveEventArgs(content, evtMsgs), this)) + return OperationStatus.Attempt.Cancel(evtMsgs); + + using (var uow = UowProvider.CreateUnitOfWork()) { - var originalPath = content.Path; - - if (Trashing.IsRaisedEventCancelled( - new MoveEventArgs(evtMsgs, new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent)), - this)) - { - return OperationStatus.Cancelled(evtMsgs); - } - - var moveInfo = new List> - { - new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent) - }; - - //Make sure that published content is unpublished before being moved to the Recycle Bin - if (HasPublishedVersion(content.Id)) - { - //TODO: this shouldn't be a 'sub operation', and if it needs to be it cannot raise events and cannot be cancelled! - UnPublish(content, userId); - } - - //Unpublish descendents of the content item that is being moved to trash - var descendants = GetDescendants(content).OrderBy(x => x.Level).ToList(); - foreach (var descendant in descendants) - { - //TODO: this shouldn't be a 'sub operation', and if it needs to be it cannot raise events and cannot be cancelled! - UnPublish(descendant, userId); - } + uow.WriteLock(Constants.Locks.ContentTree); + var repository = uow.CreateRepository(); + if (content.HasIdentity == false) + content.CreatorId = userId; content.WriterId = userId; - content.ChangeTrashedState(true); - using (var uow = UowProvider.CreateUnitOfWork()) + // saving the Published version => indicate we are .Saving + // saving the Unpublished version => remains .Unpublished + if (content.Published) + content.ChangePublishedState(PublishedState.Saving); + + repository.AddOrUpdate(content); + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); + + uow.Complete(); + } + + if (raiseEvents) + Saved.RaiseEvent(new SaveEventArgs(content, false, evtMsgs), this); + + Audit(AuditType.Save, "Save Content performed by user", userId, content.Id); + + return OperationStatus.Attempt.Succeed(evtMsgs); + } + + /// + /// Saves a collection of objects. + /// + /// + /// If the collection of content contains new objects that references eachother by Id or ParentId, + /// then use the overload Save method with a collection of Lazy . + /// + /// Collection of to save + /// Optional Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + public void Save(IEnumerable contents, int userId = 0, bool raiseEvents = true) + { + ((IContentServiceOperations) this).Save(contents, userId, raiseEvents); + } + + /// + /// Saves a collection of objects. + /// + /// Collection of to save + /// Optional Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + Attempt IContentServiceOperations.Save(IEnumerable contents, int userId, bool raiseEvents) + { + var evtMsgs = EventMessagesFactory.Get(); + var contentsA = contents.ToArray(); + + if (raiseEvents && Saving.IsRaisedEventCancelled(new SaveEventArgs(contentsA, evtMsgs), this)) + return OperationStatus.Attempt.Cancel(evtMsgs); + + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(Constants.Locks.ContentTree); + var repository = uow.CreateRepository(); + foreach (var content in contentsA) { - var repository = uow.CreateRepository(); + if (content.HasIdentity == false) + content.CreatorId = userId; + content.WriterId = userId; + + // saving the Published version => indicate we are .Saving + // saving the Unpublished version => remains .Unpublished + if (content.Published) + content.ChangePublishedState(PublishedState.Saving); + repository.AddOrUpdate(content); - - //Loop through descendants to update their trash state, but ensuring structure by keeping the ParentId - foreach (var descendant in descendants) - { - moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); - descendant.WriterId = userId; - descendant.ChangeTrashedState(true, descendant.ParentId); - repository.AddOrUpdate(descendant); - } - - uow.Complete(); + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); } - Trashed.RaiseEvent(new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()), this); - Audit(AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id); - - return OperationStatus.Success(evtMsgs); + uow.Complete(); } - } - /// - /// UnPublishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if unpublishing succeeded, otherwise False - Attempt IContentServiceOperations.UnPublish(IContent content, int userId) - { - return UnPublishDo(content, false, userId); - } + if (raiseEvents) + Saved.RaiseEvent(new SaveEventArgs(contentsA, false, evtMsgs), this); + Audit(AuditType.Save, "Bulk Save content performed by user", userId == -1 ? 0 : userId, Constants.System.Root); - /// - /// Publishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - public Attempt PublishWithStatus(IContent content, int userId = 0) - { - return ((IContentServiceOperations)this).Publish(content, userId); - } - - /// - /// Publishes a object and all its children - /// - /// The to publish along with its children - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - [Obsolete("Use PublishWithChildrenWithStatus instead, that method will provide more detailed information on the outcome and also allows the includeUnpublished flag")] - public bool PublishWithChildren(IContent content, int userId = 0) - { - var result = PublishWithChildrenDo(content, userId, true); - - //This used to just return false only when the parent content failed, otherwise would always return true so we'll - // do the same thing for the moment - if (result.All(x => x.Result.ContentItem.Id != content.Id)) - return false; - - return result.Single(x => x.Result.ContentItem.Id == content.Id).Success; - } - - /// - /// Publishes a object and all its children - /// - /// The to publish along with its children - /// Optional Id of the User issueing the publishing - /// set to true if you want to also publish children that are currently unpublished - /// True if publishing succeeded, otherwise False - public IEnumerable> PublishWithChildrenWithStatus(IContent content, int userId = 0, bool includeUnpublished = false) - { - return ((IContentServiceOperations)this).PublishWithChildren(content, userId, includeUnpublished); - } - - /// - /// UnPublishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if unpublishing succeeded, otherwise False - public bool UnPublish(IContent content, int userId = 0) - { - return ((IContentServiceOperations)this).UnPublish(content, userId).Success; + return OperationStatus.Attempt.Succeed(evtMsgs); } /// @@ -1055,139 +1112,22 @@ namespace Umbraco.Core.Services /// Optional Id of the User issueing the publishing /// Optional boolean indicating whether or not to raise save events. /// True if publishing succeeded, otherwise False - public Attempt SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true) + Attempt IContentServiceOperations.SaveAndPublish(IContent content, int userId, bool raiseEvents) { - return ((IContentServiceOperations)this).SaveAndPublish(content, userId, raiseEvents); + return SaveAndPublishDo(content, userId, raiseEvents); } /// - /// Saves a single object + /// Publishes a single object /// - /// The to save - /// Optional Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - public void Save(IContent content, int userId = 0, bool raiseEvents = true) + /// The to publish + /// Optional Id of the User issueing the publishing + /// True if publishing succeeded, otherwise False + public bool Publish(IContent content, int userId = 0) { - ((IContentServiceOperations)this).Save(content, userId, raiseEvents); - } - - /// - /// Saves a collection of objects. - /// - /// Collection of to save - /// Optional Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - Attempt IContentServiceOperations.Save(IEnumerable contents, int userId, bool raiseEvents) - { - var asArray = contents.ToArray(); - - var evtMsgs = EventMessagesFactory.Get(); - - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled( - new SaveEventArgs(asArray, evtMsgs), - this)) - { - return OperationStatus.Cancelled(evtMsgs); - } - } - using (new WriteLock(Locker)) - { - var containsNew = asArray.Any(x => x.HasIdentity == false); - - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - if (containsNew) - { - foreach (var content in asArray) - { - content.WriterId = userId; - - //Only change the publish state if the "previous" version was actually published - if (content.Published) - content.ChangePublishedState(PublishedState.Saved); - - repository.AddOrUpdate(content); - //add or update preview - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); - } - } - else - { - foreach (var content in asArray) - { - content.WriterId = userId; - repository.AddOrUpdate(content); - //add or update preview - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); - } - } - - uow.Complete(); - } - - if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(asArray, false, evtMsgs), this); - Audit(AuditType.Save, "Bulk Save content performed by user", userId == -1 ? 0 : userId, Constants.System.Root); - - return OperationStatus.Success(evtMsgs); - } - } - - /// - /// Permanently deletes an object. - /// - /// - /// This method will also delete associated media files, child content and possibly associated domains. - /// - /// Please note that this method will completely remove the Content from the database - /// The to delete - /// Optional Id of the User deleting the Content - Attempt IContentServiceOperations.Delete(IContent content, int userId) - { - var evtMsgs = EventMessagesFactory.Get(); - - using (new WriteLock(Locker)) - { - if (Deleting.IsRaisedEventCancelled( - new DeleteEventArgs(content, evtMsgs), - this)) - { - return OperationStatus.Cancelled(evtMsgs); - } - - //Make sure that published content is unpublished before being deleted - if (HasPublishedVersion(content.Id)) - { - UnPublish(content, userId); - } - - //Delete children before deleting the 'possible parent' - var children = GetChildren(content.Id); - foreach (var child in children) - { - Delete(child, userId); - } - - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - repository.Delete(content); - uow.Complete(); - - var args = new DeleteEventArgs(content, false, evtMsgs); - Deleted.RaiseEvent(args, this); - - //remove any flagged media files - repository.DeleteMediaFiles(args.MediaFilesToDelete); - } - - Audit(AuditType.Delete, "Delete Content performed by user", userId, content.Id); - - return OperationStatus.Success(evtMsgs); - } + var result = SaveAndPublishDo(content, userId); + Logger.Info("Call was made to ContentService.Publish, use PublishWithStatus instead since that method will provide more detailed information on the outcome"); + return result.Success; } /// @@ -1202,82 +1142,100 @@ namespace Umbraco.Core.Services } /// - /// Saves a single object + /// UnPublishes a single object /// - /// The to save - /// Optional Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - Attempt IContentServiceOperations.Save(IContent content, int userId, bool raiseEvents) + /// The to publish + /// Optional Id of the User issueing the publishing + /// True if unpublishing succeeded, otherwise False + public bool UnPublish(IContent content, int userId = 0) { - return Save(content, true, userId, raiseEvents); + return ((IContentServiceOperations)this).UnPublish(content, userId).Success; } /// - /// Saves a collection of objects. + /// UnPublishes a single object /// - /// - /// If the collection of content contains new objects that references eachother by Id or ParentId, - /// then use the overload Save method with a collection of Lazy . - /// - /// Collection of to save - /// Optional Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - public void Save(IEnumerable contents, int userId = 0, bool raiseEvents = true) + /// The to publish + /// Optional Id of the User issueing the publishing + /// True if unpublishing succeeded, otherwise False + Attempt IContentServiceOperations.UnPublish(IContent content, int userId) { - ((IContentServiceOperations)this).Save(contents, userId, raiseEvents); + return UnPublishDo(content, false, userId); } /// - /// Deletes all content of specified type. All children of deleted content is moved to Recycle Bin. + /// Saves and Publishes a single object /// - /// This needs extra care and attention as its potentially a dangerous and extensive operation - /// Id of the - /// Optional Id of the user issueing the delete operation - public void DeleteContentOfType(int contentTypeId, int userId = 0) + /// The to save and publish + /// Optional Id of the User issueing the publishing + /// Optional boolean indicating whether or not to raise save events. + /// True if publishing succeeded, otherwise False + public Attempt SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true) { - //TODO: This currently this is called from the ContentTypeService but that needs to change, - // if we are deleting a content type, we should just delete the data and do this operation slightly differently. - // This method will recursively go lookup every content item, check if any of it's descendants are - // of a different type, move them to the recycle bin, then permanently delete the content items. - // The main problem with this is that for every content item being deleted, events are raised... - // which we need for many things like keeping caches in sync, but we can surely do this MUCH better. - - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - //NOTE What about content that has the contenttype as part of its composition? - var query = repository.Query.Where(x => x.ContentTypeId == contentTypeId); - var contents = repository.GetByQuery(query).ToArray(); - - if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(contents), this)) - return; - - foreach (var content in contents.OrderByDescending(x => x.ParentId)) - { - //Look for children of current content and move that to trash before the current content is deleted - var c = content; - var childQuery = repository.Query.Where(x => x.Path.StartsWith(c.Path)); - var children = repository.GetByQuery(childQuery); - - foreach (var child in children) - { - if (child.ContentType.Id != contentTypeId) - MoveToRecycleBin(child, userId); - } - - //Permantly delete the content - Delete(content, userId); - } - } - - Audit(AuditType.Delete, - string.Format("Delete Content of Type {0} performed by user", contentTypeId), - userId, Constants.System.Root); - } + return ((IContentServiceOperations)this).SaveAndPublish(content, userId, raiseEvents); } + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// True if publishing succeeded, otherwise False + public Attempt PublishWithStatus(IContent content, int userId = 0) + { + return ((IContentServiceOperations) this).Publish(content, userId); + } + + /// + /// Publishes a object and all its children + /// + /// The to publish along with its children + /// Optional Id of the User issueing the publishing + /// True if publishing succeeded, otherwise False + [Obsolete("Use PublishWithChildrenWithStatus instead, that method will provide more detailed information on the outcome and also allows the includeUnpublished flag")] + public bool PublishWithChildren(IContent content, int userId = 0) + { + // this used to just return false only when the parent content failed, otherwise would + // always return true so we'll do the same thing for the moment + + var result = PublishWithChildrenDo(content, userId, true); + + // FirstOrDefault() is a pain to use with structs and result contain Attempt structs + // so use this code, which is fast and works - and please ReSharper do NOT suggest otherwise + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var r in result) + if (r.Result.ContentItem.Id == content.Id) return r.Success; + return false; + } + + /// + /// Publishes a object and all its children + /// + /// The to publish along with its children + /// Optional Id of the User issueing the publishing + /// set to true if you want to also publish children that are currently unpublished + /// True if publishing succeeded, otherwise False + public IEnumerable> PublishWithChildrenWithStatus(IContent content, int userId = 0, bool includeUnpublished = false) + { + return ((IContentServiceOperations)this).PublishWithChildren(content, userId, includeUnpublished); + } + + /// + /// Publishes a object and all its children + /// + /// The to publish along with its children + /// Optional Id of the User issueing the publishing + /// + /// The list of statuses for all published items + IEnumerable> IContentServiceOperations.PublishWithChildren(IContent content, int userId, bool includeUnpublished) + { + return PublishWithChildrenDo(content, userId, includeUnpublished); + } + + #endregion + + #region Delete + /// /// Permanently deletes an object as well as all of its Children. /// @@ -1292,6 +1250,78 @@ namespace Umbraco.Core.Services ((IContentServiceOperations)this).Delete(content, userId); } + /// + /// Permanently deletes an object. + /// + /// + /// This method will also delete associated media files, child content and possibly associated domains. + /// + /// Please note that this method will completely remove the Content from the database + /// The to delete + /// Optional Id of the User deleting the Content + Attempt IContentServiceOperations.Delete(IContent content, int userId) + { + var evtMsgs = EventMessagesFactory.Get(); + + if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(content, evtMsgs), this)) + return OperationStatus.Attempt.Cancel(evtMsgs); + + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(Constants.Locks.ContentTree); + var repository = uow.CreateRepository(); + + // if it's not trashed yet, and published, we should unpublish + // but... UnPublishing event makes no sense (not going to cancel?) and no need to save + // just raise the event + if (content.Trashed == false && content.HasPublishedVersion) + UnPublished.RaiseEvent(new PublishEventArgs(content, false, false), this); + + DeleteLocked(repository, content); + uow.Complete(); + } + + Audit(AuditType.Delete, "Delete Content performed by user", userId, content.Id); + + return OperationStatus.Attempt.Succeed(evtMsgs); + } + + private void DeleteLocked(IContentRepository repository, IContent content) + { + // then recursively delete descendants, bottom-up + // just repository.Delete + an event + var stack = new Stack(); + stack.Push(content); + var level = 1; + while (stack.Count > 0) + { + var c = stack.Peek(); + IContent[] cc; + if (c.Level == level) + while ((cc = c.Children().ToArray()).Length > 0) + { + foreach (var ci in cc) + stack.Push(ci); + c = cc[cc.Length - 1]; + } + c = stack.Pop(); + level = c.Level; + + repository.Delete(c); + var args = new DeleteEventArgs(c, false); // raise event & get flagged files + Deleted.RaiseEvent(args, this); + + IOHelper.DeleteFiles(args.MediaFilesToDelete, // remove flagged files + (file, e) => Logger.Error("An error occurred while deleting file attached to nodes: " + file, e)); + } + } + + //TODO: + // both DeleteVersions methods below have an issue. Sort of. They do NOT take care of files the way + // Delete does - for a good reason: the file may be referenced by other, non-deleted, versions. BUT, + // if that's not the case, then the file will never be deleted, because when we delete the content, + // the version referencing the file will not be there anymore. SO, we can leak files. + /// /// Permanently deletes versions from an object prior to a specific date. /// This method will never delete the latest version of a content item. @@ -1306,6 +1336,7 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { + uow.WriteLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); repository.DeleteVersions(id, versionDate); uow.Complete(); @@ -1326,30 +1357,32 @@ namespace Umbraco.Core.Services /// Optional Id of the User deleting versions of a Content object public void DeleteVersion(int id, Guid versionId, bool deletePriorVersions, int userId = 0) { - using (new WriteLock(Locker)) + if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, /*specificVersion:*/ versionId), this)) + return; + + if (deletePriorVersions) { - if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, specificVersion: versionId), this)) - return; - - if (deletePriorVersions) - { - var content = GetByVersion(versionId); - DeleteVersions(id, content.UpdateDate, userId); - } - - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - repository.DeleteVersion(versionId); - uow.Complete(); - } - - DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, specificVersion: versionId), this); - - Audit(AuditType.Delete, "Delete Content by version performed by user", userId, Constants.System.Root); + var content = GetByVersion(versionId); + DeleteVersions(id, content.UpdateDate, userId); } + + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(Constants.Locks.ContentTree); + var repository = uow.CreateRepository(); + repository.DeleteVersion(versionId); + uow.Complete(); + } + + DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false,/* specificVersion:*/ versionId), this); + + Audit(AuditType.Delete, "Delete Content by version performed by user", userId, Constants.System.Root); } + #endregion + + #region Move, RecycleBin + /// /// Deletes an object by moving it to the Recycle Bin /// @@ -1361,6 +1394,46 @@ namespace Umbraco.Core.Services ((IContentServiceOperations)this).MoveToRecycleBin(content, userId); } + /// + /// Deletes an object by moving it to the Recycle Bin + /// + /// Move an item to the Recycle Bin will result in the item being unpublished + /// The to delete + /// Optional Id of the User deleting the Content + Attempt IContentServiceOperations.MoveToRecycleBin(IContent content, int userId) + { + var evtMsgs = EventMessagesFactory.Get(); + var moves = new List>(); + + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(Constants.Locks.ContentTree); + var repository = uow.CreateRepository(); + + var originalPath = content.Path; + if (Trashing.IsRaisedEventCancelled(new MoveEventArgs(new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent)), this)) + return OperationStatus.Attempt.Cancel(evtMsgs); // causes rollback + + // if it's published we may want to force-unpublish it - that would be backward-compatible... but... + // making a radical decision here: trashing is equivalent to moving under an unpublished node so + // it's NOT unpublishing, only the content is now masked - allowing us to restore it if wanted + //if (content.HasPublishedVersion) + //{ } + + PerformMoveLocked(repository, content, Constants.System.RecycleBinContent, null, userId, moves, true); + uow.Complete(); + } + + var moveInfo = moves + .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) + .ToArray(); + + Trashed.RaiseEvent(new MoveEventArgs(false, evtMsgs, moveInfo), this); + Audit(AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id); + + return OperationStatus.Attempt.Succeed(evtMsgs); + } + /// /// Moves an object to a new location by changing its parent id. /// @@ -1374,32 +1447,104 @@ namespace Umbraco.Core.Services /// Optional Id of the User moving the Content public void Move(IContent content, int parentId, int userId = 0) { - using (new WriteLock(Locker)) + // if moving to the recycle bin then use the proper method + if (parentId == Constants.System.RecycleBinContent) { - //This ensures that the correct method is called if this method is used to Move to recycle bin. - if (parentId == Constants.System.RecycleBinContent) - { - MoveToRecycleBin(content, userId); - return; - } - - if (Moving.IsRaisedEventCancelled( - new MoveEventArgs( - new MoveEventInfo(content, content.Path, parentId)), this)) - { - return; - } - - //used to track all the moved entities to be given to the event - var moveInfo = new List>(); - - //call private method that does the recursive moving - PerformMove(content, parentId, userId, moveInfo); - - Moved.RaiseEvent(new MoveEventArgs(false, moveInfo.ToArray()), this); - - Audit(AuditType.Move, "Move Content performed by user", userId, content.Id); + MoveToRecycleBin(content, userId); + return; } + + var moves = new List>(); + + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(Constants.Locks.ContentTree); + var repository = uow.CreateRepository(); + + var parent = parentId == Constants.System.Root ? null : GetById(parentId); + if (parentId != Constants.System.Root && (parent == null || parent.Trashed)) + throw new InvalidOperationException("Parent does not exist or is trashed."); // causes rollback + + if (Moving.IsRaisedEventCancelled(new MoveEventArgs(new MoveEventInfo(content, content.Path, parentId)), this)) + return; // causes rollback + + // if content was trashed, and since we're not moving to the recycle bin, + // indicate that the trashed status should be changed to false, else just + // leave it unchanged + var trashed = content.Trashed ? false : (bool?)null; + + // if the content was trashed under another content, and so has a published version, + // it cannot move back as published but has to be unpublished first - that's for the + // root content, everything underneath will retain its published status + if (content.Trashed && content.HasPublishedVersion) + { + // however, it had been masked when being trashed, so there's no need for + // any special event here - just change its state + content.ChangePublishedState(PublishedState.Unpublishing); + } + + PerformMoveLocked(repository, content, parentId, parent, userId, moves, trashed); + + uow.Complete(); + } + + var moveInfo = moves //changes + .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) + .ToArray(); + + Moved.RaiseEvent(new MoveEventArgs(false, moveInfo), this); + + Audit(AuditType.Move, "Move Content performed by user", userId, content.Id); + } + + // MUST be called from within WriteLock + // trash indicates whether we are trashing, un-trashing, or not changing anything + private void PerformMoveLocked(IContentRepository repository, + IContent content, int parentId, IContent parent, int userId, + ICollection> moves, + bool? trash) + { + content.WriterId = userId; + content.ParentId = parentId; + + // get the level delta (old pos to new pos) + var levelDelta = parent == null + ? 1 - content.Level + (parentId == Constants.System.RecycleBinContent ? 1 : 0) + : parent.Level + 1 - content.Level; + + var paths = new Dictionary(); + + moves.Add(Tuple.Create(content, content.Path)); // capture original path + + // these will be updated by the repo because we changed parentId + //content.Path = (parent == null ? "-1" : parent.Path) + "," + content.Id; + //content.SortOrder = ((ContentRepository) repository).NextChildSortOrder(parentId); + //content.Level += levelDelta; + PerformMoveContentLocked(repository, content, userId, trash); + + // BUT content.Path will be updated only when the UOW commits, and + // because we want it now, we have to calculate it by ourselves + //paths[content.Id] = content.Path; + paths[content.Id] = (parent == null ? (parentId == Constants.System.RecycleBinContent ? "-1,-20" : "-1") : parent.Path) + "," + content.Id; + + var descendants = GetDescendants(content); + foreach (var descendant in descendants) + { + moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path + + // update path and level since we do not update parentId + descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id; + descendant.Level += levelDelta; + PerformMoveContentLocked(repository, descendant, userId, trash); + } + } + + private static void PerformMoveContentLocked(IContentRepository repository, IContent content, int userId, + bool? trash) + { + if (trash.HasValue) ((ContentBase) content).Trashed = trash.Value; + content.WriterId = userId; + repository.AddOrUpdate(content); } /// @@ -1407,38 +1552,43 @@ namespace Umbraco.Core.Services /// public void EmptyRecycleBin() { - using (new WriteLock(Locker)) + var nodeObjectType = new Guid(Constants.ObjectTypes.Document); + var deleted = new List(); + var evtMsgs = EventMessagesFactory.Get(); // todo - and then? + + using (var uow = UowProvider.CreateUnitOfWork()) { - Dictionary> entities; - List files; - bool success; - var nodeObjectType = new Guid(Constants.ObjectTypes.Document); + uow.WriteLock(Constants.Locks.ContentTree); + var repository = uow.CreateRepository(); - using (var uow = UowProvider.CreateUnitOfWork()) + // v7 EmptyingRecycleBin and EmptiedRecycleBin events are greatly simplified since + // each deleted items will have its own deleting/deleted events. so, files and such + // are managed by Delete, and not here. + + // no idea what those events are for, keep a simplified version + if (EmptyingRecycleBin.IsRaisedEventCancelled(new RecycleBinEventArgs(nodeObjectType), this)) + return; // causes rollback + + // emptying the recycle bin means deleting whetever is in there - do it properly! + var query = repository.Query.Where(x => x.ParentId == Constants.System.RecycleBinContent); + var contents = repository.GetByQuery(query).ToArray(); + foreach (var content in contents) { - var repository = uow.CreateRepository(); - //Create a dictionary of ids -> dictionary of property aliases + values - entities = repository.GetEntitiesInRecycleBin() - .ToDictionary( - key => key.Id, - val => (IEnumerable)val.Properties); - - files = ((ContentRepository)repository).GetFilesInRecycleBinForUploadField(); - - if (EmptyingRecycleBin.IsRaisedEventCancelled(new RecycleBinEventArgs(nodeObjectType, entities, files), this)) - return; - - success = repository.EmptyRecycleBin(); - - EmptiedRecycleBin.RaiseEvent(new RecycleBinEventArgs(nodeObjectType, entities, files, success), this); - - if (success) - repository.DeleteMediaFiles(files); + DeleteLocked(repository, content); + deleted.Add(content); } + + EmptiedRecycleBin.RaiseEvent(new RecycleBinEventArgs(nodeObjectType, true), this); + uow.Complete(); } + Audit(AuditType.Delete, "Empty Content Recycle Bin performed by user", 0, Constants.System.RecycleBinContent); } + #endregion + + #region Others + /// /// Copies an object by creating a new Content object of the same type and copies all data from the current /// to the new copy which is returned. Recursively copies all children. @@ -1465,68 +1615,69 @@ namespace Umbraco.Core.Services /// The newly created object public IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = 0) { - //TODO: This all needs to be managed correctly so that the logic is submitted in one - // transaction, the CRUD needs to be moved to the repo + var copy = content.DeepCloneWithResetIdentities(); + copy.ParentId = parentId; - using (new WriteLock(Locker)) + if (Copying.IsRaisedEventCancelled(new CopyEventArgs(content, copy, parentId), this)) + return null; + + // fixme - relateToOriginal is ignored?! + + using (var uow = UowProvider.CreateUnitOfWork()) { - var copy = content.DeepCloneWithResetIdentities(); - copy.ParentId = parentId; + uow.WriteLock(Constants.Locks.ContentTree); + var repository = uow.CreateRepository(); - // A copy should never be set to published automatically even if the original was. - copy.ChangePublishedState(PublishedState.Unpublished); + // a copy is .Saving and will be .Unpublished + if (copy.Published) + copy.ChangePublishedState(PublishedState.Saving); - if (Copying.IsRaisedEventCancelled(new CopyEventArgs(content, copy, parentId), this)) - return null; + // update the create author and last edit author + copy.CreatorId = userId; + copy.WriterId = userId; - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - // Update the create author and last edit author - copy.CreatorId = userId; - copy.WriterId = userId; + // save + repository.AddOrUpdate(copy); + repository.AddOrUpdatePreviewXml(copy, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); - repository.AddOrUpdate(copy); - //add or update a preview - repository.AddOrUpdatePreviewXml(copy, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); - uow.Flush(); // ensure copy has an ID - - - //Special case for the associated tags - //TODO: Move this to the repository layer in a single transaction! - //don't copy tags data in tags table if the item is in the recycle bin - if (parentId != Constants.System.RecycleBinContent) - { - - var tags = uow.Database.Fetch("WHERE nodeId = @Id", new { Id = content.Id }); - foreach (var tag in tags) - { - uow.Database.Insert(new TagRelationshipDto { NodeId = copy.Id, TagId = tag.TagId, PropertyTypeId = tag.PropertyTypeId }); - } - } - - uow.Complete(); - } + uow.Flush(); // ensure copy has an ID - fixme why? if (recursive) { - //Look for children and copy those as well - var children = GetChildren(content.Id); - foreach (var child in children) + // process descendants + var copyIds = new Dictionary(); + copyIds[content.Id] = copy; + foreach (var descendant in GetDescendants(content)) { - //TODO: This shouldn't recurse back to this method, it should be done in a private method - // that doesn't have a nested lock and so we can perform the entire operation in one commit. - Copy(child, copy.Id, relateToOriginal, true, userId); + var dcopy = descendant.DeepCloneWithResetIdentities(); + //dcopy.ParentId = copyIds[descendant.ParentId]; + var descendantParentId = descendant.ParentId; + ((Content) dcopy).SetLazyParentId(new Lazy(() => copyIds[descendantParentId].Id)); + + if (dcopy.Published) + dcopy.ChangePublishedState(PublishedState.Saving); + dcopy.CreatorId = userId; + dcopy.WriterId = userId; + + repository.AddOrUpdate(dcopy); + repository.AddOrUpdatePreviewXml(dcopy, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); + + copyIds[descendant.Id] = dcopy; } } - Copied.RaiseEvent(new CopyEventArgs(content, copy, false, parentId, relateToOriginal), this); + // fixme tag & tree issue + // tags code handling has been removed here + // - tags should be handled by the content repository + // - a copy is unpublished and therefore has no impact on tags in DB - Audit(AuditType.Copy, "Copy Content performed by user", content.WriterId, content.Id); - return copy; + uow.Complete(); } - } + Copied.RaiseEvent(new CopyEventArgs(content, copy, false, parentId, relateToOriginal), this); + Audit(AuditType.Copy, "Copy Content performed by user", content.WriterId, content.Id); + return copy; + } /// /// Sends an to Publication, which executes handlers and events for the 'Send to Publication' action. @@ -1568,13 +1719,22 @@ namespace Umbraco.Core.Services if (RollingBack.IsRaisedEventCancelled(new RollbackEventArgs(content), this)) return content; - content.WriterId = userId; content.CreatorId = userId; + // need to make sure that the repository is going to save a new version + // but if we're not changing anything, the repository would not save anything + // so - make sure the property IS dirty, doing a flip-flop with an impossible value + content.WriterId = -1; + content.WriterId = userId; + using (var uow = UowProvider.CreateUnitOfWork()) { + uow.WriteLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); - content.ChangePublishedState(PublishedState.Unpublished); + + // a rolled back version is .Saving and will be .Unpublished + content.ChangePublishedState(PublishedState.Saving); + repository.AddOrUpdate(content); repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); uow.Complete(); @@ -1600,126 +1760,66 @@ namespace Umbraco.Core.Services /// True if sorting succeeded, otherwise False public bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true) { - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(items), this)) + var itemsA = items.ToArray(); + if (itemsA.Length == 0) return true; + + if (raiseEvents && Saving.IsRaisedEventCancelled(new SaveEventArgs(itemsA), this)) return false; - } - var shouldBePublished = new List(); - var shouldBeSaved = new List(); + var published = new List(); + var saved = new List(); - var asArray = items.ToArray(); - using (new WriteLock(Locker)) + using (var uow = UowProvider.CreateUnitOfWork()) { - using (var uow = UowProvider.CreateUnitOfWork()) + uow.WriteLock(Constants.Locks.ContentTree); + var repository = uow.CreateRepository(); + var sortOrder = 0; + + foreach (var content in itemsA) { - var repository = uow.CreateRepository(); - int i = 0; - foreach (var content in asArray) + // if the current sort order equals that of the content we don't + // need to update it, so just increment the sort order and continue. + if (content.SortOrder == sortOrder) { - //If the current sort order equals that of the content - //we don't need to update it, so just increment the sort order - //and continue. - if (content.SortOrder == i) - { - i++; - continue; - } - - content.SortOrder = i; - content.WriterId = userId; - i++; - - if (content.Published) - { - //TODO: This should not be an inner operation, but if we do this, it cannot raise events and cannot be cancellable! - var published = _publishingStrategy.Publish(content, userId); - shouldBePublished.Add(content); - } - else - shouldBeSaved.Add(content); - - repository.AddOrUpdate(content); - //add or update a preview - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); + sortOrder++; + continue; } - foreach (var content in shouldBePublished) - { - //Create and Save ContentXml DTO - repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); - } + // else update + content.SortOrder = sortOrder++; + content.WriterId = userId; - uow.Complete(); + // if it's published, register it, no point running StrategyPublish + // since we're not really publishing it and it cannot be cancelled etc + if (content.Published) + published.Add(content); + else if (content.HasPublishedVersion) + published.Add(GetByVersion(content.PublishedVersionGuid)); + + // save + saved.Add(content); + repository.AddOrUpdate(content); + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); } + + foreach (var content in published) + repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); + + uow.Complete(); } if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(asArray, false), this); - - if (shouldBePublished.Any()) - { - //TODO: This should not be an inner operation, but if we do this, it cannot raise events and cannot be cancellable! - _publishingStrategy.PublishingFinalized(shouldBePublished, false); - } + Saved.RaiseEvent(new SaveEventArgs(saved, false), this); + if (raiseEvents && published.Any()) + Published.RaiseEvent(new PublishEventArgs(published, false, false), this); Audit(AuditType.Sort, "Sorting content performed by user", userId, 0); return true; } - /// - /// Returns the persisted content's XML structure - /// - /// - /// - public XElement GetContentXml(int contentId) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - return repository.GetContentXml(contentId); - } - } - - /// - /// Returns the persisted content's preview XML structure - /// - /// - /// - /// - public XElement GetContentPreviewXml(int contentId, Guid version) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - return repository.GetContentPreviewXml(contentId, version); - } - } - - /// - /// Rebuilds all xml content in the cmsContentXml table for all documents - /// - /// - /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures - /// for all content - /// - public void RebuildXmlStructures(params int[] contentTypeIds) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - repository.RebuildXmlStructures( - content => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, content), - contentTypeIds: contentTypeIds.Length == 0 ? null : contentTypeIds); - uow.Complete(); - } - - Audit(AuditType.Publish, "ContentService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, Constants.System.Root); - - } + #endregion #region Internal Methods @@ -1732,11 +1832,32 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + uow.ReadLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); - var query = repository.Query.Where(x => x.Id != content.Id && x.Path.StartsWith(content.Path) && x.Trashed == false); - var contents = repository.GetByPublishedVersion(query); + var descendants = GetPublishedDescendantsLocked(repository, content); + uow.Complete(); + return descendants; + } + } - return contents; + internal IEnumerable GetPublishedDescendantsLocked(IContentRepository repository, IContent content) + { + var pathMatch = content.Path + ","; + var query = repository.Query.Where(x => x.Id != content.Id && x.Path.StartsWith(pathMatch) /*&& x.Trashed == false*/); + var contents = repository.GetByPublishedVersion(query); + + // beware! contents contains all published version below content + // including those that are not directly published because below an unpublished content + // these must be filtered out here + + var parents = new List { content.Id }; + foreach (var c in contents) + { + if (parents.Contains(c.ParentId)) + { + yield return c; + parents.Add(c.Id); + } } } @@ -1754,83 +1875,6 @@ namespace Umbraco.Core.Services } } - //TODO: All of this needs to be moved to the repository - private void PerformMove(IContent content, int parentId, int userId, ICollection> moveInfo) - { - //add a tracking item to use in the Moved event - moveInfo.Add(new MoveEventInfo(content, content.Path, parentId)); - - content.WriterId = userId; - if (parentId == Constants.System.Root) - { - content.Path = string.Concat(Constants.System.Root, ",", content.Id); - content.Level = 1; - } - else - { - var parent = GetById(parentId); - content.Path = string.Concat(parent.Path, ",", content.Id); - content.Level = parent.Level + 1; - } - - //If Content is being moved away from Recycle Bin, its state should be un-trashed - if (content.Trashed && parentId != Constants.System.RecycleBinContent) - { - content.ChangeTrashedState(false, parentId); - } - else - { - content.ParentId = parentId; - } - - //If Content is published, it should be (re)published from its new location - if (content.Published) - { - //If Content is Publishable its saved and published - //otherwise we save the content without changing the publish state, and generate new xml because the Path, Level and Parent has changed. - if (IsPublishable(content)) - { - //TODO: This is raising events, probably not desirable as this costs performance for event listeners like Examine - SaveAndPublish(content, userId); - } - else - { - //TODO: This is raising events, probably not desirable as this costs performance for event listeners like Examine - Save(content, false, userId); - - //TODO: This shouldn't be here! This needs to be part of the repository logic but in order to fix this we need to - // change how this method calls "Save" as it needs to save using an internal method - using (var uow = UowProvider.CreateUnitOfWork()) - { - var xml = _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, content); - - var poco = new ContentXmlDto { NodeId = content.Id, Xml = xml.ToDataString() }; - var exists = - uow.Database.FirstOrDefault("WHERE nodeId = @Id", new { Id = content.Id }) != - null; - int result = exists - ? uow.Database.Update(poco) - : Convert.ToInt32(uow.Database.Insert(poco)); - } - } - } - else - { - //TODO: This is raising events, probably not desirable as this costs performance for event listeners like Examine - Save(content, userId); - } - - //Ensure that Path and Level is updated on children - var children = GetChildren(content.Id).ToArray(); - if (children.Any()) - { - foreach (var child in children) - { - PerformMove(child, content.Id, userId, moveInfo); - } - } - } - /// /// Publishes a object and all its children /// @@ -1842,90 +1886,57 @@ namespace Umbraco.Core.Services /// then the list will only contain one status item, otherwise it will contain status items for it and all of it's descendants that /// are to be published. /// - private IEnumerable> PublishWithChildrenDo( - IContent content, int userId = 0, bool includeUnpublished = false) + private IEnumerable> PublishWithChildrenDo(IContent content, int userId = 0, bool includeUnpublished = false) { - if (content == null) throw new ArgumentNullException("content"); + if (content == null) throw new ArgumentNullException(nameof(content)); var evtMsgs = EventMessagesFactory.Get(); + var publishedItems = new List(); // this is for events + Attempt[] attempts; - using (new WriteLock(Locker)) + using (var uow = UowProvider.CreateUnitOfWork()) { - var result = new List>(); + uow.WriteLock(Constants.Locks.ContentTree); + var repository = uow.CreateRepository(); - //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published - if (content.ParentId != Constants.System.Root && content.ParentId != Constants.System.RecycleBinContent && IsPublishable(content) == false) + // fail fast + use in alreadyChecked below to avoid duplicate checks + var attempt = EnsurePublishable(content, evtMsgs); + if (attempt.Success) + attempt = StrategyCanPublish(content, userId, evtMsgs); + if (attempt.Success == false) + return new[] { attempt }; // causes rollback + + var contents = new List { content }; //include parent item + contents.AddRange(GetDescendants(content)); + + // publish using the strategy - for descendants, + // - published w/out changes: nothing to do + // - published w/changes: publish those changes + // - unpublished: publish if includeUnpublished, otherwise ignore + var alreadyChecked = new[] { content }; + attempts = StrategyPublishWithChildren(contents, alreadyChecked, userId, evtMsgs, includeUnpublished).ToArray(); + + foreach (var status in attempts.Where(x => x.Success).Select(x => x.Result)) { - Logger.Info( - string.Format( - "Content '{0}' with Id '{1}' could not be published because its parent or one of its ancestors is not published.", - content.Name, content.Id)); - result.Add(Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedPathNotPublished, evtMsgs))); - return result; + // save them all, even those that are .Success because of (.StatusType == PublishStatusType.SuccessAlreadyPublished) + // so we bump the date etc + var publishedItem = status.ContentItem; + publishedItem.WriterId = userId; + repository.AddOrUpdate(publishedItem); + repository.AddOrUpdatePreviewXml(publishedItem, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); + repository.AddOrUpdateContentXml(publishedItem, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); + publishedItems.Add(publishedItem); } - //Content contains invalid property values and can therefore not be published - fire event? - if (!content.IsValid()) - { - Logger.Info( - string.Format("Content '{0}' with Id '{1}' could not be published because of invalid properties.", - content.Name, content.Id)); - result.Add( - Attempt.Fail( - new PublishStatus(content, PublishStatusType.FailedContentInvalid, evtMsgs) - { - InvalidProperties = ((ContentBase)content).LastInvalidProperties - })); - return result; - } - - //Consider creating a Path query instead of recursive method: - //var query = repository.Query.Where(x => x.Path.StartsWith(content.Path)); - - var updated = new List(); - var list = new List(); - list.Add(content); //include parent item - list.AddRange(GetDescendants(content)); - - var internalStrategy = (PublishingStrategy)_publishingStrategy; - - //Publish and then update the database with new status - var publishedOutcome = internalStrategy.PublishWithChildrenInternal(list, userId, includeUnpublished).ToArray(); - var published = publishedOutcome - .Where(x => x.Success || x.Result.StatusType == PublishStatusType.SuccessAlreadyPublished) - // ensure proper order (for events) - cannot publish a child before its parent! - .OrderBy(x => x.Result.ContentItem.Level) - .ThenBy(x => x.Result.ContentItem.SortOrder); - - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - //NOTE The Publish with subpages-dialog was used more as a republish-type-thing, so we'll have to include PublishStatusType.SuccessAlreadyPublished - //in the updated-list, so the Published event is triggered with the expected set of pages and the xml is updated. - foreach (var item in published) - { - item.Result.ContentItem.WriterId = userId; - repository.AddOrUpdate(item.Result.ContentItem); - //add or update a preview - repository.AddOrUpdatePreviewXml(item.Result.ContentItem, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); - //add or update the published xml - repository.AddOrUpdateContentXml(item.Result.ContentItem, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); - updated.Add(item.Result.ContentItem); - } - - uow.Complete(); - - } - //Save xml to db and call following method to fire event: - _publishingStrategy.PublishingFinalized(updated, false); - - Audit(AuditType.Publish, "Publish with Children performed by user", userId, content.Id); - - - return publishedOutcome; + uow.Complete(); } + + Published.RaiseEvent(new PublishEventArgs(publishedItems, false, false), this); + Audit(AuditType.Publish, "Publish with Children performed by user", userId, content.Id); + return attempts; } + /// /// UnPublishes a single object /// @@ -1935,40 +1946,40 @@ namespace Umbraco.Core.Services /// True if unpublishing succeeded, otherwise False private Attempt UnPublishDo(IContent content, bool omitCacheRefresh = false, int userId = 0) { - var newest = GetById(content.Id); // ensure we have the newest version - if (content.Version != newest.Version) // but use the original object if it's already the newest version - content = newest; + // fixme kill omitCacheRefresh! var evtMsgs = EventMessagesFactory.Get(); - var published = content.Published ? content : GetPublishedVersion(content.Id); // get the published version - if (published == null) - { - return Attempt.Succeed(new UnPublishStatus(content, UnPublishedStatusType.SuccessAlreadyUnPublished, evtMsgs)); // already unpublished - } - - var unpublished = _publishingStrategy.UnPublish(content, userId); - if (unpublished == false) return Attempt.Fail(new UnPublishStatus(content, UnPublishedStatusType.FailedCancelledByEvent, evtMsgs)); - - content.WriterId = userId; - using (var uow = UowProvider.CreateUnitOfWork()) { + uow.WriteLock(Constants.Locks.ContentTree); var repository = uow.CreateRepository(); + + var newest = GetById(content.Id); // ensure we have the newest version + if (content.Version != newest.Version) // but use the original object if it's already the newest version + content = newest; + if (content.Published == false && content.HasPublishedVersion == false) + { + uow.Complete(); + return Attempt.Succeed(new UnPublishStatus(UnPublishedStatusType.SuccessAlreadyUnPublished, evtMsgs, content)); // already unpublished + } + + // strategy + var attempt = StrategyCanUnPublish(content, userId, evtMsgs); + if (attempt == false) return attempt; // causes rollback + attempt = StrategyUnPublish(content, true, userId, evtMsgs); + if (attempt == false) return attempt; // causes rollback + + content.WriterId = userId; repository.AddOrUpdate(content); - // is published is not newest, reset the published flag on published version - if (published.Version != content.Version) - repository.ClearPublished(published); + // fixme delete xml from database! was in _publishingStrategy.UnPublishingFinalized(content); repository.DeleteContentXml(content); + uow.Complete(); } - //Delete xml from db? and call following method to fire event through PublishingStrategy to update cache - if (omitCacheRefresh == false) - _publishingStrategy.UnPublishingFinalized(content); - Audit(AuditType.UnPublish, "UnPublish performed by user", userId, content.Id); - - return Attempt.Succeed(new UnPublishStatus(content, UnPublishedStatusType.Success, evtMsgs)); + UnPublished.RaiseEvent(new PublishEventArgs(content, false, false), this); + return Attempt.Succeed(new UnPublishStatus(UnPublishedStatusType.Success, evtMsgs, content)); } /// @@ -1982,295 +1993,117 @@ namespace Umbraco.Core.Services { var evtMsgs = EventMessagesFactory.Get(); - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled( - new SaveEventArgs(content, evtMsgs), this)) - { - return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent, evtMsgs)); - } - } + if (raiseEvents && Saving.IsRaisedEventCancelled(new SaveEventArgs(content), this)) + return Attempt.Fail(new PublishStatus(PublishStatusType.FailedCancelledByEvent, evtMsgs, content)); - using (new WriteLock(Locker)) - { - //Has this content item previously been published? If so, we don't need to refresh the children - var previouslyPublished = content.HasIdentity && HasPublishedVersion(content.Id); //content might not have an id - var publishStatus = new PublishStatus(content, PublishStatusType.Success, evtMsgs); //initially set to success + var isNew = content.IsNewEntity(); + var previouslyPublished = content.HasIdentity && content.HasPublishedVersion; + var status = default(Attempt); - //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published - publishStatus.StatusType = CheckAndLogIsPublishable(content); - //if it is not successful, then check if the props are valid - if ((int)publishStatus.StatusType < 10) - { - //Content contains invalid property values and can therefore not be published - fire event? - publishStatus.StatusType = CheckAndLogIsValid(content); - //set the invalid properties (if there are any) - publishStatus.InvalidProperties = ((ContentBase)content).LastInvalidProperties; - } - //if we're still successful, then publish using the strategy - if (publishStatus.StatusType == PublishStatusType.Success) - { - var internalStrategy = (PublishingStrategy)_publishingStrategy; - //Publish and then update the database with new status - var publishResult = internalStrategy.PublishInternal(content, userId); - //set the status type to the publish result - publishStatus.StatusType = publishResult.Result.StatusType; - } - - //we are successfully published if our publishStatus is still Successful - bool published = publishStatus.StatusType == PublishStatusType.Success; - - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - if (published == false) - { - content.ChangePublishedState(PublishedState.Saved); - } - //Since this is the Save and Publish method, the content should be saved even though the publish fails or isn't allowed - if (content.HasIdentity == false) - { - content.CreatorId = userId; - } - content.WriterId = userId; - - repository.AddOrUpdate(content); - - //Generate a new preview - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); - - if (published) - { - //Content Xml - repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); - } - - uow.Complete(); - } - - if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(content, false, evtMsgs), this); - - //Save xml to db and call following method to fire event through PublishingStrategy to update cache - if (published) - { - _publishingStrategy.PublishingFinalized(content); - } - - //We need to check if children and their publish state to ensure that we 'republish' content that was previously published - if (published && previouslyPublished == false && HasChildren(content.Id)) - { - var descendants = GetPublishedDescendants(content); - - _publishingStrategy.PublishingFinalized(descendants, false); - } - - Audit(AuditType.Publish, "Save and Publish performed by user", userId, content.Id); - - return Attempt.If(publishStatus.StatusType == PublishStatusType.Success, publishStatus); - } - } - - /// - /// Saves a single object - /// - /// The to save - /// Boolean indicating whether or not to change the Published state upon saving - /// Optional Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - private Attempt Save(IContent content, bool changeState, int userId = 0, bool raiseEvents = true) - { - var evtMsgs = EventMessagesFactory.Get(); - - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled( - new SaveEventArgs(content, evtMsgs), - this)) - { - return OperationStatus.Cancelled(evtMsgs); - } - } - - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - if (content.HasIdentity == false) - { - content.CreatorId = userId; - } - content.WriterId = userId; - - //Only change the publish state if the "previous" version was actually published or marked as unpublished - if (changeState && (content.Published || ((Content)content).PublishedState == PublishedState.Unpublished)) - content.ChangePublishedState(PublishedState.Saved); - - repository.AddOrUpdate(content); - - //Generate a new preview - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); - - uow.Complete(); - } - - if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(content, false, evtMsgs), this); - - Audit(AuditType.Save, "Save Content performed by user", userId, content.Id); - - return OperationStatus.Success(evtMsgs); - } - } - - /// - /// Checks if the passed in can be published based on the anscestors publish state. - /// - /// - /// Check current is only used when falling back to checking the Parent of non-saved content, as - /// non-saved content doesn't have a valid path yet. - /// - /// to check if anscestors are published - /// Boolean indicating whether the passed in content should also be checked for published versions - /// True if the Content can be published, otherwise False - private bool IsPublishable(IContent content, bool checkCurrent) - { - var ids = content.Path.Split(',').Select(int.Parse).ToList(); - foreach (var id in ids) - { - //If Id equals that of the recycle bin we return false because nothing in the bin can be published - if (id == Constants.System.RecycleBinContent) - return false; - - //We don't check the System Root, so just continue - if (id == Constants.System.Root) continue; - - //If the current id equals that of the passed in content and if current shouldn't be checked we skip it. - if (checkCurrent == false && id == content.Id) continue; - - //Check if the content for the current id is published - escape the loop if we encounter content that isn't published - var hasPublishedVersion = HasPublishedVersion(id); - if (hasPublishedVersion == false) - return false; - } - - return true; - } - - private PublishStatusType CheckAndLogIsPublishable(IContent content) - { - //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published - if (content.ParentId != Constants.System.Root && content.ParentId != Constants.System.RecycleBinContent && IsPublishable(content) == false) - { - Logger.Info( - string.Format( - "Content '{0}' with Id '{1}' could not be published because its parent is not published.", - content.Name, content.Id)); - return PublishStatusType.FailedPathNotPublished; - } - else if (content.ExpireDate.HasValue && content.ExpireDate.Value > DateTime.MinValue && DateTime.Now > content.ExpireDate.Value) - { - Logger.Info( - string.Format( - "Content '{0}' with Id '{1}' has expired and could not be published.", - content.Name, content.Id)); - return PublishStatusType.FailedHasExpired; - } - else if (content.ReleaseDate.HasValue && content.ReleaseDate.Value > DateTime.MinValue && content.ReleaseDate.Value > DateTime.Now) - { - Logger.Info( - string.Format( - "Content '{0}' with Id '{1}' is awaiting release and could not be published.", - content.Name, content.Id)); - return PublishStatusType.FailedAwaitingRelease; - } - - return PublishStatusType.Success; - } - - private PublishStatusType CheckAndLogIsValid(IContent content) - { - //Content contains invalid property values and can therefore not be published - fire event? - if (content.IsValid() == false) - { - Logger.Info( - string.Format( - "Content '{0}' with Id '{1}' could not be published because of invalid properties.", - content.Name, content.Id)); - return PublishStatusType.FailedContentInvalid; - } - - return PublishStatusType.Success; - } - - private IContentType FindContentTypeByAlias(string contentTypeAlias) - { using (var uow = UowProvider.CreateUnitOfWork()) { - var repository = uow.CreateRepository(); - var query = repository.Query.Where(x => x.Alias == contentTypeAlias); - var types = repository.GetByQuery(query); + uow.WriteLock(Constants.Locks.ContentTree); + var repository = uow.CreateRepository(); - if (types.Any() == false) - throw new Exception( - string.Format("No ContentType matching the passed in Alias: '{0}' was found", - contentTypeAlias)); + // fixme - EnsurePublishable vs StrategyCanPublish? + // EnsurePublishable ensures that path published is ok + // StrategyCanPublish ensures other things including valid properties + // should we merge or?! - var contentType = types.First(); + // ensure content is publishable, and try to publish + status = EnsurePublishable(content, evtMsgs); + if (status.Success) + { + // strategy handles events, and various business rules eg release & expire + // dates, trashed status... + status = StrategyPublish(content, false, userId, evtMsgs); + } - if (contentType == null) - throw new Exception(string.Format("ContentType matching the passed in Alias: '{0}' was null", - contentTypeAlias)); + // save - always, even if not publishing (this is SaveAndPublish) + if (content.HasIdentity == false) + content.CreatorId = userId; + content.WriterId = userId; - return contentType; + repository.AddOrUpdate(content); + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); + if (content.Published) + repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c)); + + uow.Complete(); } + + if (status.Success == false) + { + // fixme what about the saved event? + return status; + } + + Published.RaiseEvent(new PublishEventArgs(content, false, false), this); + + // if was not published and now is... descendants that were 'published' (but + // had an unpublished ancestor) are 're-published' ie not explicitely published + // but back as 'published' nevertheless + if (isNew == false && previouslyPublished == false) + { + if (HasChildren(content.Id)) + { + var descendants = GetPublishedDescendants(content).ToArray(); + Published.RaiseEvent(new PublishEventArgs(descendants, false, false), this); + } + } + + Audit(AuditType.Publish, "Save and Publish performed by user", userId, content.Id); + return status; } - #endregion - - #region Proxy Event Handlers - /// - /// Occurs before publish. - /// - /// Proxy to the real event on the - public static event TypedEventHandler> Publishing + private Attempt EnsurePublishable(IContent content, EventMessages evtMsgs) { - add { PublishingStrategy.Publishing += value; } - remove { PublishingStrategy.Publishing -= value; } + // root content can be published + var checkParents = content.ParentId == Constants.System.Root; + + // trashed content cannot be published + if (checkParents == false && content.ParentId != Constants.System.RecycleBinContent) + { + // ensure all ancestors are published + // because content may be new its Path may be null - start with parent + var path = content.Path ?? content.Parent().Path; + if (path != null) // if parent is also null, give up + { + var ancestorIds = path.Split(',') + .Skip(1) // remove leading "-1" + .Reverse() + .Select(int.Parse); + if (content.Path != null) + ancestorIds = ancestorIds.Skip(1); // remove trailing content.Id + + if (ancestorIds.All(HasPublishedVersion)) + checkParents = true; + } + } + + if (checkParents == false) + { + Logger.Info($"Content '{content.Name}' with Id '{content.Id}' could not be published because its parent is not published."); + return Attempt.Fail(new PublishStatus(PublishStatusType.FailedPathNotPublished, evtMsgs, content)); + } + + // fixme - should we do it - are we doing it for descendants too? + if (content.IsValid() == false) + { + Logger.Info($"Content '{content.Name}' with Id '{content.Id}' could not be published because of invalid properties."); + return Attempt.Fail(new PublishStatus(PublishStatusType.FailedContentInvalid, evtMsgs, content) + { + InvalidProperties = ((ContentBase)content).LastInvalidProperties + }); + } + + return Attempt.Succeed(new PublishStatus(PublishStatusType.Success, evtMsgs, content)); } - /// - /// Occurs after publish. - /// - /// Proxy to the real event on the - public static event TypedEventHandler> Published - { - add { PublishingStrategy.Published += value; } - remove { PublishingStrategy.Published -= value; } - } - /// - /// Occurs before unpublish. - /// - /// Proxy to the real event on the - public static event TypedEventHandler> UnPublishing - { - add { PublishingStrategy.UnPublishing += value; } - remove { PublishingStrategy.UnPublishing -= value; } - } - - /// - /// Occurs after unpublish. - /// - /// Proxy to the real event on the - public static event TypedEventHandler> UnPublished - { - add { PublishingStrategy.UnPublished += value; } - remove { PublishingStrategy.UnPublished -= value; } - } #endregion #region Event Handlers + /// /// Occurs before Delete /// @@ -2375,6 +2208,384 @@ namespace Umbraco.Core.Services /// Occurs after the Recycle Bin has been Emptied /// public static event TypedEventHandler EmptiedRecycleBin; + + /// + /// Occurs before publish + /// + public static event TypedEventHandler> Publishing; + + /// + /// Occurs after publish + /// + public static event TypedEventHandler> Published; + + /// + /// Occurs before unpublish + /// + public static event TypedEventHandler> UnPublishing; + + /// + /// Occurs after unpublish + /// + public static event TypedEventHandler> UnPublished; + + #endregion + + #region Publishing Strategies + + // prob. want to find nicer names? + + internal Attempt StrategyCanPublish(IContent content, int userId, EventMessages evtMsgs) + { + if (Publishing.IsRaisedEventCancelled(new PublishEventArgs(content, evtMsgs), this)) + { + Logger.Info($"Content '{content.Name}' with Id '{content.Id}' will not be published, the event was cancelled."); + return Attempt.Fail(new PublishStatus(PublishStatusType.FailedCancelledByEvent, evtMsgs, content)); + } + + // check if the content is valid + if (content.IsValid() == false) + { + Logger.Info($"Content '{content.Name}' with Id '{content.Id}' could not be published because of invalid properties."); + return Attempt.Fail(new PublishStatus(PublishStatusType.FailedContentInvalid, evtMsgs, content) + { + InvalidProperties = ((ContentBase)content).LastInvalidProperties + }); + } + + // check if the Content is Expired + if (content.Status == ContentStatus.Expired) + { + Logger.Info($"Content '{content.Name}' with Id '{content.Id}' has expired and could not be published."); + return Attempt.Fail(new PublishStatus(PublishStatusType.FailedHasExpired, evtMsgs, content)); + } + + // check if the Content is Awaiting Release + if (content.Status == ContentStatus.AwaitingRelease) + { + Logger.Info($"Content '{content.Name}' with Id '{content.Id}' is awaiting release and could not be published."); + return Attempt.Fail(new PublishStatus(PublishStatusType.FailedAwaitingRelease, evtMsgs, content)); + } + + // check if the Content is Trashed + if (content.Status == ContentStatus.Trashed) + { + Logger.Info($"Content '{content.Name}' with Id '{content.Id}' is trashed and could not be published."); + return Attempt.Fail(new PublishStatus(PublishStatusType.FailedIsTrashed, evtMsgs, content)); + } + + return Attempt.Succeed(new PublishStatus(content, evtMsgs)); + } + + internal Attempt StrategyPublish(IContent content, bool alreadyCheckedCanPublish, int userId, EventMessages evtMsgs) + { + var attempt = alreadyCheckedCanPublish + ? Attempt.Succeed(new PublishStatus(content, evtMsgs)) // already know we can + : StrategyCanPublish(content, userId, evtMsgs); // else check + if (attempt.Success == false) + return attempt; + + // change state to publishing + content.ChangePublishedState(PublishedState.Publishing); + + Logger.Info($"Content '{content.Name}' with Id '{content.Id}' has been published."); + + return attempt; + } + + /// + /// Publishes a list of content items + /// + /// Contents, ordered by level ASC + /// Contents for which we've already checked CanPublish + /// + /// + /// Indicates whether to publish content that is completely unpublished (has no published + /// version). If false, will only publish already published content with changes. Also impacts what happens if publishing + /// fails (see remarks). + /// + /// + /// Navigate content & descendants top-down and for each, + /// - if it is published + /// - and unchanged, do nothing + /// - else (has changes), publish those changes + /// - if it is not published + /// - and at top-level, publish + /// - or includeUnpublished is true, publish + /// - else do nothing & skip the underlying branch + /// + /// When publishing fails + /// - if content has no published version, skip the underlying branch + /// - else (has published version), + /// - if includeUnpublished is true, process the underlying branch + /// - else, do not process the underlying branch + /// + internal IEnumerable> StrategyPublishWithChildren(IEnumerable contents, IEnumerable alreadyChecked, int userId, EventMessages evtMsgs, bool includeUnpublished = true) + { + var statuses = new List>(); + var alreadyCheckedA = (alreadyChecked ?? Enumerable.Empty()).ToArray(); + + // list of ids that we exclude because they could not be published + var excude = new List(); + + var topLevel = -1; + foreach (var content in contents) + { + // initialize - content is ordered by level ASC + if (topLevel < 0) + topLevel = content.Level; + + if (excude.Contains(content.ParentId)) + { + // parent is excluded, so exclude content too + Logger.Info($"Content '{content.Name}' with Id '{content.Id}' will not be published because it's parent's publishing action failed or was cancelled."); + excude.Add(content.Id); + // status has been reported for an ancestor and that one is excluded => no status + continue; + } + + if (content.Published && content.Level > topLevel) // topLevel we DO want to (re)publish + { + // newest is published already + statuses.Add(Attempt.Succeed(new PublishStatus(PublishStatusType.SuccessAlreadyPublished, evtMsgs, content))); + continue; + } + + if (content.HasPublishedVersion) + { + // newest is published already but we are topLevel, or + // newest is not published, but another version is - publish newest + var r = StrategyPublish(content, alreadyCheckedA.Contains(content), userId, evtMsgs); + if (r.Success == false) + { + // we tried to publish and it failed, but it already had / still has a published version, + // the rule in remarks says that we should skip the underlying branch if includeUnpublished + // is false, else process it - not that it makes much sense, but keep it like that for now + if (includeUnpublished == false) + excude.Add(content.Id); + } + + statuses.Add(r); + continue; + } + + if (content.Level == topLevel || includeUnpublished) + { + // content has no published version, and we want to publish it, either + // because it is top-level or because we include unpublished. + // if publishing fails, and because content does not have a published + // version at all, ensure we do not process its descendants + var r = StrategyPublish(content, alreadyCheckedA.Contains(content), userId, evtMsgs); + if (r.Success == false) + excude.Add(content.Id); + + statuses.Add(r); + continue; + } + + // content has no published version, and we don't want to publish it + excude.Add(content.Id); // ignore everything below it + // content is not even considered, really => no status + } + + return statuses; + } + + internal Attempt StrategyCanUnPublish(IContent content, int userId, EventMessages evtMsgs) + { + // fire UnPublishing event + if (UnPublishing.IsRaisedEventCancelled(new PublishEventArgs(content, evtMsgs), this)) + { + Logger.Info($"Content '{content.Name}' with Id '{content.Id}' will not be unpublished, the event was cancelled."); + return Attempt.Fail(new UnPublishStatus(UnPublishedStatusType.FailedCancelledByEvent, evtMsgs, content)); + } + + return Attempt.Succeed(new UnPublishStatus(content, evtMsgs)); + } + + internal Attempt StrategyUnPublish(IContent content, bool alreadyCheckedCanUnPublish, int userId, EventMessages evtMsgs) + { + // content should (is assumed to) be the newest version, which may not be published, + // don't know how to test this, so it's not verified + + var attempt = alreadyCheckedCanUnPublish + ? Attempt.Succeed(new UnPublishStatus(content, evtMsgs)) // already know we can + : StrategyCanUnPublish(content, userId, evtMsgs); + if (attempt.Success == false) + return attempt; + + // if Content has a release date set to before now, it should be removed so it doesn't interrupt an unpublish + // otherwise it would remain released == published + if (content.ReleaseDate.HasValue && content.ReleaseDate.Value <= DateTime.Now) + { + content.ReleaseDate = null; + Logger.Info($"Content '{content.Name}' with Id '{content.Id}' had its release date removed, because it was unpublished."); + } + + // version is published or unpublished, but content is published + // change state to unpublishing + content.ChangePublishedState(PublishedState.Unpublishing); + + Logger.Info($"Content '{content.Name}' with Id '{content.Id}' has been unpublished."); + + return attempt; + } + + internal IEnumerable> StrategyUnPublish(IEnumerable content, int userId, EventMessages evtMsgs) + { + return content.Select(x => StrategyUnPublish(x, false, userId, evtMsgs)); + } + + #endregion + + #region Content Types + + /// + /// Deletes all content of specified type. All children of deleted content is moved to Recycle Bin. + /// + /// This needs extra care and attention as its potentially a dangerous and extensive operation + /// Id of the + /// Optional Id of the user issueing the delete operation + public void DeleteContentOfType(int contentTypeId, int userId = 0) + { + //TODO: This currently this is called from the ContentTypeService but that needs to change, + // if we are deleting a content type, we should just delete the data and do this operation slightly differently. + // This method will recursively go lookup every content item, check if any of it's descendants are + // of a different type, move them to the recycle bin, then permanently delete the content items. + // The main problem with this is that for every content item being deleted, events are raised... + // which we need for many things like keeping caches in sync, but we can surely do this MUCH better. + + var moves = new List>(); + + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(Constants.Locks.ContentTree); + var repository = uow.CreateRepository(); + + // fixme what about content that has the contenttype as part of its composition? + var query = repository.Query.Where(x => x.ContentTypeId == contentTypeId); + var contents = repository.GetByQuery(query).ToArray(); + + if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(contents), this)) + return; // causes rollback + + // order by level, descending, so deepest first - that way, we cannot move + // a content of the deleted type, to the recycle bin (and then delete it...) + foreach (var content in contents.OrderByDescending(x => x.ParentId)) + { + // if it's not trashed yet, and published, we should unpublish + // but... UnPublishing event makes no sense (not going to cancel?) and no need to save + // just raise the event + if (content.Trashed == false && content.HasPublishedVersion) + UnPublished.RaiseEvent(new PublishEventArgs(content, false, false), this); + + // if current content has children, move them to trash + var c = content; + var childQuery = repository.Query.Where(x => x.Path.StartsWith(c.Path)); + var children = repository.GetByQuery(childQuery); + foreach (var child in children.Where(x => x.ContentTypeId != contentTypeId)) + { + // see MoveToRecycleBin + PerformMoveLocked(repository, child, Constants.System.RecycleBinContent, null, userId, moves, true); + } + + // delete content + // triggers the deleted event (and handles the files) + DeleteLocked(repository, content); + } + + uow.Complete(); + } + + var moveInfos = moves + .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) + .ToArray(); + if (moveInfos.Length > 0) + Trashed.RaiseEvent(new MoveEventArgs(false, moveInfos), this); + + Audit(AuditType.Delete, $"Delete Content of Type {contentTypeId} performed by user", userId, Constants.System.Root); + } + + private IContentType GetContentType(string contentTypeAlias) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.ContentTree); + + var repository = uow.CreateRepository(); + var query = repository.Query.Where(x => x.Alias == contentTypeAlias); + var contentType = repository.GetByQuery(query).FirstOrDefault(); + + if (contentType == null) + throw new Exception($"No ContentType matching the passed in Alias: '{contentTypeAlias}' was found"); // causes rollback + + uow.Complete(); + return contentType; + } + } + + #endregion + + #region Xml - Shoud Move! + + /// + /// Returns the persisted content's XML structure + /// + /// + /// + public XElement GetContentXml(int contentId) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.ContentTree); + var repository = uow.CreateRepository(); + var elt = repository.GetContentXml(contentId); + uow.Complete(); + return elt; + } + } + + /// + /// Returns the persisted content's preview XML structure + /// + /// + /// + /// + public XElement GetContentPreviewXml(int contentId, Guid version) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(Constants.Locks.ContentTree); + var repository = uow.CreateRepository(); + var elt = repository.GetContentPreviewXml(contentId, version); + uow.Complete(); + return elt; + } + } + + /// + /// Rebuilds all xml content in the cmsContentXml table for all documents + /// + /// + /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures + /// for all content + /// + public void RebuildXmlStructures(params int[] contentTypeIds) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(Constants.Locks.ContentTree); + var repository = uow.CreateRepository(); + repository.RebuildXmlStructures( + content => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, content), + contentTypeIds: contentTypeIds.Length == 0 ? null : contentTypeIds); + uow.Complete(); + } + + Audit(AuditType.Publish, "ContentService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, Constants.System.Root); + + } + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index 4013f0a943..fcbad641cf 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -1,20 +1,12 @@ using System; using System.Collections.Generic; -using System.Data; -using System.Diagnostics; using System.Linq; using System.Text; -using System.Xml.Linq; -using System.Threading; -using AutoMapper; -using Umbraco.Core.Configuration; using Umbraco.Core.Events; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; @@ -23,1287 +15,77 @@ namespace Umbraco.Core.Services /// /// Represents the ContentType Service, which is an easy access to operations involving /// - public class ContentTypeService : ContentTypeServiceBase, IContentTypeService + internal class ContentTypeService : ContentTypeServiceBase, IContentTypeService { - private readonly IContentService _contentService; - private readonly IMediaService _mediaService; + private IContentService _contentService; - //Support recursive locks because some of the methods that require locking call other methods that require locking. - //for example, the Move method needs to be locked but this calls the Save method which also needs to be locked. - private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); - - public ContentTypeService(IDatabaseUnitOfWorkProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IContentService contentService, IMediaService mediaService) + public ContentTypeService(IDatabaseUnitOfWorkProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IContentService contentService) : base(provider, logger, eventMessagesFactory) { - if (contentService == null) throw new ArgumentNullException("contentService"); - if (mediaService == null) throw new ArgumentNullException("mediaService"); _contentService = contentService; - _mediaService = mediaService; } - #region Containers - public Attempt> CreateContentTypeContainer(int parentId, string name, int userId = 0) + // beware! order is important to avoid deadlocks + protected override int[] ReadLockIds { get; } = { Constants.Locks.ContentTypes }; + protected override int[] WriteLockIds { get; } = { Constants.Locks.ContentTree, Constants.Locks.ContentTypes }; + + // don't change or remove this, will need it later + private IContentService ContentService => _contentService; + //// handle circular dependencies + //internal IContentService ContentService + //{ + // get + // { + // if (_contentService == null) + // throw new InvalidOperationException("ContentTypeService.ContentService has not been initialized."); + // return _contentService; + // } + // set { _contentService = value; } + //} + + protected override Guid ContainedObjectType => Constants.ObjectTypes.DocumentTypeGuid; + + protected override void DeleteItemsOfTypes(IEnumerable typeIds) { - var evtMsgs = EventMessagesFactory.Get(); - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repo = uow.CreateRepository(); - try - { - var container = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid) - { - Name = name, - 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.Complete(); - - 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(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); - } - } + foreach (var typeId in typeIds) + ContentService.DeleteContentOfType(typeId); } - public Attempt> CreateMediaTypeContainer(int parentId, string name, int userId = 0) - { - var evtMsgs = EventMessagesFactory.Get(); - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repo = uow.CreateRepository(); - try - { - var container = new EntityContainer(Constants.ObjectTypes.MediaTypeGuid) - { - Name = name, - 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.Complete(); - - 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(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); - } - } - } - - public Attempt SaveContentTypeContainer(EntityContainer container, int userId = 0) - { - return SaveContainer( - SavingContentTypeContainer, SavedContentTypeContainer, - container, Constants.ObjectTypes.DocumentTypeContainerGuid, "document type", userId); - } - - public Attempt SaveMediaTypeContainer(EntityContainer container, int userId = 0) - { - return SaveContainer( - SavingMediaTypeContainer, SavedMediaTypeContainer, - container, Constants.ObjectTypes.MediaTypeContainerGuid, "media type", userId); - } - - private Attempt SaveContainer( - TypedEventHandler> savingEvent, - TypedEventHandler> savedEvent, - EntityContainer container, - Guid containerObjectType, - string objectTypeName, int userId) - { - var evtMsgs = EventMessagesFactory.Get(); - - if (container.ContainedObjectType != containerObjectType) - { - var ex = new InvalidOperationException("Not a " + objectTypeName + " container."); - return OperationStatus.Exception(evtMsgs, ex); - } - - if (container.HasIdentity && container.IsPropertyDirty("ParentId")) - { - 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); - } - - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repo = uow.CreateContainerRepository(containerObjectType); - repo.AddOrUpdate(container); - uow.Complete(); - } - - savedEvent.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); - - //TODO: Audit trail ? - - return OperationStatus.Success(evtMsgs); - } - - public EntityContainer GetContentTypeContainer(int containerId) - { - return GetContainer(containerId, Constants.ObjectTypes.DocumentTypeContainerGuid); - } - - public EntityContainer GetMediaTypeContainer(int containerId) - { - return GetContainer(containerId, Constants.ObjectTypes.MediaTypeContainerGuid); - } - - private EntityContainer GetContainer(int containerId, Guid containerObjectType) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repo = uow.CreateContainerRepository(containerObjectType); - var container = repo.Get(containerId); - return container; - } - } - - public IEnumerable GetMediaTypeContainers(int[] containerIds) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repo = uow.CreateRepository(); - return repo.GetAll(containerIds); - } - } - - public IEnumerable GetMediaTypeContainers(string name, int level) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repo = uow.CreateRepository(); - return ((EntityContainerRepository) repo).Get(name, level); - } - } - - public IEnumerable GetMediaTypeContainers(IMediaType mediaType) - { - var ancestorIds = mediaType.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => - { - var asInt = x.TryConvertTo(); - if (asInt) return asInt.Result; - return int.MinValue; - }) - .Where(x => x != int.MinValue && x != mediaType.Id) - .ToArray(); - - return GetMediaTypeContainers(ancestorIds); - } - - public EntityContainer GetContentTypeContainer(Guid containerId) - { - return GetContainer(containerId, Constants.ObjectTypes.DocumentTypeContainerGuid); - } - - public IEnumerable GetContentTypeContainers(int[] containerIds) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repo = uow.CreateRepository(); - return repo.GetAll(containerIds); - } - } - - public IEnumerable GetContentTypeContainers(IContentType contentType) - { - var ancestorIds = contentType.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries) - .Select(x => - { - var asInt = x.TryConvertTo(); - if (asInt) return asInt.Result; - return int.MinValue; - }) - .Where(x => x != int.MinValue && x != contentType.Id) - .ToArray(); - - return GetContentTypeContainers(ancestorIds); - } - - public EntityContainer GetMediaTypeContainer(Guid containerId) - { - return GetContainer(containerId, Constants.ObjectTypes.MediaTypeContainerGuid); - } - - private EntityContainer GetContainer(Guid containerId, Guid containerObjectType) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repo = uow.CreateContainerRepository(containerObjectType); - var container = ((EntityContainerRepository)repo).Get(containerId); - return container; - } - } - - public IEnumerable GetContentTypeContainers(string name, int level) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repo = uow.CreateRepository(); - return ((EntityContainerRepository)repo).Get(name, level); - } - } - - public Attempt DeleteContentTypeContainer(int containerId, int userId = 0) - { - var evtMsgs = EventMessagesFactory.Get(); - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repo = uow.CreateRepository(); - var container = repo.Get(containerId); - 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.Complete(); - - DeletedContentTypeContainer.RaiseEvent(new DeleteEventArgs(container, evtMsgs), this); - - return OperationStatus.Success(evtMsgs); - //TODO: Audit trail ? - } - } - - public Attempt DeleteMediaTypeContainer(int containerId, int userId = 0) - { - var evtMsgs = EventMessagesFactory.Get(); - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repo = uow.CreateRepository(); - var container = repo.Get(containerId); - 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.Complete(); - - DeletedMediaTypeContainer.RaiseEvent(new DeleteEventArgs(container, evtMsgs), this); - - return OperationStatus.Success(evtMsgs); - //TODO: Audit trail ? - } - } - - #endregion - /// - /// Gets all property type aliases. + /// Gets all property type aliases accross content, media and member types. /// - /// + /// All property type aliases. + /// Beware! Works accross content, media and member types. public IEnumerable GetAllPropertyTypeAliases() { using (var uow = UowProvider.CreateUnitOfWork()) { - var repository = uow.CreateRepository(); - return repository.GetAllPropertyTypeAliases(); - } - } - - /// - /// Gets all content type aliases - /// - /// - /// If this list is empty, it will return all content type aliases for media, members and content, otherwise - /// it will only return content type aliases for the object types specified - /// - /// - public IEnumerable GetAllContentTypeAliases(params Guid[] objectTypes) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - return repository.GetAllContentTypeAliases(objectTypes); - } - } - - /// - /// Copies a content type as a child under the specified parent if specified (otherwise to the root) - /// - /// - /// The content type to copy - /// - /// - /// The new alias of the content type - /// - /// - /// The new name of the content type - /// - /// - /// The parent to copy the content type to, default is -1 (root) - /// - /// - public IContentType Copy(IContentType original, string alias, string name, int parentId = -1) - { - IContentType parent = null; - if (parentId > 0) - { - parent = GetContentType(parentId); - if (parent == null) - { - throw new InvalidOperationException("Could not find content type with id " + parentId); - } - } - return Copy(original, alias, name, parent); - } - - /// - /// Copies a content type as a child under the specified parent if specified (otherwise to the root) - /// - /// - /// The content type to copy - /// - /// - /// The new alias of the content type - /// - /// - /// The new name of the content type - /// - /// - /// The parent to copy the content type to, default is null (root) - /// - /// - public IContentType Copy(IContentType original, string alias, string name, IContentType parent) - { - Mandate.ParameterNotNull(original, "original"); - Mandate.ParameterNotNullOrEmpty(alias, "alias"); - if (parent != null) - { - Mandate.That(parent.HasIdentity, () => new InvalidOperationException("The parent content type must have an identity")); - } - - var clone = original.DeepCloneWithResetIdentities(alias); - - clone.Name = name; - - var compositionAliases = clone.CompositionAliases().Except(new[] { alias }).ToList(); - //remove all composition that is not it's current alias - foreach (var a in compositionAliases) - { - clone.RemoveContentType(a); - } - - //if a parent is specified set it's composition and parent - if (parent != null) - { - //add a new parent composition - clone.AddContentType(parent); - clone.ParentId = parent.Id; - } - else - { - //set to root - clone.ParentId = -1; - } - - Save(clone); - return clone; - } - - /// - /// Gets an object by its Id - /// - /// Id of the to retrieve - /// - public IContentType GetContentType(int id) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - return repository.Get(id); - } - } - - /// - /// Gets an object by its Alias - /// - /// Alias of the to retrieve - /// - public IContentType GetContentType(string alias) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - return repository.Get(alias); - } - } - - /// - /// Gets an object by its Key - /// - /// Alias of the to retrieve - /// - public IContentType GetContentType(Guid id) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - return repository.Get(id); - } - } - - /// - /// Gets a list of all available objects - /// - /// Optional list of ids - /// An Enumerable list of objects - public IEnumerable GetAllContentTypes(params int[] ids) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - return repository.GetAll(ids); - } - } - - /// - /// Gets a list of all available objects - /// - /// Optional list of ids - /// An Enumerable list of objects - public IEnumerable GetAllContentTypes(IEnumerable ids) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - return repository.GetAll(ids.ToArray()); - } - } - - /// - /// Gets a list of children for a object - /// - /// Id of the Parent - /// An Enumerable list of objects - public IEnumerable GetContentTypeChildren(int id) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var query = repository.Query.Where(x => x.ParentId == id); - var contentTypes = repository.GetByQuery(query); - return contentTypes; - } - } - - /// - /// Gets a list of children for a object - /// - /// Id of the Parent - /// An Enumerable list of objects - public IEnumerable GetContentTypeChildren(Guid id) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var found = GetContentType(id); - if (found == null) return Enumerable.Empty(); - var query = repository.Query.Where(x => x.ParentId == found.Id); - var contentTypes = repository.GetByQuery(query); - return contentTypes; - } - } - - /// - /// Checks whether an item has any children - /// - /// Id of the - /// True if the content type has any children otherwise False - public bool HasChildren(int id) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var query = repository.Query.Where(x => x.ParentId == id); - int count = repository.Count(query); - return count > 0; - } - } - - /// - /// Checks whether an item has any children - /// - /// Id of the - /// True if the content type has any children otherwise False - public bool HasChildren(Guid id) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var found = GetContentType(id); - if (found == null) return false; - var query = repository.Query.Where(x => x.ParentId == found.Id); - int count = repository.Count(query); - return count > 0; - } - } - - /// - /// This is called after an IContentType is saved and is used to update the content xml structures in the database - /// if they are required to be updated. - /// - /// A tuple of a content type and a boolean indicating if it is new (HasIdentity was false before committing) - private void UpdateContentXmlStructure(params IContentTypeBase[] contentTypes) - { - - var toUpdate = GetContentTypesForXmlUpdates(contentTypes).ToArray(); - - if (toUpdate.Any()) - { - var firstType = toUpdate.First(); - //if it is a content type then call the rebuilding methods or content - if (firstType is IContentType) - { - var typedContentService = _contentService as ContentService; - if (typedContentService != null) - { - typedContentService.RePublishAll(toUpdate.Select(x => x.Id).ToArray()); - } - else - { - //this should never occur, the content service should always be typed but we'll check anyways. - _contentService.RePublishAll(); - } - } - else if (firstType is IMediaType) - { - //if it is a media type then call the rebuilding methods for media - var typedContentService = _mediaService as MediaService; - if (typedContentService != null) - { - typedContentService.RebuildXmlStructures(toUpdate.Select(x => x.Id).ToArray()); - } - } - } - - } - - public int CountContentTypes() - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - return repository.Count(repository.Query); - } - } - - public int CountMediaTypes() - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - return repository.Count(repository.Query); - } - } - - /// - /// Validates the composition, if its invalid a list of property type aliases that were duplicated is returned - /// - /// - /// - public Attempt ValidateComposition(IContentTypeComposition compo) - { - using (new WriteLock(Locker)) - { - try - { - ValidateLocked(compo); - return Attempt.Succeed(); - } - catch (InvalidCompositionException ex) - { - return Attempt.Fail(ex.PropertyTypeAliases, ex); - } - } - } - - protected void ValidateLocked(IContentTypeComposition compositionContentType) - { - // performs business-level validation of the composition - // should ensure that it is absolutely safe to save the composition - - // eg maybe a property has been added, with an alias that's OK (no conflict with ancestors) - // but that cannot be used (conflict with descendants) - - var contentType = compositionContentType as IContentType; - var mediaType = compositionContentType as IMediaType; - var memberType = compositionContentType as IMemberType; // should NOT do it here but... v8! - - IContentTypeComposition[] allContentTypes; - if (contentType != null) - allContentTypes = GetAllContentTypes().Cast().ToArray(); - else if (mediaType != null) - allContentTypes = GetAllMediaTypes().Cast().ToArray(); - else if (memberType != null) - return; // no compositions on members, always validate - else - throw new Exception("Composition is neither IContentType nor IMediaType nor IMemberType?"); - - var compositionAliases = compositionContentType.CompositionAliases(); - var compositions = allContentTypes.Where(x => compositionAliases.Any(y => x.Alias.Equals(y))); - var propertyTypeAliases = compositionContentType.PropertyTypes.Select(x => x.Alias.ToLowerInvariant()).ToArray(); - var indirectReferences = allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == compositionContentType.Id)); - var comparer = new DelegateEqualityComparer((x, y) => x.Id == y.Id, x => x.Id); - var dependencies = new HashSet(compositions, comparer); - var stack = new Stack(); - indirectReferences.ForEach(stack.Push);//Push indirect references to a stack, so we can add recursively - while (stack.Count > 0) - { - var indirectReference = stack.Pop(); - dependencies.Add(indirectReference); - //Get all compositions for the current indirect reference - var directReferences = indirectReference.ContentTypeComposition; - - foreach (var directReference in directReferences) - { - if (directReference.Id == compositionContentType.Id || directReference.Alias.Equals(compositionContentType.Alias)) continue; - dependencies.Add(directReference); - //A direct reference has compositions of its own - these also need to be taken into account - var directReferenceGraph = directReference.CompositionAliases(); - allContentTypes.Where(x => directReferenceGraph.Any(y => x.Alias.Equals(y, StringComparison.InvariantCultureIgnoreCase))).ForEach(c => dependencies.Add(c)); - } - //Recursive lookup of indirect references - allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == indirectReference.Id)).ForEach(stack.Push); - } - - foreach (var dependency in dependencies) - { - if (dependency.Id == compositionContentType.Id) continue; - var contentTypeDependency = allContentTypes.FirstOrDefault(x => x.Alias.Equals(dependency.Alias, StringComparison.InvariantCultureIgnoreCase)); - if (contentTypeDependency == null) continue; - var intersect = contentTypeDependency.PropertyTypes.Select(x => x.Alias.ToLowerInvariant()).Intersect(propertyTypeAliases).ToArray(); - if (intersect.Length == 0) continue; - - throw new InvalidCompositionException(compositionContentType.Alias, intersect.ToArray()); - } - } - - /// - /// Saves a single object - /// - /// to save - /// Optional id of the user saving the ContentType - public void Save(IContentType contentType, int userId = 0) - { - if (SavingContentType.IsRaisedEventCancelled(new SaveEventArgs(contentType), this)) - return; - - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - ValidateLocked(contentType); // throws if invalid - contentType.CreatorId = userId; - repository.AddOrUpdate(contentType); - - uow.Complete(); - } - - UpdateContentXmlStructure(contentType); - } - SavedContentType.RaiseEvent(new SaveEventArgs(contentType, false), this); - Audit(AuditType.Save, string.Format("Save ContentType performed by user"), userId, contentType.Id); - } - - /// - /// Saves a collection of objects - /// - /// Collection of to save - /// Optional id of the user saving the ContentType - public void Save(IEnumerable contentTypes, int userId = 0) - { - var asArray = contentTypes.ToArray(); - - if (SavingContentType.IsRaisedEventCancelled(new SaveEventArgs(asArray), this)) - return; - - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - // all-or-nothing, validate them all first - foreach (var contentType in asArray) - { - ValidateLocked(contentType); // throws if invalid - } - foreach (var contentType in asArray) - { - contentType.CreatorId = userId; - repository.AddOrUpdate(contentType); - } - - //save it all in one go - uow.Complete(); - } - - UpdateContentXmlStructure(asArray.Cast().ToArray()); - } - SavedContentType.RaiseEvent(new SaveEventArgs(asArray, false), this); - Audit(AuditType.Save, string.Format("Save ContentTypes performed by user"), userId, -1); - } - - /// - /// Deletes a single object - /// - /// to delete - /// Optional id of the user issueing the delete - /// Deleting a will delete all the objects based on this - public void Delete(IContentType contentType, int userId = 0) - { - if (DeletingContentType.IsRaisedEventCancelled(new DeleteEventArgs(contentType), this)) - return; - - using (new WriteLock(Locker)) - { - - //TODO: This needs to change, if we are deleting a content type, we should just delete the data, - // this method will recursively go lookup every content item, check if any of it's descendants are - // of a different type, move them to the recycle bin, then permanently delete the content items. - // The main problem with this is that for every content item being deleted, events are raised... - // which we need for many things like keeping caches in sync, but we can surely do this MUCH better. - - _contentService.DeleteContentOfType(contentType.Id); - - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - repository.Delete(contentType); - uow.Complete(); - - DeletedContentType.RaiseEvent(new DeleteEventArgs(contentType, false), this); - } - - Audit(AuditType.Delete, string.Format("Delete ContentType performed by user"), userId, contentType.Id); - } - } - - /// - /// Deletes a collection of objects. - /// - /// Collection of to delete - /// Optional id of the user issueing the delete - /// - /// Deleting a will delete all the objects based on this - /// - public void Delete(IEnumerable contentTypes, int userId = 0) - { - var asArray = contentTypes.ToArray(); - - if (DeletingContentType.IsRaisedEventCancelled(new DeleteEventArgs(asArray), this)) - return; - - using (new WriteLock(Locker)) - { - foreach (var contentType in asArray) - { - _contentService.DeleteContentOfType(contentType.Id); - } - - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - foreach (var contentType in asArray) - { - repository.Delete(contentType); - } - - uow.Complete(); - - DeletedContentType.RaiseEvent(new DeleteEventArgs(asArray, false), this); - } - - Audit(AuditType.Delete, string.Format("Delete ContentTypes performed by user"), userId, -1); - } - } - - /// - /// Gets an object by its Id - /// - /// Id of the to retrieve - /// - public IMediaType GetMediaType(int id) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - return repository.Get(id); - } - } - - /// - /// Gets an object by its Alias - /// - /// Alias of the to retrieve - /// - public IMediaType GetMediaType(string alias) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - return repository.Get(alias); - } - } - - /// - /// Gets an object by its Id - /// - /// Id of the to retrieve - /// - public IMediaType GetMediaType(Guid id) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - return repository.Get(id); - } - } - - /// - /// Gets a list of all available objects - /// - /// Optional list of ids - /// An Enumerable list of objects - public IEnumerable GetAllMediaTypes(params int[] ids) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - return repository.GetAll(ids); - } - } - - /// - /// Gets a list of all available objects - /// - /// Optional list of ids - /// An Enumerable list of objects - public IEnumerable GetAllMediaTypes(IEnumerable ids) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - return repository.GetAll(ids.ToArray()); - } - } - - /// - /// Gets a list of children for a object - /// - /// Id of the Parent - /// An Enumerable list of objects - public IEnumerable GetMediaTypeChildren(int id) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var query = repository.Query.Where(x => x.ParentId == id); - var contentTypes = repository.GetByQuery(query); - return contentTypes; - } - } - - /// - /// Gets a list of children for a object - /// - /// Id of the Parent - /// An Enumerable list of objects - public IEnumerable GetMediaTypeChildren(Guid id) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var found = GetMediaType(id); - if (found == null) return Enumerable.Empty(); - var query = repository.Query.Where(x => x.ParentId == found.Id); - var contentTypes = repository.GetByQuery(query); - return contentTypes; - } - } - - /// - /// Checks whether an item has any children - /// - /// Id of the - /// True if the media type has any children otherwise False - public bool MediaTypeHasChildren(int id) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var query = repository.Query.Where(x => x.ParentId == id); - int count = repository.Count(query); - return count > 0; - } - } - - /// - /// Checks whether an item has any children - /// - /// Id of the - /// True if the media type has any children otherwise False - public bool MediaTypeHasChildren(Guid id) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - var found = GetMediaType(id); - if (found == null) return false; - var query = repository.Query.Where(x => x.ParentId == found.Id); - int count = repository.Count(query); - return count > 0; - } - } - - public Attempt> MoveMediaType(IMediaType toMove, int containerId) - { - var evtMsgs = EventMessagesFactory.Get(); - - if (MovingMediaType.IsRaisedEventCancelled( - new MoveEventArgs(evtMsgs, new MoveEventInfo(toMove, toMove.Path, containerId)), - this)) - { - return Attempt.Fail( - new OperationStatus( - MoveOperationStatusType.FailedCancelledByEvent, evtMsgs)); - } - - var moveInfo = new List>(); - using (var uow = UowProvider.CreateUnitOfWork()) - { - var containerRepository = uow.CreateRepository(); - var repository = uow.CreateRepository(); - - try - { - EntityContainer container = null; - if (containerId > 0) - { - container = containerRepository.Get(containerId); - if (container == null) - throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); - } - moveInfo.AddRange(repository.Move(toMove, container)); - } - catch (DataOperationException ex) - { - return Attempt.Fail( - new OperationStatus(ex.Operation, evtMsgs)); - } + // that one is special because it works accross content, media and member types + uow.ReadLock(Constants.Locks.ContentTypes, Constants.Locks.MediaTypes, Constants.Locks.MemberTypes); + var repo = uow.CreateRepository(); + var aliases = repo.GetAllPropertyTypeAliases(); uow.Complete(); + return aliases; } - - MovedMediaType.RaiseEvent(new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()), this); - - return Attempt.Succeed( - new OperationStatus(MoveOperationStatusType.Success, evtMsgs)); } - public Attempt> MoveContentType(IContentType toMove, int containerId) + /// + /// Gets all content type aliases accross content, media and member types. + /// + /// Optional object types guid to restrict to content, and/or media, and/or member types. + /// All property type aliases. + /// Beware! Works accross content, media and member types. + public IEnumerable GetAllContentTypeAliases(params Guid[] guids) { - var evtMsgs = EventMessagesFactory.Get(); - - if (MovingContentType.IsRaisedEventCancelled( - new MoveEventArgs(evtMsgs, new MoveEventInfo(toMove, toMove.Path, containerId)), - this)) - { - return Attempt.Fail( - new OperationStatus( - MoveOperationStatusType.FailedCancelledByEvent, evtMsgs)); - } - - var moveInfo = new List>(); using (var uow = UowProvider.CreateUnitOfWork()) { - var containerRepository = uow.CreateRepository(); - var repository = uow.CreateRepository(); - - try - { - EntityContainer container = null; - if (containerId > 0) - { - container = containerRepository.Get(containerId); - if (container == null) - throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); - } - moveInfo.AddRange(repository.Move(toMove, container)); - } - catch (DataOperationException ex) - { - return Attempt.Fail( - new OperationStatus(ex.Operation, evtMsgs)); - } + // that one is special because it works accross content, media and member types + uow.ReadLock(Constants.Locks.ContentTypes, Constants.Locks.MediaTypes, Constants.Locks.MemberTypes); + var repo = uow.CreateRepository(); + var aliases = repo.GetAllContentTypeAliases(guids); uow.Complete(); - } - - MovedContentType.RaiseEvent(new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()), this); - - return Attempt.Succeed( - new OperationStatus(MoveOperationStatusType.Success, evtMsgs)); - } - - public Attempt> CopyMediaType(IMediaType toCopy, int containerId) - { - var evtMsgs = EventMessagesFactory.Get(); - - IMediaType copy; - using (var uow = UowProvider.CreateUnitOfWork()) - { - var containerRepository = uow.CreateRepository(); - var repository = uow.CreateRepository(); - try - { - if (containerId > 0) - { - var container = containerRepository.Get(containerId); - if (container == null) - throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); - } - var alias = repository.GetUniqueAlias(toCopy.Alias); - copy = toCopy.DeepCloneWithResetIdentities(alias); - copy.Name = copy.Name + " (copy)"; // might not be unique - - // if it has a parent, and the parent is a content type, unplug composition - // all other compositions remain in place in the copied content type - if (copy.ParentId > 0) - { - var parent = repository.Get(copy.ParentId); - if (parent != null) - copy.RemoveContentType(parent.Alias); - } - - copy.ParentId = containerId; - repository.AddOrUpdate(copy); - } - catch (DataOperationException ex) - { - return Attempt.Fail(new OperationStatus(null, ex.Operation, evtMsgs)); - } - uow.Complete(); - } - - return Attempt.Succeed(new OperationStatus(copy, MoveOperationStatusType.Success, evtMsgs)); - } - - public Attempt> CopyContentType(IContentType toCopy, int containerId) - { - var evtMsgs = EventMessagesFactory.Get(); - - IContentType copy; - using (var uow = UowProvider.CreateUnitOfWork()) - { - var containerRepository = uow.CreateRepository(); - var repository = uow.CreateRepository(); - try - { - if (containerId > 0) - { - var container = containerRepository.Get(containerId); - if (container == null) - throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); - } - var alias = repository.GetUniqueAlias(toCopy.Alias); - copy = toCopy.DeepCloneWithResetIdentities(alias); - copy.Name = copy.Name + " (copy)"; // might not be unique - - // if it has a parent, and the parent is a content type, unplug composition - // all other compositions remain in place in the copied content type - if (copy.ParentId > 0) - { - var parent = repository.Get(copy.ParentId); - if (parent != null) - copy.RemoveContentType(parent.Alias); - } - - copy.ParentId = containerId; - repository.AddOrUpdate(copy); - } - catch (DataOperationException ex) - { - return Attempt.Fail(new OperationStatus(null, ex.Operation, evtMsgs)); - } - uow.Complete(); - } - - return Attempt.Succeed(new OperationStatus(copy, MoveOperationStatusType.Success, evtMsgs)); - } - - /// - /// Saves a single object - /// - /// to save - /// Optional Id of the user saving the MediaType - public void Save(IMediaType mediaType, int userId = 0) - { - if (SavingMediaType.IsRaisedEventCancelled(new SaveEventArgs(mediaType), this)) - return; - - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - ValidateLocked(mediaType); // throws if invalid - mediaType.CreatorId = userId; - repository.AddOrUpdate(mediaType); - uow.Complete(); - - } - - UpdateContentXmlStructure(mediaType); - } - - SavedMediaType.RaiseEvent(new SaveEventArgs(mediaType, false), this); - Audit(AuditType.Save, string.Format("Save MediaType performed by user"), userId, mediaType.Id); - } - - /// - /// Saves a collection of objects - /// - /// Collection of to save - /// Optional Id of the user savging the MediaTypes - public void Save(IEnumerable mediaTypes, int userId = 0) - { - var asArray = mediaTypes.ToArray(); - - if (SavingMediaType.IsRaisedEventCancelled(new SaveEventArgs(asArray), this)) - return; - - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - // all-or-nothing, validate them all first - foreach (var mediaType in asArray) - { - ValidateLocked(mediaType); // throws if invalid - } - foreach (var mediaType in asArray) - { - mediaType.CreatorId = userId; - repository.AddOrUpdate(mediaType); - } - - //save it all in one go - uow.Complete(); - } - - UpdateContentXmlStructure(asArray.Cast().ToArray()); - } - - SavedMediaType.RaiseEvent(new SaveEventArgs(asArray, false), this); - Audit(AuditType.Save, string.Format("Save MediaTypes performed by user"), userId, -1); - } - - /// - /// Deletes a single object - /// - /// to delete - /// Optional Id of the user deleting the MediaType - /// Deleting a will delete all the objects based on this - public void Delete(IMediaType mediaType, int userId = 0) - { - if (DeletingMediaType.IsRaisedEventCancelled(new DeleteEventArgs(mediaType), this)) - return; - using (new WriteLock(Locker)) - { - _mediaService.DeleteMediaOfType(mediaType.Id, userId); - - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - - repository.Delete(mediaType); - uow.Complete(); - - DeletedMediaType.RaiseEvent(new DeleteEventArgs(mediaType, false), this); - } - - Audit(AuditType.Delete, string.Format("Delete MediaType performed by user"), userId, mediaType.Id); - } - } - - /// - /// Deletes a collection of objects - /// - /// Collection of to delete - /// - /// Deleting a will delete all the objects based on this - public void Delete(IEnumerable mediaTypes, int userId = 0) - { - var asArray = mediaTypes.ToArray(); - - if (DeletingMediaType.IsRaisedEventCancelled(new DeleteEventArgs(asArray), this)) - return; - using (new WriteLock(Locker)) - { - foreach (var mediaType in asArray) - { - _mediaService.DeleteMediaOfType(mediaType.Id); - } - - using (var uow = UowProvider.CreateUnitOfWork()) - { - var repository = uow.CreateRepository(); - foreach (var mediaType in asArray) - { - repository.Delete(mediaType); - } - uow.Complete(); - - DeletedMediaType.RaiseEvent(new DeleteEventArgs(asArray, false), this); - } - - Audit(AuditType.Delete, string.Format("Delete MediaTypes performed by user"), userId, -1); + return aliases; } } @@ -1333,14 +115,14 @@ namespace Umbraco.Core.Services { var strictSchemaBuilder = new StringBuilder(); - var contentTypes = GetAllContentTypes(); + var contentTypes = GetAll(new int[0]); foreach (ContentType contentType in contentTypes) { string safeAlias = contentType.Alias.ToSafeAlias(); if (safeAlias != null) { - strictSchemaBuilder.AppendLine(String.Format("", safeAlias)); - strictSchemaBuilder.AppendLine(String.Format("", safeAlias)); + strictSchemaBuilder.AppendLine($""); + strictSchemaBuilder.AppendLine($""); } } @@ -1354,88 +136,21 @@ namespace Umbraco.Core.Services return dtd.ToString(); } - private void Audit(AuditType type, string message, int userId, int objectId) + protected override void UpdateContentXmlStructure(params IContentTypeBase[] contentTypes) { - using (var uow = UowProvider.CreateUnitOfWork()) + var toUpdate = GetContentTypesForXmlUpdates(contentTypes).ToArray(); + if (toUpdate.Any() == false) return; + + var contentService = _contentService as ContentService; + if (contentService != null) { - var repo = uow.CreateRepository(); - repo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); - uow.Complete(); + contentService.RePublishAll(toUpdate.Select(x => x.Id).ToArray()); + } + else + { + //this should never occur, the content service should always be typed but we'll check anyways. + _contentService.RePublishAll(); } } - - #region Event Handlers - - 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 - /// - public static event TypedEventHandler> DeletedContentType; - - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> DeletingMediaType; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> DeletedMediaType; - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> SavingContentType; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> SavedContentType; - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> SavingMediaType; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> SavedMediaType; - - /// - /// Occurs before Move - /// - public static event TypedEventHandler> MovingMediaType; - - /// - /// Occurs after Move - /// - public static event TypedEventHandler> MovedMediaType; - - /// - /// Occurs before Move - /// - public static event TypedEventHandler> MovingContentType; - - /// - /// Occurs after Move - /// - public static event TypedEventHandler> MovedContentType; - - #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBase.cs b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs index cf6a542fa0..93b323b1a7 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceBase.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs @@ -1,21 +1,23 @@ +using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core.Events; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Services { - public class ContentTypeServiceBase : RepositoryService + internal abstract class ContentTypeServiceBase : RepositoryService { - public ContentTypeServiceBase(IDatabaseUnitOfWorkProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory) + protected ContentTypeServiceBase(IDatabaseUnitOfWorkProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory) : base(provider, logger, eventMessagesFactory) - { - } - + { } + /// /// This is called after an content type is saved and is used to update the content xml structures in the database /// if they are required to be updated. @@ -33,7 +35,7 @@ namespace Umbraco.Core.Services // - a content type changes it's alias OR // - if a content type has it's property removed OR // - if a content type has a property whose alias has changed - //here we need to check if the alias of the content type changed or if one of the properties was removed. + //here we need to check if the alias of the content type changed or if one of the properties was removed. var dirty = contentType as IRememberBeingDirty; if (dirty == null) continue; @@ -50,18 +52,18 @@ namespace Umbraco.Core.Services && (dirty.WasPropertyDirty("Alias") || dirty.WasPropertyDirty("HasPropertyTypeBeenRemoved") || hasAnyPropertiesChangedAlias)) { //If the alias was changed then we only need to update the xml structures for content of the current content type. - //If a property was deleted or a property alias was changed then we need to update the xml structures for any + //If a property was deleted or a property alias was changed then we need to update the xml structures for any // content of the current content type and any of the content type's child content types. if (dirty.WasPropertyDirty("Alias") && dirty.WasPropertyDirty("HasPropertyTypeBeenRemoved") == false && hasAnyPropertiesChangedAlias == false) { - //if only the alias changed then only update the current content type + //if only the alias changed then only update the current content type toUpdate.Add(contentType); } else { //if a property was deleted or alias changed, then update all content of the current content type - // and all of it's desscendant doc types. + // and all of it's desscendant doc types. toUpdate.AddRange(contentType.DescendantsAndSelf()); } } @@ -71,4 +73,887 @@ namespace Umbraco.Core.Services } } + + internal abstract class ContentTypeServiceBase : ContentTypeServiceBase + where TItem : class, IContentTypeComposition + where TService : class, IContentTypeServiceBase + { + protected ContentTypeServiceBase(IDatabaseUnitOfWorkProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(provider, logger, eventMessagesFactory) + { + _this = this as TService; + if (_this == null) throw new Exception("Oops."); + } + + private readonly TService _this; + + public static event TypedEventHandler> Saving; + public static event TypedEventHandler> Saved; + + protected void OnSaving(SaveEventArgs args) + { + Saving.RaiseEvent(args, _this); + } + + protected bool OnSavingCancelled(SaveEventArgs args) + { + return Saving.IsRaisedEventCancelled(args, _this); + } + + protected void OnSaved(SaveEventArgs args) + { + Saved.RaiseEvent(args, _this); + } + + public static event TypedEventHandler> Deleting; + public static event TypedEventHandler> Deleted; + + protected void OnDeleting(DeleteEventArgs args) + { + Deleting.RaiseEvent(args, _this); + } + + protected bool OnDeletingCancelled(DeleteEventArgs args) + { + return Deleting.IsRaisedEventCancelled(args, _this); + } + + protected void OnDeleted(DeleteEventArgs args) + { + Deleted.RaiseEvent(args, (TService)(object)this); + } + + public static event TypedEventHandler> Moving; + public static event TypedEventHandler> Moved; + + protected void OnMoving(MoveEventArgs args) + { + Moving.RaiseEvent(args, _this); + } + + protected bool OnMovingCancelled(MoveEventArgs args) + { + return Moving.IsRaisedEventCancelled(args, _this); + } + + protected void OnMoved(MoveEventArgs args) + { + Moved.RaiseEvent(args, _this); + } + + public static event TypedEventHandler> SavingContainer; + public static event TypedEventHandler> SavedContainer; + + protected void OnSavingContainer(SaveEventArgs args) + { + SavingContainer.RaiseEvent(args, _this); + } + + protected bool OnSavingContainerCancelled(SaveEventArgs args) + { + return SavingContainer.IsRaisedEventCancelled(args, _this); + } + + protected void OnSavedContainer(SaveEventArgs args) + { + SavedContainer.RaiseEvent(args, _this); + } + + public static event TypedEventHandler> DeletingContainer; + public static event TypedEventHandler> DeletedContainer; + + protected void OnDeletingContainer(DeleteEventArgs args) + { + DeletingContainer.RaiseEvent(args, _this); + } + + protected bool OnDeletingContainerCancelled(DeleteEventArgs args) + { + return DeletingContainer.IsRaisedEventCancelled(args, _this); + } + + protected void OnDeletedContainer(DeleteEventArgs args) + { + DeletedContainer.RaiseEvent(args, _this); + } + + // for later usage + //public static event TypedEventHandler TxRefreshed; + + //protected void OnTxRefreshed(Change.EventArgs args) + //{ + // TxRefreshed.RaiseEvent(args, this); + //} + } + + internal abstract class ContentTypeServiceBase : ContentTypeServiceBase, IContentTypeServiceBase + where TRepository : IContentTypeRepositoryBase + where TItem : class, IContentTypeComposition + where TService : class, IContentTypeServiceBase + { + protected ContentTypeServiceBase(IDatabaseUnitOfWorkProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(provider, logger, eventMessagesFactory) + { } + + protected abstract int[] WriteLockIds { get; } + protected abstract int[] ReadLockIds { get; } + + #region Validation + + public Attempt ValidateComposition(TItem compo) + { + try + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + uow.ReadLock(ReadLockIds); + ValidateLocked(repo, compo); + uow.Complete(); + } + return Attempt.Succeed(); + } + catch (InvalidCompositionException ex) + { + return Attempt.Fail(ex.PropertyTypeAliases, ex); + } + } + + protected void ValidateLocked(TRepository repository, TItem compositionContentType) + { + // performs business-level validation of the composition + // should ensure that it is absolutely safe to save the composition + + // eg maybe a property has been added, with an alias that's OK (no conflict with ancestors) + // but that cannot be used (conflict with descendants) + + var allContentTypes = repository.GetAll(new int[0]).Cast().ToArray(); + + var compositionAliases = compositionContentType.CompositionAliases(); + var compositions = allContentTypes.Where(x => compositionAliases.Any(y => x.Alias.Equals(y))); + var propertyTypeAliases = compositionContentType.PropertyTypes.Select(x => x.Alias.ToLowerInvariant()).ToArray(); + var indirectReferences = allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == compositionContentType.Id)); + var comparer = new DelegateEqualityComparer((x, y) => x.Id == y.Id, x => x.Id); + var dependencies = new HashSet(compositions, comparer); + var stack = new Stack(); + indirectReferences.ForEach(stack.Push); // push indirect references to a stack, so we can add recursively + while (stack.Count > 0) + { + var indirectReference = stack.Pop(); + dependencies.Add(indirectReference); + // get all compositions for the current indirect reference + var directReferences = indirectReference.ContentTypeComposition; + + foreach (var directReference in directReferences) + { + if (directReference.Id == compositionContentType.Id || directReference.Alias.Equals(compositionContentType.Alias)) continue; + dependencies.Add(directReference); + // a direct reference has compositions of its own - these also need to be taken into account + var directReferenceGraph = directReference.CompositionAliases(); + allContentTypes.Where(x => directReferenceGraph.Any(y => x.Alias.Equals(y, StringComparison.InvariantCultureIgnoreCase))).ForEach(c => dependencies.Add(c)); + } + // recursive lookup of indirect references + allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == indirectReference.Id)).ForEach(stack.Push); + } + + foreach (var dependency in dependencies) + { + if (dependency.Id == compositionContentType.Id) continue; + var contentTypeDependency = allContentTypes.FirstOrDefault(x => x.Alias.Equals(dependency.Alias, StringComparison.InvariantCultureIgnoreCase)); + if (contentTypeDependency == null) continue; + var intersect = contentTypeDependency.PropertyTypes.Select(x => x.Alias.ToLowerInvariant()).Intersect(propertyTypeAliases).ToArray(); + if (intersect.Length == 0) continue; + + throw new InvalidCompositionException(compositionContentType.Alias, intersect.ToArray()); + } + } + + #endregion + + #region Composition + #endregion + + #region Get, Has, Is, Count + + public TItem Get(int id) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + uow.ReadLock(ReadLockIds); + var item = repo.Get(id); + uow.Complete(); + return item; + } + } + + public TItem Get(string alias) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + uow.ReadLock(ReadLockIds); + var item = repo.Get(alias); + uow.Complete(); + return item; + } + } + + public TItem Get(Guid id) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + uow.ReadLock(ReadLockIds); + var item = repo.Get(id); + uow.Complete(); + return item; + } + } + + public IEnumerable GetAll(params int[] ids) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + uow.ReadLock(ReadLockIds); + var items = repo.GetAll(ids); + uow.Complete(); + return items; + } + } + + public IEnumerable GetAll(params Guid[] ids) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + uow.ReadLock(ReadLockIds); + // IReadRepository is explicitely implemented, need to cast the repo + var items = ((IReadRepository) repo).GetAll(ids); + uow.Complete(); + return items; + } + } + + public IEnumerable GetChildren(int id) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + uow.ReadLock(ReadLockIds); + var query = repo.Query.Where(x => x.ParentId == id); + var items = repo.GetByQuery(query); + uow.Complete(); + return items; + } + } + + public IEnumerable GetChildren(Guid id) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + uow.ReadLock(ReadLockIds); + var found = Get(id); + if (found == null) return Enumerable.Empty(); + var query = repo.Query.Where(x => x.ParentId == found.Id); + var items = repo.GetByQuery(query); + uow.Complete(); + return items; + } + } + + public bool HasChildren(int id) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + uow.ReadLock(ReadLockIds); + var query = repo.Query.Where(x => x.ParentId == id); + var count = repo.Count(query); + uow.Complete(); + return count > 0; + } + } + + public bool HasChildren(Guid id) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + uow.ReadLock(ReadLockIds); + var found = Get(id); + if (found == null) return false; + var query = repo.Query.Where(x => x.ParentId == found.Id); + var count = repo.Count(query); + uow.Complete(); + return count > 0; + } + } + + public IEnumerable GetDescendants(int id, bool andSelf) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + uow.ReadLock(ReadLockIds); + + var descendants = new List(); + if (andSelf) descendants.Add(repo.Get(id)); + var ids = new Stack(); + ids.Push(id); + + while (ids.Count > 0) + { + var i = ids.Pop(); + var query = repo.Query.Where(x => x.ParentId == i); + var result = repo.GetByQuery(query).ToArray(); + + foreach (var c in result) + { + descendants.Add(c); + ids.Push(c.Id); + } + } + + var descendantsA = descendants.ToArray(); + uow.Complete(); + return descendantsA; + } + } + + public IEnumerable GetComposedOf(int id) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + uow.ReadLock(ReadLockIds); + + // hash set handles duplicates + var composed = new HashSet(new DelegateEqualityComparer( + (x, y) => x.Id == y.Id, + x => x.Id.GetHashCode())); + + var ids = new Stack(); + ids.Push(id); + + while (ids.Count > 0) + { + var i = ids.Pop(); + var result = repo.GetTypesDirectlyComposedOf(i).ToArray(); + + foreach (var c in result) + { + composed.Add(c); + ids.Push(c.Id); + } + } + + var composedA = composed.ToArray(); + uow.Complete(); + return composedA; + } + } + + public int Count() + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + uow.ReadLock(ReadLockIds); + var count = repo.Count(repo.Query); + uow.Complete(); + return count; + } + } + + #endregion + + #region Save + + public void Save(TItem item, int userId = 0) + { + if (OnSavingCancelled(new SaveEventArgs(item))) + return; + + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + uow.WriteLock(WriteLockIds); + + // validate the DAG transform, within the lock + ValidateLocked(repo, item); // throws if invalid + + item.CreatorId = userId; + repo.AddOrUpdate(item); // also updates content/media/member items + uow.Flush(); // to db but no commit yet + + // ... + + uow.Complete(); + } + + // todo: should use TxRefreshed event within the transaction instead, see CC branch + UpdateContentXmlStructure(item); + + OnSaved(new SaveEventArgs(item, false)); + Audit(AuditType.Save, $"Save {typeof(TItem).Name} performed by user", userId, item.Id); + } + + public void Save(IEnumerable items, int userId = 0) + { + var itemsA = items.ToArray(); + + if (OnSavingCancelled(new SaveEventArgs(itemsA))) + return; + + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + uow.WriteLock(WriteLockIds); + + // all-or-nothing, validate them all first + foreach (var contentType in itemsA) + { + ValidateLocked(repo, contentType); // throws if invalid + } + foreach (var contentType in itemsA) + { + contentType.CreatorId = userId; + repo.AddOrUpdate(contentType); + } + + //save it all in one go + uow.Complete(); + } + + // todo: should use TxRefreshed event within the transaction instead, see CC branch + UpdateContentXmlStructure(itemsA.Cast().ToArray()); + + OnSaved(new SaveEventArgs(itemsA, false)); + Audit(AuditType.Save, $"Save {typeof(TItem).Name} performed by user", userId, -1); + } + + #endregion + + #region Delete + + public void Delete(TItem item, int userId = 0) + { + if (OnDeletingCancelled(new DeleteEventArgs(item))) + return; + + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + uow.WriteLock(WriteLockIds); + + // all descendants are going to be deleted + var descendantsAndSelf = item.DescendantsAndSelf() + .ToArray(); + + // delete content + DeleteItemsOfTypes(descendantsAndSelf.Select(x => x.Id)); + + // finally delete the content type + // - recursively deletes all descendants + // - deletes all associated property data + // (contents of any descendant type have been deleted but + // contents of any composed (impacted) type remain but + // need to have their property data cleared) + repo.Delete(item); + uow.Flush(); // to db but no commit yet + + //... + + uow.Complete(); + } + + OnDeleted(new DeleteEventArgs(item, false)); + Audit(AuditType.Delete, $"Delete {typeof(TItem).Name} performed by user", userId, item.Id); + } + + public void Delete(IEnumerable items, int userId = 0) + { + var itemsA = items.ToArray(); + + if (OnDeletingCancelled(new DeleteEventArgs(itemsA))) + return; + + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + uow.WriteLock(WriteLockIds); + + // all descendants are going to be deleted + var allDescendantsAndSelf = itemsA.SelectMany(xx => xx.DescendantsAndSelf()) + .Distinct() + .ToArray(); + + // delete content + DeleteItemsOfTypes(allDescendantsAndSelf.Select(x => x.Id)); + + // finally delete the content types + // (see notes in overload) + foreach (var item in itemsA) + repo.Delete(item); + + uow.Flush(); // to db but no commit yet + + // ... + + uow.Complete(); + + } + + OnDeleted(new DeleteEventArgs(itemsA, false)); + Audit(AuditType.Delete, $"Delete {typeof(TItem).Name} performed by user", userId, -1); + } + + protected abstract void DeleteItemsOfTypes(IEnumerable typeIds); + + #endregion + + #region Copy + + public TItem Copy(TItem original, string alias, string name, int parentId = -1) + { + TItem parent = null; + if (parentId > 0) + { + parent = Get(parentId); + if (parent == null) + { + throw new InvalidOperationException("Could not find parent with id " + parentId); + } + } + return Copy(original, alias, name, parent); + } + + public TItem Copy(TItem original, string alias, string name, TItem parent) + { + Mandate.ParameterNotNull(original, "original"); + Mandate.ParameterNotNullOrEmpty(alias, "alias"); + + if (parent != null) + Mandate.That(parent.HasIdentity, () => new InvalidOperationException("The parent must have an identity")); + + // this is illegal + //var originalb = (ContentTypeCompositionBase)original; + // but we *know* it has to be a ContentTypeCompositionBase anyways + var originalb = (ContentTypeCompositionBase) (object) original; + var clone = (TItem) originalb.DeepCloneWithResetIdentities(alias); + + clone.Name = name; + + //remove all composition that is not it's current alias + var compositionAliases = clone.CompositionAliases().Except(new[] { alias }).ToList(); + foreach (var a in compositionAliases) + { + clone.RemoveContentType(a); + } + + //if a parent is specified set it's composition and parent + if (parent != null) + { + //add a new parent composition + clone.AddContentType(parent); + clone.ParentId = parent.Id; + } + else + { + //set to root + clone.ParentId = -1; + } + + Save(clone); + return clone; + } + + public Attempt> Copy(TItem copying, int containerId) + { + var evtMsgs = EventMessagesFactory.Get(); + + TItem copy; + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + uow.WriteLock(WriteLockIds); + + var containerRepository = uow.CreateContainerRepository(ContainerObjectType); + try + { + if (containerId > 0) + { + var container = containerRepository.Get(containerId); + if (container == null) + throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); // causes rollback + } + var alias = repo.GetUniqueAlias(copying.Alias); + + // this is illegal + //var copyingb = (ContentTypeCompositionBase) copying; + // but we *know* it has to be a ContentTypeCompositionBase anyways + var copyingb = (ContentTypeCompositionBase) (object)copying; + copy = (TItem) copyingb.DeepCloneWithResetIdentities(alias); + + copy.Name = copy.Name + " (copy)"; // might not be unique + + // if it has a parent, and the parent is a content type, unplug composition + // all other compositions remain in place in the copied content type + if (copy.ParentId > 0) + { + var parent = repo.Get(copy.ParentId); + if (parent != null) + copy.RemoveContentType(parent.Alias); + } + + copy.ParentId = containerId; + repo.AddOrUpdate(copy); + uow.Complete(); + } + catch (DataOperationException ex) + { + return OperationStatus.Attempt.Fail(ex.Operation, evtMsgs); // causes rollback + } + } + + return OperationStatus.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs, copy); + } + + #endregion + + #region Move + + public Attempt> Move(TItem moving, int containerId) + { + var evtMsgs = EventMessagesFactory.Get(); + + if (OnMovingCancelled(new MoveEventArgs(evtMsgs, new MoveEventInfo(moving, moving.Path, containerId)))) + return OperationStatus.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs); + + var moveInfo = new List>(); + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(WriteLockIds); // also for containers + + var repo = uow.CreateRepository(); + var containerRepo = uow.CreateRepository(); + + try + { + EntityContainer container = null; + if (containerId > 0) + { + container = containerRepo.Get(containerId); + if (container == null) + throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); // causes rollback + } + moveInfo.AddRange(repo.Move(moving, container)); + uow.Complete(); + } + catch (DataOperationException ex) + { + return OperationStatus.Attempt.Fail(ex.Operation, evtMsgs); // causes rollback + } + } + + OnMoved(new MoveEventArgs(false, evtMsgs, moveInfo.ToArray())); + + return OperationStatus.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs); + } + + #endregion + + #region Containers + + protected abstract Guid ContainedObjectType { get; } + + protected Guid ContainerObjectType => EntityContainer.GetContainerObjectType(ContainedObjectType); + + public Attempt> CreateContainer(int parentId, string name, int userId = 0) + { + var evtMsgs = EventMessagesFactory.Get(); + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(WriteLockIds); // also for containers + + var repo = uow.CreateContainerRepository(ContainerObjectType); + try + { + var container = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid) + { + Name = name, + ParentId = parentId, + CreatorId = userId + }; + + if (OnSavingContainerCancelled(new SaveEventArgs(container, evtMsgs))) + return OperationStatus.Attempt.Cancel(evtMsgs, container); // causes rollback + + repo.AddOrUpdate(container); + uow.Complete(); + + OnSavedContainer(new SaveEventArgs(container, evtMsgs)); + //TODO: Audit trail ? + + return OperationStatus.Attempt.Succeed(evtMsgs, container); + } + catch (Exception ex) + { + return OperationStatus.Attempt.Fail(OperationStatusType.FailedCancelledByEvent, evtMsgs, ex); + } + } + } + + public Attempt SaveContainer(EntityContainer container, int userId = 0) + { + var evtMsgs = EventMessagesFactory.Get(); + + var containerObjectType = ContainerObjectType; + if (container.ContainedObjectType != containerObjectType) + { + var ex = new InvalidOperationException("Not a container of the proper type."); + return OperationStatus.Attempt.Fail(evtMsgs, ex); + } + + if (container.HasIdentity && container.IsPropertyDirty("ParentId")) + { + var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); + return OperationStatus.Attempt.Fail(evtMsgs, ex); + } + + if (OnSavingContainerCancelled(new SaveEventArgs(container, evtMsgs))) + return OperationStatus.Attempt.Cancel(evtMsgs); + + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(WriteLockIds); // also for containers + + var repo = uow.CreateContainerRepository(containerObjectType); + repo.AddOrUpdate(container); + uow.Complete(); + } + + OnSavedContainer(new SaveEventArgs(container, evtMsgs)); + + //TODO: Audit trail ? + + return OperationStatus.Attempt.Succeed(evtMsgs); + } + + public EntityContainer GetContainer(int containerId) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(ReadLockIds); // also for containers + + var repo = uow.CreateContainerRepository(ContainerObjectType); + var container = repo.Get(containerId); + uow.Complete(); + return container; + } + } + + public EntityContainer GetContainer(Guid containerId) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(ReadLockIds); // also for containers + + var repo = uow.CreateContainerRepository(ContainerObjectType); + var container = ((EntityContainerRepository) repo).Get(containerId); + uow.Complete(); + return container; + } + } + + public IEnumerable GetContainers(int[] containerIds) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(ReadLockIds); // also for containers + + var repo = uow.CreateContainerRepository(ContainerObjectType); + var containers = repo.GetAll(containerIds); + uow.Complete(); + return containers; + } + } + + public IEnumerable GetContainers(TItem item) + { + var ancestorIds = item.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => + { + var asInt = x.TryConvertTo(); + return asInt ? asInt.Result : int.MinValue; + }) + .Where(x => x != int.MinValue && x != item.Id) + .ToArray(); + + return GetContainers(ancestorIds); + } + + public IEnumerable GetContainers(string name, int level) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.ReadLock(ReadLockIds); // also for containers + + var repo = uow.CreateContainerRepository(ContainerObjectType); + var containers = ((EntityContainerRepository) repo).Get(name, level); + uow.Complete(); + return containers; + } + } + + // fixme - what happens if deleting a non-empty container? + public Attempt DeleteContainer(int containerId, int userId = 0) + { + var evtMsgs = EventMessagesFactory.Get(); + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(WriteLockIds); // also for containers + + var repo = uow.CreateContainerRepository(ContainerObjectType); + var container = repo.Get(containerId); + if (container == null) return OperationStatus.Attempt.NoOperation(evtMsgs); + + if (OnDeletingContainerCancelled(new DeleteEventArgs(container, evtMsgs))) + return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs)); // causes rollback + + repo.Delete(container); + uow.Complete(); + + OnDeletedContainer(new DeleteEventArgs(container, evtMsgs)); + + return OperationStatus.Attempt.Succeed(evtMsgs); + //TODO: Audit trail ? + } + } + + #endregion + + #region Audit + + private void Audit(AuditType type, string message, int userId, int objectId) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + var repo = uow.CreateRepository(); + repo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); + uow.Complete(); + } + } + + #endregion + + #region Xml - Should Move! + + protected abstract void UpdateContentXmlStructure(params IContentTypeBase[] contentTypes); + + #endregion + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index f17a245d38..754f26762e 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -28,7 +28,7 @@ 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(); using (var uow = UowProvider.CreateUnitOfWork()) @@ -43,12 +43,8 @@ namespace Umbraco.Core.Services CreatorId = userId }; - if (SavingContainer.IsRaisedEventCancelled( - new SaveEventArgs(container, evtMsgs), - this)) - { - return Attempt.Fail(new OperationStatus(container, OperationStatusType.FailedCancelledByEvent, evtMsgs)); - } + if (SavingContainer.IsRaisedEventCancelled(new SaveEventArgs(container, evtMsgs), this)) + return OperationStatus.Attempt.Cancel(evtMsgs, container); // causes rollback repo.AddOrUpdate(container); uow.Complete(); @@ -56,11 +52,11 @@ namespace Umbraco.Core.Services SavedContainer.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); //TODO: Audit trail ? - return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); + return OperationStatus.Attempt.Succeed(evtMsgs, container); } catch (Exception ex) { - return Attempt.Fail(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); + return OperationStatus.Attempt.Fail(evtMsgs, ex); } } } @@ -71,6 +67,7 @@ namespace Umbraco.Core.Services { var repo = uow.CreateRepository(); var container = repo.Get(containerId); + uow.Complete(); return container; } } @@ -81,6 +78,7 @@ namespace Umbraco.Core.Services { var repo = uow.CreateRepository(); var container = ((EntityContainerRepository)repo).Get(containerId); + uow.Complete(); return container; } } @@ -90,7 +88,9 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repo = uow.CreateRepository(); - return ((EntityContainerRepository)repo).Get(name, level); + var containers = ((EntityContainerRepository)repo).Get(name, level); + uow.Complete(); + return containers; } } @@ -114,7 +114,9 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repo = uow.CreateRepository(); - return repo.GetAll(containerIds); + var containers = repo.GetAll(containerIds); + uow.Complete(); + return containers; } } @@ -125,20 +127,20 @@ namespace Umbraco.Core.Services if (container.ContainedObjectType != Constants.ObjectTypes.DataTypeGuid) { var ex = new InvalidOperationException("Not a " + Constants.ObjectTypes.DataTypeGuid + " container."); - return OperationStatus.Exception(evtMsgs, ex); + return OperationStatus.Attempt.Fail(evtMsgs, ex); } if (container.HasIdentity && container.IsPropertyDirty("ParentId")) { var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); - return OperationStatus.Exception(evtMsgs, ex); + return OperationStatus.Attempt.Fail(evtMsgs, ex); } if (SavingContainer.IsRaisedEventCancelled( new SaveEventArgs(container, evtMsgs), this)) { - return OperationStatus.Cancelled(evtMsgs); + return OperationStatus.Attempt.Cancel(evtMsgs); } using (var uow = UowProvider.CreateUnitOfWork()) @@ -152,7 +154,7 @@ namespace Umbraco.Core.Services //TODO: Audit trail ? - return OperationStatus.Success(evtMsgs); + return OperationStatus.Attempt.Succeed(evtMsgs); } public Attempt DeleteContainer(int containerId, int userId = 0) @@ -162,21 +164,17 @@ namespace Umbraco.Core.Services { var repo = uow.CreateRepository(); var container = repo.Get(containerId); - if (container == null) return OperationStatus.NoOperation(evtMsgs); + if (container == null) return OperationStatus.Attempt.NoOperation(evtMsgs); - if (DeletingContainer.IsRaisedEventCancelled( - new DeleteEventArgs(container, evtMsgs), - this)) - { - return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs)); - } + if (DeletingContainer.IsRaisedEventCancelled(new DeleteEventArgs(container, evtMsgs), this)) + return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs)); // causes rollback repo.Delete(container); uow.Complete(); DeletedContainer.RaiseEvent(new DeleteEventArgs(container, evtMsgs), this); - return OperationStatus.Success(evtMsgs); + return OperationStatus.Attempt.Succeed(evtMsgs); //TODO: Audit trail ? } } @@ -193,7 +191,9 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repository = uow.CreateRepository(); - return repository.GetByQuery(repository.Query.Where(x => x.Name == name)).FirstOrDefault(); + var def = repository.GetByQuery(repository.Query.Where(x => x.Name == name)).FirstOrDefault(); + uow.Complete(); + return def; } } @@ -207,7 +207,9 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repository = uow.CreateRepository(); - return repository.Get(id); + var def = repository.Get(id); + uow.Complete(); + return def; } } @@ -222,8 +224,9 @@ namespace Umbraco.Core.Services { var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.Key == id); - var definitions = repository.GetByQuery(query); - return definitions.FirstOrDefault(); + var definition = repository.GetByQuery(query).FirstOrDefault(); + uow.Complete(); + return definition; } } @@ -239,6 +242,7 @@ namespace Umbraco.Core.Services var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.PropertyEditorAlias == propertyEditorAlias); var definitions = repository.GetByQuery(query); + uow.Complete(); return definitions; } } @@ -253,7 +257,9 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repository = uow.CreateRepository(); - return repository.GetAll(ids); + var defs = repository.GetAll(ids); + uow.Complete(); + return defs; } } @@ -272,6 +278,7 @@ namespace Umbraco.Core.Services var list = collection.FormatAsDictionary() .Select(x => x.Value.Value) .ToList(); + uow.Complete(); return list; } } @@ -286,7 +293,9 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repository = uow.CreateRepository(); - return repository.GetPreValuesCollectionByDataTypeId(id); + var vals = repository.GetPreValuesCollectionByDataTypeId(id); + uow.Complete(); + return vals; } } @@ -300,7 +309,9 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repository = uow.CreateRepository(); - return repository.GetPreValueAsString(id); + var val = repository.GetPreValueAsString(id); + uow.Complete(); + return val; } } @@ -312,9 +323,7 @@ namespace Umbraco.Core.Services new MoveEventArgs(evtMsgs, new MoveEventInfo(toMove, toMove.Path, parentId)), this)) { - return Attempt.Fail( - new OperationStatus( - MoveOperationStatusType.FailedCancelledByEvent, evtMsgs)); + return OperationStatus.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs); } var moveInfo = new List>(); @@ -330,22 +339,20 @@ namespace Umbraco.Core.Services { container = containerRepository.Get(parentId); if (container == null) - throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); + throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); // causes rollback } moveInfo.AddRange(repository.Move(toMove, container)); + uow.Complete(); } catch (DataOperationException ex) { - return Attempt.Fail( - new OperationStatus(ex.Operation, evtMsgs)); + return OperationStatus.Attempt.Fail(ex.Operation, evtMsgs); } - uow.Complete(); } Moved.RaiseEvent(new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()), this); - return Attempt.Succeed( - new OperationStatus(MoveOperationStatusType.Success, evtMsgs)); + return OperationStatus.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs); } /// @@ -424,26 +431,21 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { - using (var transaction = uow.Database.GetTransaction()) + var sortOrderObj = uow.Database.ExecuteScalar( + "SELECT max(sortorder) FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId", new { DataTypeId = dataTypeId }); + + int sortOrder; + if (sortOrderObj == null || int.TryParse(sortOrderObj.ToString(), out sortOrder) == false) + sortOrder = 1; + + foreach (var value in values) { - var sortOrderObj = - uow.Database.ExecuteScalar( - "SELECT max(sortorder) FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId", new { DataTypeId = dataTypeId }); - int sortOrder; - if (sortOrderObj == null || int.TryParse(sortOrderObj.ToString(), out sortOrder) == false) - { - sortOrder = 1; - } - - foreach (var value in values) - { - var dto = new DataTypePreValueDto { DataTypeNodeId = dataTypeId, Value = value, SortOrder = sortOrder }; - uow.Database.Insert(dto); - sortOrder++; - } - - transaction.Complete(); + var dto = new DataTypePreValueDto { DataTypeNodeId = dataTypeId, Value = value, SortOrder = sortOrder }; + uow.Database.Insert(dto); + sortOrder++; } + + uow.Complete(); } } diff --git a/src/Umbraco.Core/Services/DomainService.cs b/src/Umbraco.Core/Services/DomainService.cs index 9100fb9511..ca0184b259 100644 --- a/src/Umbraco.Core/Services/DomainService.cs +++ b/src/Umbraco.Core/Services/DomainService.cs @@ -23,7 +23,9 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repo = uow.CreateRepository(); - return repo.Exists(domainName); + var exists = repo.Exists(domainName); + uow.Complete(); + return exists; } } @@ -34,7 +36,7 @@ namespace Umbraco.Core.Services new DeleteEventArgs(domain, evtMsgs), this)) { - return OperationStatus.Cancelled(evtMsgs); + return OperationStatus.Attempt.Cancel(evtMsgs); } using (var uow = UowProvider.CreateUnitOfWork()) @@ -46,7 +48,7 @@ namespace Umbraco.Core.Services var args = new DeleteEventArgs(domain, false, evtMsgs); Deleted.RaiseEvent(args, this); - return OperationStatus.Success(evtMsgs); + return OperationStatus.Attempt.Succeed(evtMsgs); } public IDomain GetByName(string name) @@ -54,7 +56,9 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repository = uow.CreateRepository(); - return repository.GetByName(name); + var domain = repository.GetByName(name); + uow.Complete(); + return domain; } } @@ -63,7 +67,9 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repo = uow.CreateRepository(); - return repo.Get(id); + var domain = repo.Get(id); + uow.Complete(); + return domain; } } @@ -72,7 +78,9 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repo = uow.CreateRepository(); - return repo.GetAll(includeWildcards); + var domains = repo.GetAll(includeWildcards); + uow.Complete(); + return domains; } } @@ -81,7 +89,9 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repo = uow.CreateRepository(); - return repo.GetAssignedDomains(contentId, includeWildcards); + var domains = repo.GetAssignedDomains(contentId, includeWildcards); + uow.Complete(); + return domains; } } @@ -92,7 +102,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(domainEntity, evtMsgs), this)) { - return OperationStatus.Cancelled(evtMsgs); + return OperationStatus.Attempt.Cancel(evtMsgs); } using (var uow = UowProvider.CreateUnitOfWork()) @@ -103,7 +113,7 @@ namespace Umbraco.Core.Services } Saved.RaiseEvent(new SaveEventArgs(domainEntity, false, evtMsgs), this); - return OperationStatus.Success(evtMsgs); + return OperationStatus.Attempt.Succeed(evtMsgs); } #region Event Handlers diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index e18e9056f9..2154241524 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -23,20 +23,22 @@ namespace Umbraco.Core.Services public EntityService(IDatabaseUnitOfWorkProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, - IContentService contentService, IContentTypeService contentTypeService, IMediaService mediaService, IDataTypeService dataTypeService, - IMemberService memberService, IMemberTypeService memberTypeService, IRuntimeCacheProvider runtimeCache) + IContentService contentService, IContentTypeService contentTypeService, + IMediaService mediaService, IMediaTypeService mediaTypeService, + IDataTypeService dataTypeService, + IMemberService memberService, IMemberTypeService memberTypeService, + IRuntimeCacheProvider runtimeCache) : base(provider, logger, eventMessagesFactory) { _runtimeCache = runtimeCache; - IContentTypeService contentTypeService1 = contentTypeService; _supportedObjectTypes = new Dictionary>> { {typeof (IDataTypeDefinition).FullName, new Tuple>(UmbracoObjectTypes.DataType, dataTypeService.GetDataTypeDefinitionById)}, {typeof (IContent).FullName, new Tuple>(UmbracoObjectTypes.Document, contentService.GetById)}, - {typeof (IContentType).FullName, new Tuple>(UmbracoObjectTypes.DocumentType, contentTypeService1.GetContentType)}, + {typeof (IContentType).FullName, new Tuple>(UmbracoObjectTypes.DocumentType, contentTypeService.Get)}, {typeof (IMedia).FullName, new Tuple>(UmbracoObjectTypes.Media, mediaService.GetById)}, - {typeof (IMediaType).FullName, new Tuple>(UmbracoObjectTypes.MediaType, contentTypeService1.GetMediaType)}, + {typeof (IMediaType).FullName, new Tuple>(UmbracoObjectTypes.MediaType, mediaTypeService.Get)}, {typeof (IMember).FullName, new Tuple>(UmbracoObjectTypes.Member, memberService.GetById)}, {typeof (IMemberType).FullName, new Tuple>(UmbracoObjectTypes.MemberType, memberTypeService.Get)}, //{typeof (IUmbracoEntity).FullName, new Tuple>(UmbracoObjectTypes.EntityContainer, id => @@ -74,6 +76,7 @@ namespace Umbraco.Core.Services { var result = _runtimeCache.GetCacheItem(CacheKeys.IdToKeyCacheKey + key, () => { + int? id; using (var uow = UowProvider.CreateUnitOfWork()) { switch (umbracoObjectType) @@ -87,11 +90,12 @@ namespace Umbraco.Core.Services case UmbracoObjectTypes.Member: case UmbracoObjectTypes.DataType: case UmbracoObjectTypes.DocumentTypeContainer: - return uow.Database.ExecuteScalar( + id = uow.Database.ExecuteScalar( uow.Database.Sql() .Select("id") .From() .Where(dto => dto.UniqueId == key)); + break; case UmbracoObjectTypes.RecycleBin: case UmbracoObjectTypes.Stylesheet: case UmbracoObjectTypes.MemberGroup: @@ -102,6 +106,8 @@ namespace Umbraco.Core.Services default: throw new NotSupportedException(); } + uow.Complete(); + return id; } }); return result.HasValue ? Attempt.Succeed(result.Value) : Attempt.Fail(); @@ -119,6 +125,7 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.CreateUnitOfWork()) { + Guid? guid; switch (umbracoObjectType) { case UmbracoObjectTypes.Document: @@ -129,11 +136,12 @@ namespace Umbraco.Core.Services case UmbracoObjectTypes.DocumentType: case UmbracoObjectTypes.Member: case UmbracoObjectTypes.DataType: - return uow.Database.ExecuteScalar( + guid = uow.Database.ExecuteScalar( uow.Database.Sql() .Select("uniqueID") .From() .Where(dto => dto.NodeId == id)); + break; case UmbracoObjectTypes.RecycleBin: case UmbracoObjectTypes.Stylesheet: case UmbracoObjectTypes.MemberGroup: @@ -144,6 +152,8 @@ namespace Umbraco.Core.Services default: throw new NotSupportedException(); } + uow.Complete(); + return guid; } }); return result.HasValue ? Attempt.Succeed(result.Value) : Attempt.Fail(); @@ -156,7 +166,9 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repository = uow.CreateRepository(); - return repository.GetByKey(key); + var entity = repository.GetByKey(key); + uow.Complete(); + return entity; } } @@ -187,7 +199,9 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repository = uow.CreateRepository(); - return repository.Get(id); + var e = repository.Get(id); + uow.Complete(); + return e; } } @@ -207,7 +221,9 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repository = uow.CreateRepository(); - return repository.GetByKey(key, objectTypeId); + var entity = repository.GetByKey(key, objectTypeId); + uow.Complete(); + return entity; } } @@ -239,7 +255,9 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repository = uow.CreateRepository(); - return repository.Get(id, objectTypeId); + var e = repository.Get(id, objectTypeId); + uow.Complete(); + return e; } } @@ -272,7 +290,9 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repository = uow.CreateRepository(); - return repository.Get(id); + var e = repository.Get(id); + uow.Complete(); + return e; } } @@ -301,7 +321,9 @@ namespace Umbraco.Core.Services if (entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21) return null; - return repository.Get(entity.ParentId); + var e = repository.Get(entity.ParentId); + uow.Complete(); + return e; } } @@ -321,7 +343,9 @@ namespace Umbraco.Core.Services return null; var objectTypeId = umbracoObjectType.GetGuid(); - return repository.Get(entity.ParentId, objectTypeId); + var e = repository.Get(entity.ParentId, objectTypeId); + uow.Complete(); + return e; } } @@ -337,7 +361,7 @@ namespace Umbraco.Core.Services var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.ParentId == parentId); var contents = repository.GetByQuery(query); - + uow.Complete(); return contents; } } @@ -356,7 +380,7 @@ namespace Umbraco.Core.Services var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.ParentId == parentId); var contents = repository.GetByQuery(query, objectTypeId).ToList(); // run within using! - + uow.Complete(); return contents; } } @@ -375,7 +399,7 @@ namespace Umbraco.Core.Services var pathMatch = entity.Path + ","; var query = repository.Query.Where(x => x.Path.StartsWith(pathMatch) && x.Id != id); var entities = repository.GetByQuery(query); - + uow.Complete(); return entities; } } @@ -395,7 +419,7 @@ namespace Umbraco.Core.Services var entity = repository.Get(id); var query = repository.Query.Where(x => x.Path.StartsWith(entity.Path) && x.Id != id); var entities = repository.GetByQuery(query, objectTypeId); - + uow.Complete(); return entities; } } @@ -413,7 +437,7 @@ namespace Umbraco.Core.Services var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.ParentId == -1); var entities = repository.GetByQuery(query, objectTypeId); - + uow.Complete(); return entities; } } @@ -456,7 +480,9 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repository = uow.CreateRepository(); - return repository.GetAll(objectTypeId, ids); + var entities = repository.GetAll(objectTypeId, ids); + uow.Complete(); + return entities; } } @@ -474,7 +500,9 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repository = uow.CreateRepository(); - return repository.GetAll(objectTypeId, keys); + var entities = repository.GetAll(objectTypeId, keys); + uow.Complete(); + return entities; } } @@ -498,7 +526,9 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repository = uow.CreateRepository(); - return repository.GetAll(objectTypeId, ids); + var entities = repository.GetAll(objectTypeId, ids); + uow.Complete(); + return entities; } } @@ -517,7 +547,9 @@ namespace Umbraco.Core.Services .Where(x => x.NodeId == id); var nodeObjectTypeId = uow.Database.ExecuteScalar(sql); var objectTypeId = nodeObjectTypeId; - return UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); + var t = UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); + uow.Complete(); + return t; } } @@ -536,7 +568,9 @@ namespace Umbraco.Core.Services .Where(x => x.UniqueId == key); var nodeObjectTypeId = uow.Database.ExecuteScalar(sql); var objectTypeId = nodeObjectTypeId; - return UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); + var t = UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); + uow.Complete(); + return t; } } diff --git a/src/Umbraco.Core/Services/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/EntityXmlSerializer.cs index 7449e890ad..a290774829 100644 --- a/src/Umbraco.Core/Services/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs @@ -446,7 +446,7 @@ namespace Umbraco.Core.Services if (contentType.Level != 1 && masterContentType == null) { //get url encoded folder names - var folders = contentTypeService.GetContentTypeContainers(contentType) + var folders = contentTypeService.GetContainers(contentType) .OrderBy(x => x.Level) .Select(x => HttpUtility.UrlEncode(x.Name)); diff --git a/src/Umbraco.Core/Services/ExternalLoginService.cs b/src/Umbraco.Core/Services/ExternalLoginService.cs index c97ec85717..c38daafc0f 100644 --- a/src/Umbraco.Core/Services/ExternalLoginService.cs +++ b/src/Umbraco.Core/Services/ExternalLoginService.cs @@ -25,7 +25,9 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repo = uow.CreateRepository(); - return repo.GetByQuery(repo.Query.Where(x => x.UserId == userId)); + var ident = repo.GetByQuery(repo.Query.Where(x => x.UserId == userId)); + uow.Complete(); + return ident; } } @@ -40,8 +42,10 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { var repo = uow.CreateRepository(); - return repo.GetByQuery(repo.Query + var idents = repo.GetByQuery(repo.Query .Where(x => x.ProviderKey == login.ProviderKey && x.LoginProvider == login.LoginProvider)); + uow.Complete(); + return idents; } } diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index 1368908dbd..2395f719ae 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -51,7 +51,9 @@ namespace Umbraco.Core.Services using (var uow = _fileUowProvider.CreateUnitOfWork()) { var repository = uow.CreateRepository(); - return repository.GetAll(names); + var stylesheets = repository.GetAll(names); + uow.Complete(); + return stylesheets; } } @@ -65,7 +67,9 @@ namespace Umbraco.Core.Services using (var uow = _fileUowProvider.CreateUnitOfWork()) { var repository = uow.CreateRepository(); - return repository.Get(name); + var stylesheet = repository.Get(name); + uow.Complete(); + return stylesheet; } } @@ -102,10 +106,14 @@ namespace Umbraco.Core.Services { var repository = uow.CreateRepository(); stylesheet = repository.Get(path); - if (stylesheet == null) return; + if (stylesheet == null) + { + uow.Complete(); + return; + } if (DeletingStylesheet.IsRaisedEventCancelled(new DeleteEventArgs(stylesheet), this)) - return; + return; // causes rollback repository.Delete(stylesheet); uow.Complete(); @@ -125,7 +133,9 @@ namespace Umbraco.Core.Services using (var uow = _fileUowProvider.CreateUnitOfWork()) { var repository = uow.CreateRepository(); - return repository.ValidateStylesheet(stylesheet); + var valid = repository.ValidateStylesheet(stylesheet); + uow.Complete(); + return valid; } } @@ -141,7 +151,9 @@ namespace Umbraco.Core.Services using (var uow = _fileUowProvider.CreateUnitOfWork()) { var repository = uow.CreateRepository(); - return repository.GetAll(names); + var scripts = repository.GetAll(names); + uow.Complete(); + return scripts; } } @@ -155,7 +167,9 @@ namespace Umbraco.Core.Services using (var uow = _fileUowProvider.CreateUnitOfWork()) { var repository = uow.CreateRepository(); - return repository.Get(name); + var script = repository.Get(name); + uow.Complete(); + return script; } } @@ -193,10 +207,14 @@ namespace Umbraco.Core.Services { var repository = uow.CreateRepository(); script = repository.Get(path); - if (script == null) return; + if (script == null) + { + uow.Complete(); + return; + } if (DeletingScript.IsRaisedEventCancelled(new DeleteEventArgs diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs index 44db417dab..7cead27241 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs +++ b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs @@ -66,7 +66,7 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs private bool PopulateListOfValidAlternateDocumentTypes() { // Start with all content types - var documentTypes = Services.ContentTypeService.GetAllContentTypes().ToArray(); + var documentTypes = Services.ContentTypeService.GetAll().ToArray(); // Remove invalid ones from list of potential alternatives documentTypes = RemoveCurrentDocumentTypeFromAlternatives(documentTypes).ToArray(); @@ -189,7 +189,7 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs private IContentType GetSelectedDocumentType() { - return Services.ContentTypeService.GetContentType(int.Parse(NewDocumentTypeList.SelectedItem.Value)); + return Services.ContentTypeService.Get(int.Parse(NewDocumentTypeList.SelectedItem.Value)); } private IEnumerable GetPropertiesOfContentType(IContentType contentType) diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index b38fab829f..1e63c67e3e 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -67,10 +67,10 @@ namespace Umbraco.Web.Cache //Bind to content type events - ContentTypeService.SavedContentType += ContentTypeServiceSavedContentType; - ContentTypeService.SavedMediaType += ContentTypeServiceSavedMediaType; - ContentTypeService.DeletedContentType += ContentTypeServiceDeletedContentType; - ContentTypeService.DeletedMediaType += ContentTypeServiceDeletedMediaType; + ContentTypeService.Saved += ContentTypeServiceSavedContentType; + MediaTypeService.Saved += ContentTypeServiceSavedMediaType; + ContentTypeService.Deleted += ContentTypeServiceDeletedContentType; + MediaTypeService.Deleted += ContentTypeServiceDeletedMediaType; MemberTypeService.Saved += MemberTypeServiceSaved; MemberTypeService.Deleted += MemberTypeServiceDeleted; @@ -113,8 +113,8 @@ namespace Umbraco.Web.Cache ContentService.Trashed += ContentServiceTrashed; ContentService.EmptiedRecycleBin += ContentServiceEmptiedRecycleBin; - PublishingStrategy.Published += PublishingStrategy_Published; - PublishingStrategy.UnPublished += PublishingStrategy_UnPublished; + ContentService.Published += ContentService_Published; + ContentService.UnPublished += ContentService_UnPublished; //public access events PublicAccessService.Saved += PublicAccessService_Saved; @@ -123,7 +123,7 @@ namespace Umbraco.Web.Cache #region Publishing - void PublishingStrategy_UnPublished(IPublishingStrategy sender, PublishEventArgs e) + void ContentService_UnPublished(IContentService sender, PublishEventArgs e) { if (e.PublishedEntities.Any()) { @@ -150,7 +150,7 @@ namespace Umbraco.Web.Cache DistributedCache.Instance.RemovePageCache(content); } - void PublishingStrategy_Published(IPublishingStrategy sender, PublishEventArgs e) + void ContentService_Published(IContentService sender, PublishEventArgs e) { if (e.PublishedEntities.Any()) { @@ -439,7 +439,7 @@ namespace Umbraco.Web.Cache /// /// /// - static void ContentTypeServiceDeletedMediaType(IContentTypeService sender, DeleteEventArgs e) + static void ContentTypeServiceDeletedMediaType(IMediaTypeService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveMediaTypeCache(x)); } @@ -469,7 +469,7 @@ namespace Umbraco.Web.Cache /// /// /// - static void ContentTypeServiceSavedMediaType(IContentTypeService sender, SaveEventArgs e) + static void ContentTypeServiceSavedMediaType(IMediaTypeService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshMediaTypeCache(x)); } diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index 44a6efe9ff..a95eef157d 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -300,7 +300,7 @@ namespace Umbraco.Web.Cache ClearContentTypeCache( ids.Select( x => - ApplicationContext.Current.Services.ContentTypeService.GetContentType(x) as IContentTypeBase) + ApplicationContext.Current.Services.ContentTypeService.Get(x) as IContentTypeBase) .WhereNotNull() .Select(x => FromContentType(x, isDeleted)) .ToArray()); diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index d1604f6a8e..93407bcbbd 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -144,7 +144,7 @@ namespace Umbraco.Web.Editors [OutgoingEditorModelEvent] public ContentItemDisplay GetEmpty(string contentTypeAlias, int parentId) { - var contentType = Services.ContentTypeService.GetContentType(contentTypeAlias); + var contentType = Services.ContentTypeService.Get(contentTypeAlias); if (contentType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index 45a2821130..70f8ca249c 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -34,7 +34,7 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] [EnableOverrideAuthorization] - public class ContentTypeController : ContentTypeControllerBase + public class ContentTypeController : ContentTypeControllerBase { /// /// Constructor @@ -55,12 +55,12 @@ namespace Umbraco.Web.Editors public int GetCount() { - return Services.ContentTypeService.CountContentTypes(); + return Services.ContentTypeService.Count(); } public DocumentTypeDisplay GetById(int id) { - var ct = Services.ContentTypeService.GetContentType(id); + var ct = Services.ContentTypeService.Get(id); if (ct == null) { throw new HttpResponseException(HttpStatusCode.NotFound); @@ -79,7 +79,7 @@ namespace Umbraco.Web.Editors [HttpPost] public HttpResponseMessage DeleteById(int id) { - var foundType = Services.ContentTypeService.GetContentType(id); + var foundType = Services.ContentTypeService.Get(id); if (foundType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); @@ -163,14 +163,14 @@ namespace Umbraco.Web.Editors [HttpPost] public HttpResponseMessage DeleteContainer(int id) { - Services.ContentTypeService.DeleteContentTypeContainer(id, Security.CurrentUser.Id); + Services.ContentTypeService.DeleteContainer(id, Security.CurrentUser.Id); return Request.CreateResponse(HttpStatusCode.OK); } public HttpResponseMessage PostCreateContainer(int parentId, string name) { - var result = Services.ContentTypeService.CreateContentTypeContainer(parentId, name, Security.CurrentUser.Id); + var result = Services.ContentTypeService.CreateContainer(parentId, name, Security.CurrentUser.Id); return result ? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id @@ -179,9 +179,9 @@ namespace Umbraco.Web.Editors public DocumentTypeDisplay PostSave(DocumentTypeSave contentTypeSave) { - var savedCt = PerformPostSave( + var savedCt = PerformPostSave( contentTypeSave: contentTypeSave, - getContentType: i => Services.ContentTypeService.GetContentType(i), + getContentType: i => Services.ContentTypeService.Get(i), saveContentType: type => Services.ContentTypeService.Save(type), beforeCreateNew: ctSave => { @@ -199,7 +199,7 @@ namespace Umbraco.Web.Editors () => ctSave.Alias, () => tryCreateTemplate.Result.StatusType); } - template = tryCreateTemplate.Result.Entity; + template = tryCreateTemplate.Result.Value; } //make sure the template alias is set on the default and allowed template so we can map it back @@ -227,7 +227,7 @@ namespace Umbraco.Web.Editors IContentType ct; if (parentId != Constants.System.Root) { - var parent = Services.ContentTypeService.GetContentType(parentId); + var parent = Services.ContentTypeService.Get(parentId); ct = parent != null ? new ContentType(parent, string.Empty) : new ContentType(parentId); } else @@ -245,7 +245,7 @@ namespace Umbraco.Web.Editors /// public IEnumerable GetAll() { - var types = Services.ContentTypeService.GetAllContentTypes(); + var types = Services.ContentTypeService.GetAll(); var basics = types.Select(Mapper.Map); return basics.Select(basic => @@ -269,7 +269,7 @@ namespace Umbraco.Web.Editors IEnumerable types; if (contentId == Constants.System.Root) { - types = Services.ContentTypeService.GetAllContentTypes().ToList(); + types = Services.ContentTypeService.GetAll().ToList(); //if no allowed root types are set, just return everything if (types.Any(x => x.AllowedAsRoot)) @@ -287,7 +287,7 @@ namespace Umbraco.Web.Editors if (ids.Any() == false) return Enumerable.Empty(); - types = Services.ContentTypeService.GetAllContentTypes(ids).ToList(); + types = Services.ContentTypeService.GetAll(ids).ToList(); } var basics = types.Select(Mapper.Map).ToList(); @@ -311,8 +311,8 @@ namespace Umbraco.Web.Editors { return PerformMove( move, - getContentType: i => Services.ContentTypeService.GetContentType(i), - doMove: (type, i) => Services.ContentTypeService.MoveContentType(type, i)); + getContentType: i => Services.ContentTypeService.Get(i), + doMove: (type, i) => Services.ContentTypeService.Move(type, i)); } /// @@ -324,8 +324,8 @@ namespace Umbraco.Web.Editors { return PerformCopy( copy, - getContentType: i => Services.ContentTypeService.GetContentType(i), - doCopy: (type, i) => Services.ContentTypeService.CopyContentType(type, i)); + getContentType: i => Services.ContentTypeService.Get(i), + doCopy: (type, i) => Services.ContentTypeService.Copy(type, i)); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs index 0e699b591a..62bf5092e0 100644 --- a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs @@ -4,21 +4,16 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Text; -using System.Text.RegularExpressions; using System.Web.Http; using AutoMapper; -using Newtonsoft.Json; using Umbraco.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.Dictionary; using Umbraco.Core.Exceptions; using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; -using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Editors { @@ -27,7 +22,8 @@ namespace Umbraco.Web.Editors /// [PluginController("UmbracoApi")] [PrefixlessBodyModelValidator] - public abstract class ContentTypeControllerBase : UmbracoAuthorizedJsonController + public abstract class ContentTypeControllerBase : UmbracoAuthorizedJsonController + where TContentType : class, IContentTypeComposition { private ICultureDictionary _cultureDictionary; @@ -79,19 +75,19 @@ namespace Umbraco.Web.Editors case UmbracoObjectTypes.DocumentType: if (contentTypeId > 0) { - source = Services.ContentTypeService.GetContentType(contentTypeId); + source = Services.ContentTypeService.Get(contentTypeId); if (source == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); } - allContentTypes = Services.ContentTypeService.GetAllContentTypes().Cast().ToArray(); + allContentTypes = Services.ContentTypeService.GetAll().Cast().ToArray(); break; case UmbracoObjectTypes.MediaType: if (contentTypeId > 0) { - source = Services.ContentTypeService.GetMediaType(contentTypeId); + source = Services.MediaTypeService.Get(contentTypeId); if (source == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); } - allContentTypes = Services.ContentTypeService.GetAllMediaTypes().Cast().ToArray(); + allContentTypes = Services.MediaTypeService.GetAll().Cast().ToArray(); break; case UmbracoObjectTypes.MemberType: @@ -148,12 +144,11 @@ namespace Umbraco.Web.Editors return CultureDictionary[text].IfNullOrWhiteSpace(text); } - protected TContentType PerformPostSave( + protected TContentType PerformPostSave( TContentTypeSave contentTypeSave, Func getContentType, Action saveContentType, Action beforeCreateNew = null) - where TContentType : class, IContentTypeComposition where TContentTypeDisplay : ContentTypeCompositionDisplay where TContentTypeSave : ContentTypeSave where TPropertyType : PropertyTypeBasic @@ -312,11 +307,10 @@ namespace Umbraco.Web.Editors /// /// /// - protected HttpResponseMessage PerformCopy( + protected HttpResponseMessage PerformCopy( MoveOrCopy move, Func getContentType, - Func>> doCopy) - where TContentType : IContentTypeComposition + Func>> doCopy) { var toMove = getContentType(move.Id); if (toMove == null) @@ -327,7 +321,7 @@ namespace Umbraco.Web.Editors var result = doCopy(toMove, move.ParentId); if (result.Success) { - var copy = result.Result.Entity; + var copy = result.Result.Value; var response = Request.CreateResponse(HttpStatusCode.OK); response.Content = new StringContent(copy.Path, Encoding.UTF8, "application/json"); return response; @@ -356,12 +350,13 @@ namespace Umbraco.Web.Editors /// /// /// - private HttpResponseException CreateCompositionValidationExceptionIfInvalid(TContentTypeSave contentTypeSave, IContentTypeComposition composition) + private HttpResponseException CreateCompositionValidationExceptionIfInvalid(TContentTypeSave contentTypeSave, TContentType composition) where TContentTypeSave : ContentTypeSave where TPropertyType : PropertyTypeBasic where TContentTypeDisplay : ContentTypeCompositionDisplay { - var validateAttempt = Services.ContentTypeService.ValidateComposition(composition); + var service = ApplicationContext.Services.GetContentTypeService(); + var validateAttempt = service.ValidateComposition(composition); if (validateAttempt == false) { //if it's not successful then we need to return some model state for the property aliases that diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 596e27e3a5..9d8955a374 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -566,8 +566,8 @@ namespace Umbraco.Web.Editors case UmbracoEntityTypes.PropertyType: //get all document types, then combine all property types into one list - var propertyTypes = Services.ContentTypeService.GetAllContentTypes().Cast() - .Concat(Services.ContentTypeService.GetAllMediaTypes()) + var propertyTypes = Services.ContentTypeService.GetAll().Cast() + .Concat(Services.MediaTypeService.GetAll()) .ToArray() .SelectMany(x => x.PropertyTypes) .DistinctBy(composition => composition.Alias); @@ -577,8 +577,8 @@ namespace Umbraco.Web.Editors case UmbracoEntityTypes.PropertyGroup: //get all document types, then combine all property types into one list - var propertyGroups = Services.ContentTypeService.GetAllContentTypes().Cast() - .Concat(Services.ContentTypeService.GetAllMediaTypes()) + var propertyGroups = Services.ContentTypeService.GetAll().Cast() + .Concat(Services.MediaTypeService.GetAll()) .ToArray() .SelectMany(x => x.PropertyGroups) .DistinctBy(composition => composition.Name); diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index dbdd9bea40..d50b10ef48 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -72,7 +72,7 @@ namespace Umbraco.Web.Editors [OutgoingEditorModelEvent] public MediaItemDisplay GetEmpty(string contentTypeAlias, int parentId) { - var contentType = Services.ContentTypeService.GetMediaType(contentTypeAlias); + var contentType = Services.MediaTypeService.Get(contentTypeAlias); if (contentType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); @@ -152,7 +152,7 @@ namespace Umbraco.Web.Editors { //Suggested convention for folder mediatypes - we can make this more or less complicated as long as we document it... //if you create a media type, which has an alias that ends with ...Folder then its a folder: ex: "secureFolder", "bannerFolder", "Folder" - var folderTypes = Services.ContentTypeService.GetAllMediaTypes().ToArray().Where(x => x.Alias.EndsWith("Folder")).Select(x => x.Id); + var folderTypes = Services.MediaTypeService.GetAll().ToArray().Where(x => x.Alias.EndsWith("Folder")).Select(x => x.Id); var children = (id < 0) ? Services.MediaService.GetRootMedia() : Services.MediaService.GetById(id).Children(); return children.Where(x => folderTypes.Contains(x.ContentTypeId)).Select(Mapper.Map>); diff --git a/src/Umbraco.Web/Editors/MediaTypeController.cs b/src/Umbraco.Web/Editors/MediaTypeController.cs index 31be4509fb..bf5ea1c9e6 100644 --- a/src/Umbraco.Web/Editors/MediaTypeController.cs +++ b/src/Umbraco.Web/Editors/MediaTypeController.cs @@ -32,7 +32,7 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] [EnableOverrideAuthorization] - public class MediaTypeController : ContentTypeControllerBase + public class MediaTypeController : ContentTypeControllerBase { /// /// Constructor @@ -54,12 +54,12 @@ namespace Umbraco.Web.Editors public int GetCount() { - return Services.ContentTypeService.CountContentTypes(); + return Services.ContentTypeService.Count(); } public MediaTypeDisplay GetById(int id) { - var ct = Services.ContentTypeService.GetMediaType(id); + var ct = Services.MediaTypeService.Get(id); if (ct == null) { throw new HttpResponseException(HttpStatusCode.NotFound); @@ -78,13 +78,13 @@ namespace Umbraco.Web.Editors [HttpPost] public HttpResponseMessage DeleteById(int id) { - var foundType = Services.ContentTypeService.GetMediaType(id); + var foundType = Services.MediaTypeService.Get(id); if (foundType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } - Services.ContentTypeService.Delete(foundType, Security.CurrentUser.Id); + Services.MediaTypeService.Delete(foundType, Security.CurrentUser.Id); return Request.CreateResponse(HttpStatusCode.OK); } @@ -131,7 +131,7 @@ namespace Umbraco.Web.Editors public IEnumerable GetAll() { - return Services.ContentTypeService.GetAllMediaTypes() + return Services.MediaTypeService.GetAll() .Select(Mapper.Map); } @@ -144,14 +144,14 @@ namespace Umbraco.Web.Editors [HttpPost] public HttpResponseMessage DeleteContainer(int id) { - Services.ContentTypeService.DeleteMediaTypeContainer(id, Security.CurrentUser.Id); + Services.MediaTypeService.DeleteContainer(id, Security.CurrentUser.Id); return Request.CreateResponse(HttpStatusCode.OK); } public HttpResponseMessage PostCreateContainer(int parentId, string name) { - var result = Services.ContentTypeService.CreateMediaTypeContainer(parentId, name, Security.CurrentUser.Id); + var result = Services.MediaTypeService.CreateContainer(parentId, name, Security.CurrentUser.Id); return result ? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id @@ -160,10 +160,10 @@ namespace Umbraco.Web.Editors public MediaTypeDisplay PostSave(MediaTypeSave contentTypeSave) { - var savedCt = PerformPostSave( + var savedCt = PerformPostSave( contentTypeSave: contentTypeSave, - getContentType: i => Services.ContentTypeService.GetMediaType(i), - saveContentType: type => Services.ContentTypeService.Save(type)); + getContentType: i => Services.MediaTypeService.Get(i), + saveContentType: type => Services.MediaTypeService.Save(type)); var display = Mapper.Map(savedCt); @@ -188,7 +188,7 @@ namespace Umbraco.Web.Editors IEnumerable types; if (contentId == Constants.System.Root) { - types = Services.ContentTypeService.GetAllMediaTypes().ToList(); + types = Services.MediaTypeService.GetAll().ToList(); //if no allowed root types are set, just return everything if (types.Any(x => x.AllowedAsRoot)) @@ -206,7 +206,7 @@ namespace Umbraco.Web.Editors if (ids.Any() == false) return Enumerable.Empty(); - types = Services.ContentTypeService.GetAllMediaTypes(ids).ToList(); + types = Services.MediaTypeService.GetAll(ids).ToList(); } var basics = types.Select(Mapper.Map).ToList(); @@ -229,8 +229,8 @@ namespace Umbraco.Web.Editors { return PerformMove( move, - getContentType: i => Services.ContentTypeService.GetMediaType(i), - doMove: (type, i) => Services.ContentTypeService.MoveMediaType(type, i)); + getContentType: i => Services.MediaTypeService.Get(i), + doMove: (type, i) => Services.MediaTypeService.Move(type, i)); } /// @@ -242,8 +242,8 @@ namespace Umbraco.Web.Editors { return PerformCopy( copy, - getContentType: i => Services.ContentTypeService.GetMediaType(i), - doCopy: (type, i) => Services.ContentTypeService.CopyMediaType(type, i)); + getContentType: i => Services.MediaTypeService.Get(i), + doCopy: (type, i) => Services.MediaTypeService.Copy(type, i)); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/MemberTypeController.cs b/src/Umbraco.Web/Editors/MemberTypeController.cs index 1d8766f253..4ce6766a63 100644 --- a/src/Umbraco.Web/Editors/MemberTypeController.cs +++ b/src/Umbraco.Web/Editors/MemberTypeController.cs @@ -25,7 +25,7 @@ namespace Umbraco.Web.Editors /// [PluginController("UmbracoApi")] [UmbracoTreeAuthorize(Constants.Trees.MemberTypes)] - public class MemberTypeController : ContentTypeControllerBase + public class MemberTypeController : ContentTypeControllerBase { /// /// Constructor @@ -131,7 +131,7 @@ namespace Umbraco.Web.Editors public MemberTypeDisplay PostSave(MemberTypeSave contentTypeSave) { - var savedCt = PerformPostSave( + var savedCt = PerformPostSave( contentTypeSave: contentTypeSave, getContentType: i => Services.MemberTypeService.Get(i), saveContentType: type => Services.MemberTypeService.Save(type)); diff --git a/src/Umbraco.Web/Editors/TemplateQueryController.cs b/src/Umbraco.Web/Editors/TemplateQueryController.cs index 2eaf63f159..e03e8e8f56 100644 --- a/src/Umbraco.Web/Editors/TemplateQueryController.cs +++ b/src/Umbraco.Web/Editors/TemplateQueryController.cs @@ -288,7 +288,7 @@ namespace Umbraco.Web.Editors public IEnumerable GetContentTypes() { var contentTypes = - ApplicationContext.Services.ContentTypeService.GetAllContentTypes() + ApplicationContext.Services.ContentTypeService.GetAll() .Select(x => new ContentTypeModel() { Alias = x.Alias, Name = x.Name }) .OrderBy(x => x.Name).ToList(); contentTypes.Insert(0, new ContentTypeModel() { Alias = string.Empty, Name = "Everything" }); diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index d9f1f25189..a859ba0813 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -219,7 +219,7 @@ namespace Umbraco.Web.Models.Mapping if (HttpContext.Current != null && UmbracoContext.Current != null && UmbracoContext.Current.Security.CurrentUser != null && UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) { - var currentDocumentType = contentTypeService.GetContentType(display.ContentTypeAlias); + var currentDocumentType = contentTypeService.Get(display.ContentTypeAlias); var currentDocumentTypeName = currentDocumentType == null ? string.Empty : currentDocumentType.Name; var currentDocumentTypeId = currentDocumentType == null ? string.Empty : currentDocumentType.Id.ToString(CultureInfo.InvariantCulture); diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs index 52f2dbad4b..f86f813385 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs @@ -69,7 +69,7 @@ namespace Umbraco.Web.Models.Mapping foreach (var a in add) { //TODO: Remove N+1 lookup - var addCt = applicationContext.Services.ContentTypeService.GetContentType(a); + var addCt = applicationContext.Services.ContentTypeService.Get(a); if (addCt != null) dest.AddContentType(addCt); } @@ -96,7 +96,7 @@ namespace Umbraco.Web.Models.Mapping foreach (var a in add) { //TODO: Remove N+1 lookup - var addCt = applicationContext.Services.ContentTypeService.GetMediaType(a); + var addCt = applicationContext.Services.MediaTypeService.Get(a); if (addCt != null) dest.AddContentType(addCt); } diff --git a/src/Umbraco.Web/Models/Mapping/LockedCompositionsResolver.cs b/src/Umbraco.Web/Models/Mapping/LockedCompositionsResolver.cs index d3d692d10d..cc3007ef29 100644 --- a/src/Umbraco.Web/Models/Mapping/LockedCompositionsResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/LockedCompositionsResolver.cs @@ -21,12 +21,12 @@ namespace Umbraco.Web.Models.Mapping // get ancestor ids from path of parent if not root if (source.ParentId != Constants.System.Root) { - var parent = _applicationContext.Services.ContentTypeService.GetContentType(source.ParentId); + var parent = _applicationContext.Services.ContentTypeService.Get(source.ParentId); if (parent != null) { var ancestorIds = parent.Path.Split(',').Select(int.Parse); // loop through all content types and return ordered aliases of ancestors - var allContentTypes = _applicationContext.Services.ContentTypeService.GetAllContentTypes().ToArray(); + var allContentTypes = _applicationContext.Services.ContentTypeService.GetAll().ToArray(); foreach (var ancestorId in ancestorIds) { var ancestor = allContentTypes.FirstOrDefault(x => x.Id == ancestorId); diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 578aab2755..66d16f6bd3 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -634,10 +634,10 @@ namespace Umbraco.Web private static bool IsDocumentTypeRecursive(IPublishedContent content, string docTypeAlias) { var contentTypeService = UmbracoContext.Current.Application.Services.ContentTypeService; - var type = contentTypeService.GetContentType(content.DocumentTypeAlias); + var type = contentTypeService.Get(content.DocumentTypeAlias); while (type != null && type.ParentId > 0) { - type = contentTypeService.GetContentType(type.ParentId); + type = contentTypeService.Get(type.ParentId); if (type.Alias.InvariantEquals(docTypeAlias)) return true; } diff --git a/src/Umbraco.Web/Services/SectionService.cs b/src/Umbraco.Web/Services/SectionService.cs index 6f31727a4d..39f7aa9d5c 100644 --- a/src/Umbraco.Web/Services/SectionService.cs +++ b/src/Umbraco.Web/Services/SectionService.cs @@ -213,9 +213,12 @@ namespace Umbraco.Web.Services lock (Locker) { //delete the assigned applications - _uowProvider.CreateUnitOfWork().Database.Execute( - "delete from umbracoUser2App where app = @appAlias", - new { appAlias = section.Alias }); + using (var uow = _uowProvider.CreateUnitOfWork()) + { + uow.Database.Execute("delete from umbracoUser2App where app = @appAlias", + new { appAlias = section.Alias }); + uow.Complete(); + } //delete the assigned trees var trees = _applicationTreeService.GetApplicationTrees(section.Alias); diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index 29dd5179ef..4a20b727f5 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -99,9 +99,9 @@ namespace Umbraco.Web.Trees } else { - var ct = Services.ContentTypeService.GetContentType(int.Parse(id)); + var ct = Services.ContentTypeService.Get(int.Parse(id)); IContentType parent = null; - parent = ct == null ? null : Services.ContentTypeService.GetContentType(ct.ParentId); + parent = ct == null ? null : Services.ContentTypeService.Get(ct.ParentId); if (enableInheritedDocumentTypes) { diff --git a/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs b/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs index 5afc7cee19..d832f743c3 100644 --- a/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs +++ b/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs @@ -30,7 +30,7 @@ namespace Umbraco.Web.WebApi.Binders protected override IContent CreateNew(ContentItemSave model) { - var contentType = ApplicationContext.Services.ContentTypeService.GetContentType(model.ContentTypeAlias); + var contentType = ApplicationContext.Services.ContentTypeService.Get(model.ContentTypeAlias); if (contentType == null) { throw new InvalidOperationException("No content type found wth alias " + model.ContentTypeAlias); diff --git a/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs b/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs index 726994d43e..2a335fdc98 100644 --- a/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs +++ b/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs @@ -29,7 +29,7 @@ namespace Umbraco.Web.WebApi.Binders protected override IMedia CreateNew(MediaItemSave model) { - var contentType = ApplicationContext.Services.ContentTypeService.GetMediaType(model.ContentTypeAlias); + var contentType = ApplicationContext.Services.MediaTypeService.Get(model.ContentTypeAlias); if (contentType == null) { throw new InvalidOperationException("No content type found wth alias " + model.ContentTypeAlias); diff --git a/src/Umbraco.Web/WebServices/BulkPublishController.cs b/src/Umbraco.Web/WebServices/BulkPublishController.cs index 8459cdbcf8..65e0078ace 100644 --- a/src/Umbraco.Web/WebServices/BulkPublishController.cs +++ b/src/Umbraco.Web/WebServices/BulkPublishController.cs @@ -56,7 +56,7 @@ namespace Umbraco.Web.WebServices private string GetMessageForStatuses(IEnumerable statuses, IContent doc) { //if all are successful then just say it was successful - if (statuses.All(x => ((int) x.StatusType) < 10)) + if (statuses.All(x => x.StatusType.IsSuccess())) { return Services.TextService.Localize("publish/nodePublishAll", new[] { doc.Name}); } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installedPackage.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installedPackage.aspx.cs index 9bcee907e4..7b20ee25c2 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installedPackage.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installedPackage.aspx.cs @@ -431,7 +431,7 @@ namespace umbraco.presentation.developer.packages if (int.TryParse(li.Value, out nId)) { - var contentType = contentTypeService.GetContentType(nId); + var contentType = contentTypeService.Get(nId); if (contentType != null) { contentTypes.Add(contentType); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/exportDocumenttype.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/exportDocumenttype.aspx.cs index 545a8493ee..3e3454d58f 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/exportDocumenttype.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/exportDocumenttype.aspx.cs @@ -34,7 +34,7 @@ namespace umbraco.presentation.dialogs int documentTypeId = Request.GetItemAs("nodeID"); if (documentTypeId > 0) { - var contentType = Services.ContentTypeService.GetContentType(documentTypeId); + var contentType = Services.ContentTypeService.Get(documentTypeId); if (contentType == null) throw new NullReferenceException("No content type found with id " + documentTypeId); Response.AddHeader("Content-Disposition", "attachment;filename=" + contentType.Alias + ".udt"); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs index 4e8c6f0a35..49df700caf 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs @@ -165,7 +165,7 @@ namespace umbraco.presentation.umbraco.dialogs Constants.Conventions.PublicAccess.MemberRoleRuleType, role); - if (entry.Success == false && entry.Result.Entity == null) + if (entry.Success == false && entry.Result.Value == null) { throw new Exception("Document is not protected!"); } @@ -180,7 +180,7 @@ namespace umbraco.presentation.umbraco.dialogs Constants.Conventions.PublicAccess.MemberUsernameRuleType, membershipUserName); - if (entry.Success == false && entry.Result.Entity == null) + if (entry.Success == false && entry.Result.Value == null) { throw new Exception("Document is not protected!"); } diff --git a/src/umbraco.cms/businesslogic/ContentType.cs b/src/umbraco.cms/businesslogic/ContentType.cs index 12956efb8d..05df73b1b5 100644 --- a/src/umbraco.cms/businesslogic/ContentType.cs +++ b/src/umbraco.cms/businesslogic/ContentType.cs @@ -1165,11 +1165,11 @@ namespace umbraco.cms.businesslogic { if (nodeObjectType == new Guid(Constants.ObjectTypes.DocumentType)) { - return ApplicationContext.Current.Services.ContentTypeService.GetContentType; + return ApplicationContext.Current.Services.ContentTypeService.Get; } if (nodeObjectType == new Guid(Constants.ObjectTypes.MediaType)) { - return ApplicationContext.Current.Services.ContentTypeService.GetMediaType; + return ApplicationContext.Current.Services.MediaTypeService.Get; } if (nodeObjectType == new Guid(Constants.ObjectTypes.MemberType)) { @@ -1177,7 +1177,8 @@ namespace umbraco.cms.businesslogic } //default to content - return ApplicationContext.Current.Services.ContentTypeService.GetContentType; + // should throw! + return ApplicationContext.Current.Services.ContentTypeService.Get; } } diff --git a/src/umbraco.cms/businesslogic/media/MediaType.cs b/src/umbraco.cms/businesslogic/media/MediaType.cs index 25e47b4ea4..059b86e696 100644 --- a/src/umbraco.cms/businesslogic/media/MediaType.cs +++ b/src/umbraco.cms/businesslogic/media/MediaType.cs @@ -66,7 +66,7 @@ namespace umbraco.cms.businesslogic.media [Obsolete("Obsolete, Use Umbraco.Core.Services.ContentTypeService.GetMediaType()", false)] public static new MediaType GetByAlias(string Alias) { - var mediaType = ApplicationContext.Current.Services.ContentTypeService.GetMediaType(Alias); + var mediaType = ApplicationContext.Current.Services.MediaTypeService.Get(Alias); return new MediaType(mediaType); } @@ -85,7 +85,7 @@ namespace umbraco.cms.businesslogic.media [Obsolete("Obsolete, Use Umbraco.Core.Services.ContentTypeService.GetMediaType()", false)] public static IEnumerable GetAllAsList() { - var mediaTypes = ApplicationContext.Current.Services.ContentTypeService.GetAllMediaTypes(); + var mediaTypes = ApplicationContext.Current.Services.MediaTypeService.GetAll(); return mediaTypes.OrderBy(x => x.Name).Select(x => new MediaType(x)); } @@ -98,7 +98,7 @@ namespace umbraco.cms.businesslogic.media internal static MediaType MakeNew(IUser u, string text, int parentId) { var mediaType = new Umbraco.Core.Models.MediaType(parentId) { Name = text, Alias = text, CreatorId = u.Id, Thumbnail = "icon-folder", Icon = "icon-folder" }; - ApplicationContext.Current.Services.ContentTypeService.Save(mediaType, u.Id); + ApplicationContext.Current.Services.MediaTypeService.Save(mediaType, u.Id); var mt = new MediaType(mediaType.Id); @@ -114,7 +114,7 @@ namespace umbraco.cms.businesslogic.media { var current = Thread.CurrentPrincipal != null ? Thread.CurrentPrincipal.Identity as UmbracoBackOfficeIdentity : null; var userId = current == null ? Attempt.Fail() : current.Id.TryConvertTo(); - ApplicationContext.Current.Services.ContentTypeService.Save(MediaTypeItem, userId.Success ? userId.Result : 0); + ApplicationContext.Current.Services.MediaTypeService.Save(MediaTypeItem, userId.Success ? userId.Result : 0); base.Save(); @@ -132,7 +132,7 @@ namespace umbraco.cms.businesslogic.media throw new ArgumentException("Can't delete a Media Type used as a Master Content Type. Please remove all references first!"); } - ApplicationContext.Current.Services.ContentTypeService.Delete(MediaTypeItem); + ApplicationContext.Current.Services.MediaTypeService.Delete(MediaTypeItem); } @@ -142,7 +142,7 @@ namespace umbraco.cms.businesslogic.media protected override void setupNode() { - var mediaType = ApplicationContext.Current.Services.ContentTypeService.GetMediaType(Id); + var mediaType = ApplicationContext.Current.Services.MediaTypeService.Get(Id); // If it's null, it's probably a folder if (mediaType != null) SetupNode(mediaType); diff --git a/src/umbraco.cms/businesslogic/web/DocumentType.cs b/src/umbraco.cms/businesslogic/web/DocumentType.cs index 5833b1d198..97fcbd85c4 100644 --- a/src/umbraco.cms/businesslogic/web/DocumentType.cs +++ b/src/umbraco.cms/businesslogic/web/DocumentType.cs @@ -72,7 +72,7 @@ namespace umbraco.cms.businesslogic.web { try { - var contentType = ApplicationContext.Current.Services.ContentTypeService.GetContentType(Alias); + var contentType = ApplicationContext.Current.Services.ContentTypeService.Get(Alias); return new DocumentType(contentType.Id); } catch @@ -104,7 +104,7 @@ namespace umbraco.cms.businesslogic.web [Obsolete("Obsolete, Use Umbraco.Core.Services.ContentTypeService.GetAllContentTypes()", false)] public static List GetAllAsList() { - var contentTypes = ApplicationContext.Current.Services.ContentTypeService.GetAllContentTypes(); + var contentTypes = ApplicationContext.Current.Services.ContentTypeService.GetAll(); var documentTypes = contentTypes.Select(x => new DocumentType(x)); return documentTypes.OrderBy(x => x.Text).ToList(); @@ -423,7 +423,7 @@ namespace umbraco.cms.businesslogic.web protected override void setupNode() { - var contentType = ApplicationContext.Current.Services.ContentTypeService.GetContentType(Id); + var contentType = ApplicationContext.Current.Services.ContentTypeService.Get(Id); // If it's null, it's probably a folder if (contentType != null) From e20c1cd0bb96295e74775128ae0be2dbf007feee Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 18 May 2016 13:10:36 +0200 Subject: [PATCH 04/10] U4-8465 Remove Core.Publishing namespace and classes --- src/Umbraco.Core/CoreBootManager.cs | 12 -- .../ServicesCompositionRoot.cs | 6 - .../Publishing/BasePublishingStrategy.cs | 46 ----- .../Publishing/IPublishingStrategy.cs | 72 -------- .../Publishing/ScheduledPublisher.cs | 61 ------- src/Umbraco.Core/Services/ContentService.cs | 40 ++++- src/Umbraco.Core/Services/IContentService.cs | 8 +- src/Umbraco.Core/Services/MediaService.cs | 6 - .../{Publishing => Services}/PublishStatus.cs | 81 +++++---- .../PublishStatusType.cs | 166 +++++++++--------- .../UnPublishStatus.cs | 3 +- .../UnPublishedStatusType.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 11 +- .../Persistence/SqlCeTableByTableTest.cs | 21 +-- .../MultiValuePropertyEditorTests.cs | 7 - .../Publishing/PublishingStrategyTests.cs | 1 - .../Services/ThreadSafetyServiceTest.cs | 1 - .../TestHelpers/BaseDatabaseFactoryTest.cs | 9 - .../TestHelpers/BaseUmbracoApplicationTest.cs | 10 -- src/Umbraco.Tests/TestHelpers/TestObjects.cs | 2 - .../Cache/CacheRefresherEventHandler.cs | 1 - src/Umbraco.Web/Editors/ContentController.cs | 11 -- src/Umbraco.Web/Strategies/LegacyClasses.cs | 22 --- src/Umbraco.Web/Umbraco.Web.csproj | 1 - .../WebServices/BulkPublishController.cs | 3 - .../WebServices/CoreStringsController.cs | 9 +- .../WebServices/ScheduledPublishController.cs | 5 +- .../umbraco/dialogs/sort.aspx.cs | 2 +- 28 files changed, 180 insertions(+), 439 deletions(-) delete mode 100644 src/Umbraco.Core/Publishing/BasePublishingStrategy.cs delete mode 100644 src/Umbraco.Core/Publishing/IPublishingStrategy.cs delete mode 100644 src/Umbraco.Core/Publishing/ScheduledPublisher.cs rename src/Umbraco.Core/{Publishing => Services}/PublishStatus.cs (94%) rename src/Umbraco.Core/{Publishing => Services}/PublishStatusType.cs (96%) rename src/Umbraco.Core/{Publishing => Services}/UnPublishStatus.cs (95%) rename src/Umbraco.Core/{Publishing => Services}/UnPublishedStatusType.cs (94%) delete mode 100644 src/Umbraco.Web/Strategies/LegacyClasses.cs diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index eb9cc6903e..61947ac165 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -1,17 +1,13 @@ using System; using System.IO; -using System.IO; -using System.Linq; using System.Threading.Tasks; using System.Threading; using AutoMapper; using LightInject; -using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Events; using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -19,18 +15,10 @@ using Umbraco.Core.Manifest; using Umbraco.Core.Models.Mapping; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.ObjectResolution; -using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Migrations; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Profiling; using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Publishing; -using Umbraco.Core.Macros; -using Umbraco.Core.Manifest; -using Umbraco.Core.Models.Identity; -using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Core.Sync; using Umbraco.Core.Strings; diff --git a/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs b/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs index e6a100b3e2..9d8bafa354 100644 --- a/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs +++ b/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs @@ -1,17 +1,11 @@ using System; -using System.Collections.Generic; -using System.ComponentModel; using System.IO; using System.Linq; using LightInject; using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Publishing; using Umbraco.Core.Services; -using Umbraco.Core.Strings; namespace Umbraco.Core.DependencyInjection { diff --git a/src/Umbraco.Core/Publishing/BasePublishingStrategy.cs b/src/Umbraco.Core/Publishing/BasePublishingStrategy.cs deleted file mode 100644 index 63f32c41c5..0000000000 --- a/src/Umbraco.Core/Publishing/BasePublishingStrategy.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Publishing -{ - /// - /// Abstract class for the implementation of an , which provides the events used for publishing/unpublishing. - /// - public abstract class BasePublishingStrategy : IPublishingStrategy - { - - public abstract bool Publish(IContent content, int userId); - public abstract bool PublishWithChildren(IEnumerable content, int userId); - public abstract bool UnPublish(IContent content, int userId); - public abstract bool UnPublish(IEnumerable content, int userId); - - /// - /// Call to fire event that updating the published content has finalized. - /// - /// - /// This seperation of the OnPublished event is done to ensure that the Content - /// has been properly updated (committed unit of work) and xml saved in the db. - /// - /// thats being published - public abstract void PublishingFinalized(IContent content); - - /// - /// Call to fire event that updating the published content has finalized. - /// - /// An enumerable list of thats being published - /// Boolean indicating whether its all content that is republished - public abstract void PublishingFinalized(IEnumerable content, bool isAllRepublished); - - /// - /// Call to fire event that updating the unpublished content has finalized. - /// - /// thats being unpublished - public abstract void UnPublishingFinalized(IContent content); - - /// - /// Call to fire event that updating the unpublished content has finalized. - /// - /// An enumerable list of thats being unpublished - public abstract void UnPublishingFinalized(IEnumerable content); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Publishing/IPublishingStrategy.cs b/src/Umbraco.Core/Publishing/IPublishingStrategy.cs deleted file mode 100644 index 0408409488..0000000000 --- a/src/Umbraco.Core/Publishing/IPublishingStrategy.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Publishing -{ - /// - /// Defines the Publishing Strategy - /// - public interface IPublishingStrategy - { - /// - /// Publishes a single piece of Content - /// - /// to publish - /// Id of the User issueing the publish operation - /// True if the publish operation was successfull and not cancelled, otherwise false - bool Publish(IContent content, int userId); - - /// - /// Publishes a list of Content - /// - /// An enumerable list of - /// Id of the User issueing the publish operation - /// True if the publish operation was successfull and not cancelled, otherwise false - bool PublishWithChildren(IEnumerable content, int userId); - - /// - /// Unpublishes a single piece of Content - /// - /// to unpublish - /// Id of the User issueing the unpublish operation - /// True if the unpublish operation was successfull and not cancelled, otherwise false - bool UnPublish(IContent content, int userId); - - /// - /// Unpublishes a list of Content - /// - /// An enumerable list of - /// Id of the User issueing the unpublish operation - /// True if the unpublish operation was successfull and not cancelled, otherwise false - bool UnPublish(IEnumerable content, int userId); - - /// - /// Call to fire event that updating the published content has finalized. - /// - /// - /// This seperation of the OnPublished event is done to ensure that the Content - /// has been properly updated (committed unit of work) and xml saved in the db. - /// - /// thats being published - void PublishingFinalized(IContent content); - - /// - /// Call to fire event that updating the published content has finalized. - /// - /// An enumerable list of thats being published - /// Boolean indicating whether its all content that is republished - void PublishingFinalized(IEnumerable content, bool isAllRepublished); - - /// - /// Call to fire event that updating the unpublished content has finalized. - /// - /// thats being unpublished - void UnPublishingFinalized(IContent content); - - /// - /// Call to fire event that updating the unpublished content has finalized. - /// - /// An enumerable list of thats being unpublished - void UnPublishingFinalized(IEnumerable content); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Publishing/ScheduledPublisher.cs b/src/Umbraco.Core/Publishing/ScheduledPublisher.cs deleted file mode 100644 index 45492423ab..0000000000 --- a/src/Umbraco.Core/Publishing/ScheduledPublisher.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Publishing -{ - /// - /// Used to perform scheduled publishing/unpublishing - /// - internal class ScheduledPublisher - { - private readonly IContentService _contentService; - - public ScheduledPublisher(IContentService contentService) - { - _contentService = contentService; - } - - public void CheckPendingAndProcess() - { - foreach (var d in _contentService.GetContentForRelease()) - { - try - { - d.ReleaseDate = null; - var result = _contentService.SaveAndPublishWithStatus(d, (int)d.GetWriterProfile().Id); - if (result.Success == false) - { - if (result.Exception != null) - { - LogHelper.Error("Could not published the document (" + d.Id + ") based on it's scheduled release, status result: " + result.Result.StatusType, result.Exception); - } - else - { - LogHelper.Warn("Could not published the document (" + d.Id + ") based on it's scheduled release. Status result: " + result.Result.StatusType); - } - } - } - catch (Exception ee) - { - LogHelper.Error(string.Format("Error publishing node {0}", d.Id), ee); - throw; - } - } - foreach (var d in _contentService.GetContentForExpiration()) - { - try - { - d.ExpireDate = null; - _contentService.UnPublish(d, (int)d.GetWriterProfile().Id); - } - catch (Exception ee) - { - LogHelper.Error(string.Format("Error unpublishing node {0}", d.Id), ee); - throw; - } - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index ce9c9ad323..2cc3a2a002 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -12,7 +12,6 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Publishing; using Umbraco.Core.Strings; namespace Umbraco.Core.Services @@ -1220,6 +1219,45 @@ namespace Umbraco.Core.Services return ((IContentServiceOperations)this).PublishWithChildren(content, userId, includeUnpublished); } + /// + /// Used to perform scheduled publishing/unpublishing + /// + public IEnumerable> PerformScheduledPublish() + { + //TODO: Do I need to move all of this logic to the repo? Or wrap this all in a unit of work? + + foreach (var d in GetContentForRelease()) + { + d.ReleaseDate = null; + var result = SaveAndPublishWithStatus(d, (int)d.GetWriterProfile(_userService).Id); + if (result.Success == false) + { + if (result.Exception != null) + { + Logger.Error("Could not published the document (" + d.Id + ") based on it's scheduled release, status result: " + result.Result.StatusType, result.Exception); + } + else + { + Logger.Warn("Could not published the document (" + d.Id + ") based on it's scheduled release. Status result: " + result.Result.StatusType); + } + } + yield return result; + } + foreach (var d in GetContentForExpiration()) + { + try + { + d.ExpireDate = null; + UnPublish(d, (int)d.GetWriterProfile(_userService).Id); + } + catch (Exception ee) + { + Logger.Error($"Error unpublishing node {d.Id}", ee); + throw; + } + } + } + /// /// Publishes a object and all its children /// diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 625f917ffe..14d1791814 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -5,7 +5,6 @@ using System.Xml.Linq; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Publishing; namespace Umbraco.Core.Services { @@ -63,6 +62,11 @@ namespace Umbraco.Core.Services /// The list of statuses for all published items IEnumerable> PublishWithChildren(IContent content, int userId = 0, bool includeUnpublished = false); + /// + /// Used to perform scheduled publishing/unpublishing + /// + IEnumerable> PerformScheduledPublish(); + /// /// Saves and Publishes a single object /// @@ -94,6 +98,8 @@ namespace Umbraco.Core.Services /// public interface IContentService : IService { + + /// /// Returns the persisted content's XML structure /// diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index f7849ce640..0ebc3d71be 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -1,23 +1,17 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Globalization; using System.Linq; -using System.Text.RegularExpressions; using System.Threading; -using System.Xml.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Publishing; using Umbraco.Core.Strings; namespace Umbraco.Core.Services diff --git a/src/Umbraco.Core/Publishing/PublishStatus.cs b/src/Umbraco.Core/Services/PublishStatus.cs similarity index 94% rename from src/Umbraco.Core/Publishing/PublishStatus.cs rename to src/Umbraco.Core/Services/PublishStatus.cs index f348367f22..046ef2e806 100644 --- a/src/Umbraco.Core/Publishing/PublishStatus.cs +++ b/src/Umbraco.Core/Services/PublishStatus.cs @@ -1,42 +1,41 @@ -using System.Collections.Generic; -using Umbraco.Core.Events; -using Umbraco.Core.Models; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Publishing -{ - /// - /// Represents the result of publishing a content item. - /// - public class PublishStatus : OperationStatus - { - /// - /// Creates a new instance of the class with a status type, event messages, and a content item. - /// - /// The status of the operation. - /// Event messages produced by the operation. - /// The content item. - public PublishStatus(PublishStatusType statusType, EventMessages eventMessages, IContent content) - : base(statusType, eventMessages, content) - { } - - /// - /// Creates a new successful instance of the class with a event messages, and a content item. - /// - /// Event messages produced by the operation. - /// The content item. - public PublishStatus(IContent content, EventMessages eventMessages) - : base(PublishStatusType.Success, eventMessages, content) - { } - - /// - /// Gets the content item. - /// - public IContent ContentItem => Value; - - /// - /// Gets or sets the invalid properties, if the status failed due to validation. - /// - public IEnumerable InvalidProperties { get; set; } - } +using System.Collections.Generic; +using Umbraco.Core.Events; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + /// + /// Represents the result of publishing a content item. + /// + public class PublishStatus : OperationStatus + { + /// + /// Creates a new instance of the class with a status type, event messages, and a content item. + /// + /// The status of the operation. + /// Event messages produced by the operation. + /// The content item. + public PublishStatus(PublishStatusType statusType, EventMessages eventMessages, IContent content) + : base(statusType, eventMessages, content) + { } + + /// + /// Creates a new successful instance of the class with a event messages, and a content item. + /// + /// Event messages produced by the operation. + /// The content item. + public PublishStatus(IContent content, EventMessages eventMessages) + : base(PublishStatusType.Success, eventMessages, content) + { } + + /// + /// Gets the content item. + /// + public IContent ContentItem => Value; + + /// + /// Gets or sets the invalid properties, if the status failed due to validation. + /// + public IEnumerable InvalidProperties { get; set; } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Publishing/PublishStatusType.cs b/src/Umbraco.Core/Services/PublishStatusType.cs similarity index 96% rename from src/Umbraco.Core/Publishing/PublishStatusType.cs rename to src/Umbraco.Core/Services/PublishStatusType.cs index e68b31c887..cc9f21d94d 100644 --- a/src/Umbraco.Core/Publishing/PublishStatusType.cs +++ b/src/Umbraco.Core/Services/PublishStatusType.cs @@ -1,84 +1,84 @@ -namespace Umbraco.Core.Publishing -{ - /// - /// A value indicating the result of publishing a content item. - /// - /// Do NOT compare against a hard-coded numeric value to check for success or failure, - /// but instead use the IsSuccess() extension method defined below - which should be the unique - /// place where the numeric test should take place. - /// - public enum PublishStatusType - { - /// - /// The publishing was successful. - /// - Success = 0, - - /// - /// The item was already published. - /// - SuccessAlreadyPublished = 1, - - // Values below this value indicate a success, values above it indicate a failure. - // This value is considered a failure. - //Reserved = 10, - - /// - /// The content could not be published because it's ancestor path isn't published. - /// - FailedPathNotPublished = 11, - - /// - /// The content item was scheduled to be un-published and it has expired so we cannot force it to be - /// published again as part of a bulk publish operation. - /// - FailedHasExpired = 12, - - /// - /// The content item is scheduled to be released in the future and therefore we cannot force it to - /// be published during a bulk publish operation. - /// - FailedAwaitingRelease = 13, - - /// - /// The content item could not be published because it is in the trash. - /// - FailedIsTrashed = 14, - - /// - /// The publish action has been cancelled by an event handler. - /// - FailedCancelledByEvent = 15, - - /// - /// The content item could not be published because it contains invalid data (has not passed validation requirements). - /// - FailedContentInvalid = 16 - } - - /// - /// Provides extension methods for the enum. - /// - public static class PublicStatusTypeExtensions - { - /// - /// Gets a value indicating whether the status indicates a success. - /// - /// The status. - /// A value indicating whether the status indicates a success. - public static bool IsSuccess(this PublishStatusType status) - { - return (int) status < 10; - } - - /// - /// Gets a value indicating whether the status indicates a failure. - /// - /// The status. - /// A value indicating whether the status indicates a failure. - public static bool IsFailure(this PublishStatusType status) - { - return (int) status >= 10; - } - } +namespace Umbraco.Core.Services +{ + /// + /// A value indicating the result of publishing a content item. + /// + /// Do NOT compare against a hard-coded numeric value to check for success or failure, + /// but instead use the IsSuccess() extension method defined below - which should be the unique + /// place where the numeric test should take place. + /// + public enum PublishStatusType + { + /// + /// The publishing was successful. + /// + Success = 0, + + /// + /// The item was already published. + /// + SuccessAlreadyPublished = 1, + + // Values below this value indicate a success, values above it indicate a failure. + // This value is considered a failure. + //Reserved = 10, + + /// + /// The content could not be published because it's ancestor path isn't published. + /// + FailedPathNotPublished = 11, + + /// + /// The content item was scheduled to be un-published and it has expired so we cannot force it to be + /// published again as part of a bulk publish operation. + /// + FailedHasExpired = 12, + + /// + /// The content item is scheduled to be released in the future and therefore we cannot force it to + /// be published during a bulk publish operation. + /// + FailedAwaitingRelease = 13, + + /// + /// The content item could not be published because it is in the trash. + /// + FailedIsTrashed = 14, + + /// + /// The publish action has been cancelled by an event handler. + /// + FailedCancelledByEvent = 15, + + /// + /// The content item could not be published because it contains invalid data (has not passed validation requirements). + /// + FailedContentInvalid = 16 + } + + /// + /// Provides extension methods for the enum. + /// + public static class PublicStatusTypeExtensions + { + /// + /// Gets a value indicating whether the status indicates a success. + /// + /// The status. + /// A value indicating whether the status indicates a success. + public static bool IsSuccess(this PublishStatusType status) + { + return (int) status < 10; + } + + /// + /// Gets a value indicating whether the status indicates a failure. + /// + /// The status. + /// A value indicating whether the status indicates a failure. + public static bool IsFailure(this PublishStatusType status) + { + return (int) status >= 10; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Publishing/UnPublishStatus.cs b/src/Umbraco.Core/Services/UnPublishStatus.cs similarity index 95% rename from src/Umbraco.Core/Publishing/UnPublishStatus.cs rename to src/Umbraco.Core/Services/UnPublishStatus.cs index 91f242cc6f..42a48177a3 100644 --- a/src/Umbraco.Core/Publishing/UnPublishStatus.cs +++ b/src/Umbraco.Core/Services/UnPublishStatus.cs @@ -1,8 +1,7 @@ using Umbraco.Core.Events; using Umbraco.Core.Models; -using Umbraco.Core.Services; -namespace Umbraco.Core.Publishing +namespace Umbraco.Core.Services { /// /// Represents the result of unpublishing a content item. diff --git a/src/Umbraco.Core/Publishing/UnPublishedStatusType.cs b/src/Umbraco.Core/Services/UnPublishedStatusType.cs similarity index 94% rename from src/Umbraco.Core/Publishing/UnPublishedStatusType.cs rename to src/Umbraco.Core/Services/UnPublishedStatusType.cs index 515e98daf4..cf887871ef 100644 --- a/src/Umbraco.Core/Publishing/UnPublishedStatusType.cs +++ b/src/Umbraco.Core/Services/UnPublishedStatusType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Publishing +namespace Umbraco.Core.Services { /// /// A status type of the result of unpublishing a content item diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index b15060f743..ac034c170b 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -499,8 +499,8 @@ - - + + @@ -1202,12 +1202,9 @@ - - - - + + - diff --git a/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs b/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs index cb6bac7571..a06354c513 100644 --- a/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs +++ b/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs @@ -1,26 +1,7 @@ -using System; -using System.IO; -using Moq; -using NPoco; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Events; -using Umbraco.Core.Logging; +using NUnit.Framework; using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.ObjectResolution; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Profiling; -using Umbraco.Core.Publishing; -using Umbraco.Core.Services; -using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; -using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; namespace Umbraco.Tests.Persistence { diff --git a/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs index 7bb0b3660a..6d55801631 100644 --- a/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs @@ -1,21 +1,14 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Globalization; -using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Profiling; using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Publishing; using Umbraco.Core.Services; -using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; using Umbraco.Web.PropertyEditors; diff --git a/src/Umbraco.Tests/Publishing/PublishingStrategyTests.cs b/src/Umbraco.Tests/Publishing/PublishingStrategyTests.cs index eaad5b22a2..50f5542de3 100644 --- a/src/Umbraco.Tests/Publishing/PublishingStrategyTests.cs +++ b/src/Umbraco.Tests/Publishing/PublishingStrategyTests.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Events; using Umbraco.Core.Models; -using Umbraco.Core.Publishing; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using System.Linq; diff --git a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs index cf5cc6212b..0534599173 100644 --- a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs +++ b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs @@ -14,7 +14,6 @@ using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Publishing; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index 10ddecdd57..dc49b6c20e 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.ComponentModel; using System.Configuration; using System.Data.SqlServerCe; using System.IO; @@ -13,28 +11,21 @@ using SQLCE4Umbraco; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.ObjectResolution; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Publishing; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.XmlPublishedCache; -using Umbraco.Web.Routing; using Umbraco.Web.Security; -using umbraco.BusinessLogic; using Umbraco.Core.Events; -using Umbraco.Core.Models; using File = System.IO.File; namespace Umbraco.Tests.TestHelpers diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index 2be8423a76..d36042e180 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -1,6 +1,4 @@ using System; -using System.Collections; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; @@ -16,24 +14,16 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models.Mapping; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Profiling; using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Publishing; -using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Web; -using Umbraco.Web.Models.Mapping; -using umbraco.BusinessLogic; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Events; -using Umbraco.Core.Models.Identity; -using Umbraco.Core.Persistence.Repositories; using Umbraco.Web.DependencyInjection; -using ObjectExtensions = Umbraco.Core.ObjectExtensions; namespace Umbraco.Tests.TestHelpers { diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index eccc68d07e..1c0f7c1b82 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Data.Common; using System.IO; using System.Linq; -using Moq; using NPoco; using Umbraco.Core; using Umbraco.Core.Events; @@ -12,7 +11,6 @@ using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Publishing; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Web.Services; diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index 1e63c67e3e..b354bb8a8f 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -9,7 +9,6 @@ using Umbraco.Core.Services; using umbraco.BusinessLogic; using System.Linq; using Umbraco.Core.Logging; -using Umbraco.Core.Publishing; using Umbraco.Web.Services; using Content = Umbraco.Core.Models.Content; using ApplicationTree = Umbraco.Core.Models.ApplicationTree; diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 93407bcbbd..0dbd75171a 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -4,36 +4,25 @@ using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; -using System.Net.Http.Formatting; using System.Text; using System.Web.Http; using System.Web.Http.ModelBinding; -using System.Web.Http.ModelBinding.Binders; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Publishing; using Umbraco.Core.Services; -using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Mapping; using Umbraco.Web.Mvc; -using Umbraco.Web.Security; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Binders; using Umbraco.Web.WebApi.Filters; -using umbraco; -using Umbraco.Core.Models; -using Umbraco.Core.Dynamics; using umbraco.cms.businesslogic.web; using umbraco.presentation.preview; -using Umbraco.Core.PropertyEditors; -using Umbraco.Web.UI; using Constants = Umbraco.Core.Constants; -using Notification = Umbraco.Web.Models.ContentEditing.Notification; namespace Umbraco.Web.Editors { diff --git a/src/Umbraco.Web/Strategies/LegacyClasses.cs b/src/Umbraco.Web/Strategies/LegacyClasses.cs deleted file mode 100644 index 434ab8ccbf..0000000000 --- a/src/Umbraco.Web/Strategies/LegacyClasses.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Events; -using Umbraco.Core.Models; -using Umbraco.Core.Publishing; -using Umbraco.Web.Cache; - - -namespace Umbraco.Web.Strategies.Publishing -{ - [Obsolete("This is not used and will be removed from the codebase in future versions")] - public class UpdateCacheAfterPublish : ApplicationEventHandler - { - } - - [Obsolete("This is not used and will be removed from the codebase in future versions")] - public class UpdateCacheAfterUnPublish : ApplicationEventHandler - { - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 41c9338134..afa98a407b 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1057,7 +1057,6 @@ - diff --git a/src/Umbraco.Web/WebServices/BulkPublishController.cs b/src/Umbraco.Web/WebServices/BulkPublishController.cs index 65e0078ace..5411beb61f 100644 --- a/src/Umbraco.Web/WebServices/BulkPublishController.cs +++ b/src/Umbraco.Web/WebServices/BulkPublishController.cs @@ -4,11 +4,8 @@ using System.Text; using System.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Core.Publishing; using Umbraco.Core.Services; using Umbraco.Web.Mvc; -using umbraco; -using umbraco.cms.businesslogic.web; namespace Umbraco.Web.WebServices { diff --git a/src/Umbraco.Web/WebServices/CoreStringsController.cs b/src/Umbraco.Web/WebServices/CoreStringsController.cs index de7bcd55d5..43be19908c 100644 --- a/src/Umbraco.Web/WebServices/CoreStringsController.cs +++ b/src/Umbraco.Web/WebServices/CoreStringsController.cs @@ -1,14 +1,7 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web.Mvc; +using System.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Strings; -using Umbraco.Core.Models; -using Umbraco.Core.Publishing; -using Umbraco.Core.Services; using Umbraco.Web.Mvc; -using umbraco; namespace Umbraco.Web.WebServices { diff --git a/src/Umbraco.Web/WebServices/ScheduledPublishController.cs b/src/Umbraco.Web/WebServices/ScheduledPublishController.cs index 0ab76738dc..8169a44284 100644 --- a/src/Umbraco.Web/WebServices/ScheduledPublishController.cs +++ b/src/Umbraco.Web/WebServices/ScheduledPublishController.cs @@ -2,7 +2,7 @@ using System.Web.Mvc; using umbraco; using Umbraco.Core.Logging; -using Umbraco.Core.Publishing; +using Umbraco.Core.Services; using Umbraco.Web.Mvc; namespace Umbraco.Web.WebServices @@ -27,8 +27,7 @@ namespace Umbraco.Web.WebServices // DO not run publishing if content is re-loading if (content.Instance.isInitializing == false) { - var publisher = new ScheduledPublisher(Services.ContentService); - publisher.CheckPendingAndProcess(); + Services.ContentService.WithResult().PerformScheduledPublish(); } return Json(new diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sort.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sort.aspx.cs index f55a08aead..47325af895 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sort.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/sort.aspx.cs @@ -144,7 +144,7 @@ namespace umbraco.cms.presentation return node; } - public struct SortableNode + public class SortableNode { public string id; public int sortOrder; From 885595b1e6fc02d9fcc761e2786920685f4d33de Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 18 May 2016 16:06:59 +0200 Subject: [PATCH 05/10] Hides and Obsoletes crappy extension methods that rely on ApplicationContext.Current and updates all usages to not use them - this is most important because the services were using extensions methods that go back out to the ApplicationContext.Current (booo!) --- src/Umbraco.Core/Models/ContentBase.cs | 9 + src/Umbraco.Core/Models/ContentExtensions.cs | 157 ++++++++++++------ src/Umbraco.Core/Services/ContentService.cs | 4 +- src/Umbraco.Tests/Models/ContentXmlTest.cs | 4 +- src/Umbraco.Tests/Models/MediaXmlTest.cs | 2 +- .../Services/ContentServiceTests.cs | 14 +- .../config/ClientDependency.config | 2 +- .../umbraco/dialogs/ChangeDocType.aspx.cs | 2 +- src/Umbraco.Web/Editors/MediaController.cs | 6 +- src/Umbraco.Web/Models/ContentExtensions.cs | 2 +- .../Models/Mapping/ContentModelMapper.cs | 19 ++- .../Models/Mapping/CreatorResolver.cs | 10 +- .../Models/Mapping/MediaModelMapper.cs | 17 +- .../Models/Mapping/MemberModelMapper.cs | 6 +- .../Models/Mapping/OwnerResolver.cs | 10 +- .../PublishedCache/MemberPublishedContent.cs | 12 +- .../Routing/UrlProviderExtensions.cs | 8 +- .../WebServices/DomainsApiController.cs | 2 +- src/Umbraco.Web/umbraco.presentation/page.cs | 1 + 19 files changed, 189 insertions(+), 98 deletions(-) diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index aff5dc35a3..16fd59b456 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -9,6 +9,7 @@ using System.Reflection; using System.Runtime.Serialization; using System.Web; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Services; namespace Umbraco.Core.Models { @@ -436,6 +437,14 @@ namespace Umbraco.Core.Models /// /// Alias of the PropertyType /// Value to set for the Property + /// + public virtual void SetPropertyValue(string propertyTypeAlias, HttpPostedFileBase value, IDataTypeService dataTypeService) + { + ContentExtensions.SetValue(this, propertyTypeAlias, value, dataTypeService); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use the overload with the IDataTypeService parameter instead")] public virtual void SetPropertyValue(string propertyTypeAlias, HttpPostedFileBase value) { ContentExtensions.SetValue(this, propertyTypeAlias, value); diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs index ceed239d53..bc155e313d 100644 --- a/src/Umbraco.Core/Models/ContentExtensions.cs +++ b/src/Umbraco.Core/Models/ContentExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Drawing; using System.Globalization; using System.IO; @@ -221,7 +222,15 @@ namespace Umbraco.Core.Models /// Returns a list of the current contents ancestors, not including the content itself. /// /// Current content + /// /// An enumerable list of objects + public static IEnumerable Ancestors(this IContent content, IContentService contentService) + { + return contentService.GetAncestors(content); + } + + [Obsolete("Use the overload with the service reference instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public static IEnumerable Ancestors(this IContent content) { return ApplicationContext.Current.Services.ContentService.GetAncestors(content); @@ -231,7 +240,15 @@ namespace Umbraco.Core.Models /// Returns a list of the current contents children. /// /// Current content + /// /// An enumerable list of objects + public static IEnumerable Children(this IContent content, IContentService contentService) + { + return contentService.GetChildren(content.Id); + } + + [Obsolete("Use the overload with the service reference instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public static IEnumerable Children(this IContent content) { return ApplicationContext.Current.Services.ContentService.GetChildren(content.Id); @@ -241,7 +258,15 @@ namespace Umbraco.Core.Models /// Returns a list of the current contents descendants, not including the content itself. /// /// Current content + /// /// An enumerable list of objects + public static IEnumerable Descendants(this IContent content, IContentService contentService) + { + return contentService.GetDescendants(content); + } + + [Obsolete("Use the overload with the service reference instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public static IEnumerable Descendants(this IContent content) { return ApplicationContext.Current.Services.ContentService.GetDescendants(content); @@ -251,7 +276,15 @@ namespace Umbraco.Core.Models /// Returns the parent of the current content. /// /// Current content + /// /// An object + public static IContent Parent(this IContent content, IContentService contentService) + { + return contentService.GetById(content.ParentId); + } + + [Obsolete("Use the overload with the service reference instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public static IContent Parent(this IContent content) { return ApplicationContext.Current.Services.ContentService.GetById(content.ParentId); @@ -259,11 +292,20 @@ namespace Umbraco.Core.Models #endregion #region IMedia + /// /// Returns a list of the current medias ancestors, not including the media itself. /// /// Current media + /// /// An enumerable list of objects + public static IEnumerable Ancestors(this IMedia media, IMediaService mediaService) + { + return mediaService.GetAncestors(media); + } + + [Obsolete("Use the overload with the service reference instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public static IEnumerable Ancestors(this IMedia media) { return ApplicationContext.Current.Services.MediaService.GetAncestors(media); @@ -273,7 +315,15 @@ namespace Umbraco.Core.Models /// Returns a list of the current medias children. /// /// Current media + /// /// An enumerable list of objects + public static IEnumerable Children(this IMedia media, IMediaService mediaService) + { + return mediaService.GetChildren(media.Id); + } + + [Obsolete("Use the overload with the service reference instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public static IEnumerable Children(this IMedia media) { return ApplicationContext.Current.Services.MediaService.GetChildren(media.Id); @@ -283,7 +333,15 @@ namespace Umbraco.Core.Models /// Returns a list of the current medias descendants, not including the media itself. /// /// Current media + /// /// An enumerable list of objects + public static IEnumerable Descendants(this IMedia media, IMediaService mediaService) + { + return mediaService.GetDescendants(media); + } + + [Obsolete("Use the overload with the service reference instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public static IEnumerable Descendants(this IMedia media) { return ApplicationContext.Current.Services.MediaService.GetDescendants(media); @@ -293,7 +351,15 @@ namespace Umbraco.Core.Models /// Returns the parent of the current media. /// /// Current media + /// /// An object + public static IMedia Parent(this IMedia media, IMediaService mediaService) + { + return mediaService.GetById(media.ParentId); + } + + [Obsolete("Use the overload with the service reference instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public static IMedia Parent(this IMedia media) { return ApplicationContext.Current.Services.MediaService.GetById(media.ParentId); @@ -455,7 +521,8 @@ namespace Umbraco.Core.Models /// to add property value to /// Alias of the property to save the value on /// The containing the file that will be uploaded - public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value) + /// + public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value, IDataTypeService dataTypeService) { // Ensure we get the filename without the path in IE in intranet mode // http://stackoverflow.com/questions/382464/httppostedfile-filename-different-from-ie @@ -470,7 +537,14 @@ namespace Umbraco.Core.Models .ToLower()); if (string.IsNullOrEmpty(name) == false) - SetFileOnContent(content, propertyTypeAlias, name, value.InputStream); + SetFileOnContent(content, propertyTypeAlias, name, value.InputStream, dataTypeService); + } + + [Obsolete("Use the overload with the IDataTypeService parameter instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value) + { + content.SetValue(propertyTypeAlias, value, ApplicationContext.Current.Services.DataTypeService); } /// @@ -479,6 +553,14 @@ namespace Umbraco.Core.Models /// to add property value to /// Alias of the property to save the value on /// The containing the file that will be uploaded + /// + public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFile value, IDataTypeService dataTypeService) + { + SetValue(content, propertyTypeAlias, new HttpPostedFileWrapper(value), dataTypeService); + } + + [Obsolete("Use the overload with the IDataTypeService parameter instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFile value) { SetValue(content, propertyTypeAlias, (HttpPostedFileBase)new HttpPostedFileWrapper(value)); @@ -491,6 +573,7 @@ namespace Umbraco.Core.Models /// Alias of the property to save the value on /// The containing the file that will be uploaded [Obsolete("There is no reason for this overload since HttpPostedFileWrapper inherits from HttpPostedFileBase")] + [EditorBrowsable(EditorBrowsableState.Never)] public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileWrapper value) { SetValue(content, propertyTypeAlias, (HttpPostedFileBase)value); @@ -503,15 +586,23 @@ namespace Umbraco.Core.Models /// Alias of the property to save the value on /// Name of the file /// to save to disk - public static void SetValue(this IContentBase content, string propertyTypeAlias, string fileName, Stream fileStream) + /// + public static void SetValue(this IContentBase content, string propertyTypeAlias, string fileName, Stream fileStream, IDataTypeService dataTypeService) { var name = IOHelper.SafeFileName(fileName); if (string.IsNullOrEmpty(name) == false && fileStream != null) - SetFileOnContent(content, propertyTypeAlias, name, fileStream); + SetFileOnContent(content, propertyTypeAlias, name, fileStream, dataTypeService); } - private static void SetFileOnContent(IContentBase content, string propertyTypeAlias, string filename, Stream fileStream) + [Obsolete("Use the overload with the IDataTypeService parameter instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static void SetValue(this IContentBase content, string propertyTypeAlias, string fileName, Stream fileStream) + { + content.SetValue(propertyTypeAlias, fileName, fileStream, ApplicationContext.Current.Services.DataTypeService); + } + + private static void SetFileOnContent(IContentBase content, string propertyTypeAlias, string filename, Stream fileStream, IDataTypeService dataTypeService) { var property = content.Properties.FirstOrDefault(x => x.Alias == propertyTypeAlias); if (property == null) @@ -558,7 +649,7 @@ namespace Umbraco.Core.Models if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias) { //Get Prevalues by the DataType's Id: property.PropertyType.DataTypeId - var values = ApplicationContext.Current.Services.DataTypeService.GetPreValuesByDataTypeId(property.PropertyType.DataTypeDefinitionId); + var values = dataTypeService.GetPreValuesByDataTypeId(property.PropertyType.DataTypeDefinitionId); var thumbnailSizes = values.FirstOrDefault(); //Additional thumbnails configured as prevalues on the DataType if (thumbnailSizes != null) @@ -601,10 +692,9 @@ namespace Umbraco.Core.Models #region User/Profile methods - /// - /// Gets the for the Creator of this media item. - /// + [Obsolete("Use the overload that declares the IUserService to use")] + [EditorBrowsable(EditorBrowsableState.Never)] public static IProfile GetCreatorProfile(this IMedia media) { return ApplicationContext.Current.Services.UserService.GetProfileById(media.CreatorId); @@ -617,11 +707,9 @@ namespace Umbraco.Core.Models { return userService.GetProfileById(media.CreatorId); } - - /// - /// Gets the for the Creator of this content item. - /// + [Obsolete("Use the overload that declares the IUserService to use")] + [EditorBrowsable(EditorBrowsableState.Never)] public static IProfile GetCreatorProfile(this IContentBase content) { return ApplicationContext.Current.Services.UserService.GetProfileById(content.CreatorId); @@ -634,11 +722,9 @@ namespace Umbraco.Core.Models { return userService.GetProfileById(content.CreatorId); } - - /// - /// Gets the for the Writer of this content. - /// + [Obsolete("Use the overload that declares the IUserService to use")] + [EditorBrowsable(EditorBrowsableState.Never)] public static IProfile GetWriterProfile(this IContent content) { return ApplicationContext.Current.Services.UserService.GetProfileById(content.WriterId); @@ -667,20 +753,7 @@ namespace Umbraco.Core.Models #region Tag methods - ///// - ///// Returns the tags for the given property - ///// - ///// - ///// - ///// - ///// - ///// - ///// The tags returned are only relavent for published content & saved media or members - ///// - //public static IEnumerable GetTags(this IContentBase content, string propertyTypeAlias, string tagGroup = "default") - //{ - - //} + /// /// Sets tags for the property - will add tags to the tags table and set the property value to be the comma delimited value of the tags. @@ -807,11 +880,7 @@ namespace Umbraco.Core.Models return packagingService.Export(content, true, raiseEvents: false); } - /// - /// Creates the xml representation for the object - /// - /// to generate xml for - /// Xml representation of the passed in + [Obsolete("Use the overload that declares the IPackagingService to use")] public static XElement ToXml(this IContent content) { @@ -829,11 +898,6 @@ namespace Umbraco.Core.Models return packagingService.Export(content, raiseEvents: false); } - /// - /// Creates the xml representation for the object - /// - /// to generate xml for - /// Xml representation of the passed in [Obsolete("Use the overload that declares the IPackagingService to use")] public static XElement ToXml(this IMedia media) { @@ -862,12 +926,6 @@ namespace Umbraco.Core.Models return packagingService.Export(media, true, raiseEvents: false); } - /// - /// Creates the xml representation for the object - /// - /// to generate xml for - /// Boolean indicating whether the xml should be generated for preview - /// Xml representation of the passed in [Obsolete("Use the overload that declares the IPackagingService to use")] public static XElement ToXml(this IContent content, bool isPreview) { @@ -890,11 +948,6 @@ namespace Umbraco.Core.Models return content.ToXml(packagingService); } - /// - /// Creates the xml representation for the object - /// - /// to generate xml for - /// Xml representation of the passed in [Obsolete("Use the overload that declares the IPackagingService to use")] public static XElement ToXml(this IMember member) { diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 2cc3a2a002..a17e40420b 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1336,7 +1336,7 @@ namespace Umbraco.Core.Services var c = stack.Peek(); IContent[] cc; if (c.Level == level) - while ((cc = c.Children().ToArray()).Length > 0) + while ((cc = c.Children(this).ToArray()).Length > 0) { foreach (var ci in cc) stack.Push(ci); @@ -2104,7 +2104,7 @@ namespace Umbraco.Core.Services { // ensure all ancestors are published // because content may be new its Path may be null - start with parent - var path = content.Path ?? content.Parent().Path; + var path = content.Path ?? content.Parent(this).Path; if (path != null) // if parent is also null, give up { var ancestorIds = path.Split(',') diff --git a/src/Umbraco.Tests/Models/ContentXmlTest.cs b/src/Umbraco.Tests/Models/ContentXmlTest.cs index f3b8ab520e..501e70ebbc 100644 --- a/src/Umbraco.Tests/Models/ContentXmlTest.cs +++ b/src/Umbraco.Tests/Models/ContentXmlTest.cs @@ -60,8 +60,8 @@ namespace Umbraco.Tests.Models Assert.AreEqual(content.Path, (string)element.Attribute("path")); Assert.AreEqual("", (string)element.Attribute("isDoc")); Assert.AreEqual(content.ContentType.Id.ToString(), (string)element.Attribute("nodeType")); - Assert.AreEqual(content.GetCreatorProfile().Name, (string)element.Attribute("creatorName")); - Assert.AreEqual(content.GetWriterProfile().Name, (string)element.Attribute("writerName")); + Assert.AreEqual(content.GetCreatorProfile(ServiceContext.UserService).Name, (string)element.Attribute("creatorName")); + Assert.AreEqual(content.GetWriterProfile(ServiceContext.UserService).Name, (string)element.Attribute("writerName")); Assert.AreEqual(content.WriterId.ToString(), (string)element.Attribute("writerID")); Assert.AreEqual(content.Template == null ? "0" : content.Template.Id.ToString(), (string)element.Attribute("template")); diff --git a/src/Umbraco.Tests/Models/MediaXmlTest.cs b/src/Umbraco.Tests/Models/MediaXmlTest.cs index 618fdcfec3..dbf7b67de9 100644 --- a/src/Umbraco.Tests/Models/MediaXmlTest.cs +++ b/src/Umbraco.Tests/Models/MediaXmlTest.cs @@ -60,7 +60,7 @@ namespace Umbraco.Tests.Models Assert.AreEqual(media.Path, (string)element.Attribute("path")); Assert.AreEqual("", (string)element.Attribute("isDoc")); Assert.AreEqual(media.ContentType.Id.ToString(), (string)element.Attribute("nodeType")); - Assert.AreEqual(media.GetCreatorProfile().Name, (string)element.Attribute("writerName")); + Assert.AreEqual(media.GetCreatorProfile(ServiceContext.UserService).Name, (string)element.Attribute("writerName")); Assert.AreEqual(media.CreatorId.ToString(), (string)element.Attribute("writerID")); Assert.AreEqual(media.Version.ToString(), (string)element.Attribute("version")); Assert.AreEqual("0", (string)element.Attribute("template")); diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index c5eb4b38bd..552b3f7e82 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -923,7 +923,7 @@ namespace Umbraco.Tests.Services { contentService.PublishWithChildren(c); } - var allContent = rootContent.Concat(rootContent.SelectMany(x => x.Descendants())); + var allContent = rootContent.Concat(rootContent.SelectMany(x => x.Descendants(contentService))); //for testing we need to clear out the contentXml table so we can see if it worked var provider = new NPocoUnitOfWorkProvider(Logger); using (var uow = provider.CreateUnitOfWork()) @@ -957,7 +957,7 @@ namespace Umbraco.Tests.Services { contentService.PublishWithChildren(c); } - var allContent = rootContent.Concat(rootContent.SelectMany(x => x.Descendants())).ToList(); + var allContent = rootContent.Concat(rootContent.SelectMany(x => x.Descendants(contentService))).ToList(); //for testing we need to clear out the contentXml table so we can see if it worked var provider = new NPocoUnitOfWorkProvider(Logger); @@ -1366,7 +1366,7 @@ namespace Umbraco.Tests.Services var contentService = ServiceContext.ContentService; var temp = contentService.GetById(NodeDto.NodeIdSeed + 1); Assert.AreEqual("Home", temp.Name); - Assert.AreEqual(2, temp.Children().Count()); + Assert.AreEqual(2, temp.Children(contentService).Count()); // Act var copy = contentService.Copy(temp, temp.ParentId, false, true, 0); @@ -1376,10 +1376,10 @@ namespace Umbraco.Tests.Services Assert.That(copy, Is.Not.Null); Assert.That(copy.Id, Is.Not.EqualTo(content.Id)); Assert.AreNotSame(content, copy); - Assert.AreEqual(2, copy.Children().Count()); + Assert.AreEqual(2, copy.Children(contentService).Count()); var child = contentService.GetById(NodeDto.NodeIdSeed + 2); - var childCopy = copy.Children().First(); + var childCopy = copy.Children(contentService).First(); Assert.AreEqual(childCopy.Name, child.Name); Assert.AreNotEqual(childCopy.Id, child.Id); Assert.AreNotEqual(childCopy.Key, child.Key); @@ -1392,7 +1392,7 @@ namespace Umbraco.Tests.Services var contentService = ServiceContext.ContentService; var temp = contentService.GetById(NodeDto.NodeIdSeed + 1); Assert.AreEqual("Home", temp.Name); - Assert.AreEqual(2, temp.Children().Count()); + Assert.AreEqual(2, temp.Children(contentService).Count()); // Act var copy = contentService.Copy(temp, temp.ParentId, false, false, 0); @@ -1402,7 +1402,7 @@ namespace Umbraco.Tests.Services Assert.That(copy, Is.Not.Null); Assert.That(copy.Id, Is.Not.EqualTo(content.Id)); Assert.AreNotSame(content, copy); - Assert.AreEqual(0, copy.Children().Count()); + Assert.AreEqual(0, copy.Children(contentService).Count()); } [Test] diff --git a/src/Umbraco.Web.UI/config/ClientDependency.config b/src/Umbraco.Web.UI/config/ClientDependency.config index b086b8a90b..8a205d985d 100644 --- a/src/Umbraco.Web.UI/config/ClientDependency.config +++ b/src/Umbraco.Web.UI/config/ClientDependency.config @@ -10,7 +10,7 @@ NOTES: * Compression/Combination/Minification is not enabled unless debug="false" is specified on the 'compiliation' element in the web.config * A new version will invalidate both client and server cache and create new persisted files --> - + Logging, root cache stuff moved to Cache, XmlHelper --> Xml, TypeFinder, etc.. --> Plugins --- src/Umbraco.Core/ApplicationContext.cs | 1 + src/Umbraco.Core/{ => Cache}/CacheHelper.cs | 849 +++++---- .../{ => Cache}/CacheRefreshersResolver.cs | 91 +- .../Cache/DictionaryCacheProviderBase.cs | 1 + .../Cache/ObjectCacheRuntimeCacheProvider.cs | 1 + .../{ => Collections}/ConcurrentHashSet.cs | 332 ++-- .../{ => Collections}/ObservableDictionary.cs | 342 ++-- .../UmbracoSettings/ContentElement.cs | 14 +- .../UmbracoSettings/IContentSection.cs | 1 + src/Umbraco.Core/CoreBootManager.cs | 2 +- .../RepositoryCompositionRoot.cs | 2 + .../ServicesCompositionRoot.cs | 1 + src/Umbraco.Core/DisposableTimer.cs | 1 - src/Umbraco.Core/Dynamics/DynamicXml.cs | 1 + .../Dynamics/DynamicXmlConverter.cs | 1 + .../Dynamics/ExtensionMethodFinder.cs | 1 + .../Events/MacroErrorEventArgs.cs | 1 + .../IO/FileSystemProviderManager.cs | 1 + src/Umbraco.Core/IconPickerBehaviour.cs | 27 - .../{Profiling => Logging}/IProfiler.cs | 84 +- .../{Profiling => Logging}/LogProfiler.cs | 80 +- .../ProfilerExtensions.cs | 68 +- .../ProfilerResolver.cs | 82 +- src/Umbraco.Core/Logging/ProfilingLogger.cs | 1 - .../{Profiling => Logging}/WebProfiler.cs | 327 ++-- .../{ => Macros}/MacroErrorBehaviour.cs | 46 +- src/Umbraco.Core/Macros/MacroTagParser.cs | 1 + src/Umbraco.Core/Models/DeepCloneHelper.cs | 1 + .../EntityBase/TracksChangesEntityBase.cs | 1 + .../Models/MacroPropertyCollection.cs | 1 + .../PublishedContent/PublishedPropertyType.cs | 1 + src/Umbraco.Core/ObjectExtensions.cs | 1 + .../ManyObjectsResolverBase.cs | 1 + .../Querying/BaseExpressionHelper.cs | 1 + .../Repositories/AuditRepository.cs | 1 + .../Repositories/ContentPreviewRepository.cs | 1 + .../Repositories/ContentTypeRepositoryBase.cs | 1 + .../Repositories/ContentXmlRepository.cs | 1 + .../Repositories/ExternalLoginRepository.cs | 1 + .../Interfaces/DataTypeContainerRepository.cs | 3 +- .../DocumentTypeContainerRepository.cs | 3 +- .../MediaTypeContainerRepository.cs | 3 +- .../Repositories/MacroRepository.cs | 1 + .../Repositories/MediaRepository.cs | 1 + .../Repositories/MemberRepository.cs | 1 + .../Repositories/MigrationEntryRepository.cs | 1 + .../Repositories/NPocoRepositoryBase.cs | 1 + .../Repositories/RecycleBinRepository.cs | 1 + .../Repositories/RelationRepository.cs | 1 + .../Repositories/RelationTypeRepository.cs | 1 + .../Repositories/SimpleGetRepository.cs | 1 + .../Persistence/Repositories/TagRepository.cs | 1 + .../Repositories/TaskRepository.cs | 1 + .../Repositories/TaskTypeRepository.cs | 1 + .../Repositories/UserRepository.cs | 1 + .../Repositories/UserTypeRepository.cs | 1 + .../Repositories/VersionableRepositoryBase.cs | 1 + .../HideFromTypeFinderAttribute.cs | 2 +- .../{ => Plugins}/PluginManager.cs | 1573 ++++++++--------- src/Umbraco.Core/{ => Plugins}/TypeFinder.cs | 1371 +++++++------- src/Umbraco.Core/{ => Plugins}/TypeHelper.cs | 619 ++++--- .../PropertyEditors/PreValueEditor.cs | 1 + src/Umbraco.Core/ServiceProviderExtensions.cs | 1 + src/Umbraco.Core/TypeExtensions.cs | 1 + src/Umbraco.Core/Umbraco.Core.csproj | 32 +- src/Umbraco.Core/{ => Xml}/XmlHelper.cs | 938 +++++----- src/Umbraco.Tests/ApplicationContextTests.cs | 1 + .../ApplicationUrlHelperTests.cs | 1 + .../UmbracoSettings/ContentElementTests.cs | 1 + .../Migrations/Upgrades/BaseUpgradeTest.cs | 1 + src/Umbraco.Tests/MockTests.cs | 1 + .../Models/Mapping/AutoMapperTests.cs | 5 +- .../Persistence/DatabaseContextTests.cs | 1 + .../MigrationStartupHandlerTests.cs | 1 + .../Repositories/ContentTypeRepositoryTest.cs | 1 + .../Repositories/LanguageRepositoryTest.cs | 1 + .../Repositories/MacroRepositoryTest.cs | 1 + .../Repositories/MemberTypeRepositoryTest.cs | 1 + .../Repositories/RelationRepositoryTest.cs | 1 + .../RelationTypeRepositoryTest.cs | 1 + .../Repositories/UserRepositoryTest.cs | 1 + .../Repositories/UserTypeRepositoryTest.cs | 1 + .../Plugins/PluginManagerExtensions.cs | 1 + .../Plugins/PluginManagerTests.cs | 1 + src/Umbraco.Tests/Plugins/TypeFinderTests.cs | 1 + src/Umbraco.Tests/Plugins/TypeHelperTests.cs | 1 + .../MultiValuePropertyEditorTests.cs | 1 + .../DynamicDocumentTestsBase.cs | 1 + .../DynamicXmlConverterTests.cs | 1 + .../PublishedContent/DynamicXmlTests.cs | 1 + .../PublishedContentMoreTests.cs | 1 + .../PublishedContent/PublishedContentTests.cs | 1 + .../Resolvers/ResolverBaseTest.cs | 1 + .../Routing/RenderRouteHandlerTests.cs | 1 + .../Security/BackOfficeCookieManagerTests.cs | 1 + .../Services/MacroServiceTests.cs | 1 + .../Services/ThreadSafetyServiceTest.cs | 1 + .../Templates/TemplateRepositoryTests.cs | 1 + .../TestHelpers/BaseDatabaseFactoryTest.cs | 1 + .../TestHelpers/BaseUmbracoApplicationTest.cs | 1 + .../TestHelpers/BaseUsingSqlCeSyntax.cs | 1 + .../Stubs/TestControllerFactory.cs | 1 + src/Umbraco.Tests/TestHelpers/TestObjects.cs | 1 + src/Umbraco.Tests/TestHelpers/TestProfiler.cs | 1 + src/Umbraco.Tests/UI/LegacyDialogTests.cs | 1 + ...RenderIndexActionSelectorAttributeTests.cs | 1 + .../Web/Mvc/SurfaceControllerTests.cs | 1 + .../Web/Mvc/UmbracoViewPageTests.cs | 1 + .../Web/WebExtensionMethodTests.cs | 1 + src/Umbraco.Tests/XmlHelperTests.cs | 1 + .../Cache/DistributedCacheExtensions.cs | 1 + .../Editors/BackOfficeController.cs | 1 + src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 1 + .../Install/InstallStatusTracker.cs | 1 + src/Umbraco.Web/LightInjectExtensions.cs | 1 + .../Mvc/DefaultRenderMvcControllerResolver.cs | 1 + src/Umbraco.Web/Mvc/PluginControllerArea.cs | 1 + src/Umbraco.Web/Mvc/ProfilingView.cs | 1 + src/Umbraco.Web/Mvc/ProfilingViewEngine.cs | 1 + src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 1 + src/Umbraco.Web/Mvc/UrlHelperExtensions.cs | 1 + src/Umbraco.Web/PluginManagerExtensions.cs | 1 + .../ImageCropDataSetConverter.cs | 1 + .../XmlPublishedCache/XmlPublishedProperty.cs | 1 + src/Umbraco.Web/PublishedContentQuery.cs | 1 + .../AuthenticationOptionsExtensions.cs | 1 + .../Services/ApplicationTreeService.cs | 1 + src/Umbraco.Web/Services/SectionService.cs | 1 + .../Trees/ApplicationTreeExtensions.cs | 1 + .../Trees/LegacyBaseTreeAttribute.cs | 1 + src/Umbraco.Web/UmbracoModule.cs | 1 + .../FilterAllowedOutgoingMediaAttribute.cs | 1 + src/Umbraco.Web/WebBootManager.cs | 2 +- .../ExamineManagementApiController.cs | 1 + src/Umbraco.Web/_Legacy/Actions/Action.cs | 3 +- .../_Legacy/PackageActions/PackageHelper.cs | 1 + .../PackageActions/addDashboardSection.cs | 1 + .../PackageActions/addProxyFeedHost.cs | 1 + .../PackageActions/addStringToHtmlElement.cs | 1 + .../umbraco.presentation/content.cs | 1 + .../umbraco.presentation/library.cs | 1 + src/Umbraco.Web/umbraco.presentation/macro.cs | 1 + .../umbraco.presentation/template.cs | 1 + .../umbraco/Trees/TreeDefinitionCollection.cs | 1 + .../umbraco/dashboard/FeedProxy.aspx.cs | 1 + .../umbraco/dialogs/search.aspx.cs | 1 + .../umbraco/translation/xml.aspx.cs | 1 + .../webservices/CacheRefresher.asmx.cs | 1 + .../umbraco/webservices/Developer.asmx.cs | 1 + src/UmbracoExamine/UmbracoContentIndexer.cs | 1 + src/umbraco.cms/businesslogic/Dictionary.cs | 1 + .../businesslogic/Packager/Installer.cs | 1 + .../businesslogic/Packager/data.cs | 1 + src/umbraco.cms/businesslogic/macro/Macro.cs | 1 + .../businesslogic/macro/MacroProperty.cs | 1 + .../businesslogic/member/Member.cs | 1 + .../businesslogic/template/Template.cs | 1 + .../businesslogic/web/StyleSheet.cs | 1 + 158 files changed, 3549 insertions(+), 3483 deletions(-) rename src/Umbraco.Core/{ => Cache}/CacheHelper.cs (96%) rename src/Umbraco.Core/{ => Cache}/CacheRefreshersResolver.cs (93%) rename src/Umbraco.Core/{ => Collections}/ConcurrentHashSet.cs (97%) rename src/Umbraco.Core/{ => Collections}/ObservableDictionary.cs (95%) delete mode 100644 src/Umbraco.Core/IconPickerBehaviour.cs rename src/Umbraco.Core/{Profiling => Logging}/IProfiler.cs (93%) rename src/Umbraco.Core/{Profiling => Logging}/LogProfiler.cs (83%) rename src/Umbraco.Core/{Profiling => Logging}/ProfilerExtensions.cs (94%) rename src/Umbraco.Core/{Profiling => Logging}/ProfilerResolver.cs (93%) rename src/Umbraco.Core/{Profiling => Logging}/WebProfiler.cs (96%) rename src/Umbraco.Core/{ => Macros}/MacroErrorBehaviour.cs (93%) rename src/Umbraco.Core/{ => Plugins}/HideFromTypeFinderAttribute.cs (90%) rename src/Umbraco.Core/{ => Plugins}/PluginManager.cs (97%) rename src/Umbraco.Core/{ => Plugins}/TypeFinder.cs (97%) rename src/Umbraco.Core/{ => Plugins}/TypeHelper.cs (97%) rename src/Umbraco.Core/{ => Xml}/XmlHelper.cs (97%) diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs index 8c7f62f980..00dbefa02c 100644 --- a/src/Umbraco.Core/ApplicationContext.cs +++ b/src/Umbraco.Core/ApplicationContext.cs @@ -1,6 +1,7 @@ using System; using System.Configuration; using System.Threading; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.ObjectResolution; diff --git a/src/Umbraco.Core/CacheHelper.cs b/src/Umbraco.Core/Cache/CacheHelper.cs similarity index 96% rename from src/Umbraco.Core/CacheHelper.cs rename to src/Umbraco.Core/Cache/CacheHelper.cs index 0dc5f5b00f..cbf2836232 100644 --- a/src/Umbraco.Core/CacheHelper.cs +++ b/src/Umbraco.Core/Cache/CacheHelper.cs @@ -1,428 +1,421 @@ -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Web; -using System.Web.Caching; -using Umbraco.Core.Cache; -using Umbraco.Core.Logging; - -namespace Umbraco.Core -{ - /// - /// Class that is exposed by the ApplicationContext for application wide caching purposes - /// - public class CacheHelper - { - private static readonly ICacheProvider NullRequestCache = new NullCacheProvider(); - private static readonly ICacheProvider NullStaticCache = new NullCacheProvider(); - private static readonly IRuntimeCacheProvider NullRuntimeCache = new NullCacheProvider(); - - /// - /// Creates a cache helper with disabled caches - /// - /// - /// - /// Good for unit testing - /// - public static CacheHelper CreateDisabledCacheHelper() - { - return new CacheHelper(NullRuntimeCache, NullStaticCache, NullRequestCache, new IsolatedRuntimeCache(t => NullRuntimeCache)); - } - - /// - /// Initializes a new instance for use in the web - /// - public CacheHelper() - : this( - new HttpRuntimeCacheProvider(HttpRuntime.Cache), - new StaticCacheProvider(), - new HttpRequestCacheProvider(), - new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider())) - { - } - - /// - /// Initializes a new instance for use in the web - /// - /// - public CacheHelper(System.Web.Caching.Cache cache) - : this( - new HttpRuntimeCacheProvider(cache), - new StaticCacheProvider(), - new HttpRequestCacheProvider(), - new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider())) - { - } - - [Obsolete("Use the constructor the specifies all dependencies")] - [EditorBrowsable(EditorBrowsableState.Never)] - public CacheHelper( - IRuntimeCacheProvider httpCacheProvider, - ICacheProvider staticCacheProvider, - ICacheProvider requestCacheProvider) - : this(httpCacheProvider, staticCacheProvider, requestCacheProvider, new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider())) - { - } - - /// - /// Initializes a new instance based on the provided providers - /// - /// - /// - /// - /// - public CacheHelper( - IRuntimeCacheProvider httpCacheProvider, - ICacheProvider staticCacheProvider, - ICacheProvider requestCacheProvider, - IsolatedRuntimeCache isolatedCacheManager) - { - if (httpCacheProvider == null) throw new ArgumentNullException("httpCacheProvider"); - if (staticCacheProvider == null) throw new ArgumentNullException("staticCacheProvider"); - if (requestCacheProvider == null) throw new ArgumentNullException("requestCacheProvider"); - if (isolatedCacheManager == null) throw new ArgumentNullException("isolatedCacheManager"); - RuntimeCache = httpCacheProvider; - StaticCache = staticCacheProvider; - RequestCache = requestCacheProvider; - IsolatedRuntimeCache = isolatedCacheManager; - } - - /// - /// Returns the current Request cache - /// - public ICacheProvider RequestCache { get; internal set; } - - /// - /// Returns the current Runtime cache - /// - public ICacheProvider StaticCache { get; internal set; } - - /// - /// Returns the current Runtime cache - /// - public IRuntimeCacheProvider RuntimeCache { get; internal set; } - - /// - /// Returns the current Isolated Runtime cache manager - /// - public IsolatedRuntimeCache IsolatedRuntimeCache { get; internal set; } - - #region Legacy Runtime/Http Cache accessors - - /// - /// Clears the item in umbraco's runtime cache - /// - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public void ClearAllCache() - { - RuntimeCache.ClearAllCache(); - IsolatedRuntimeCache.ClearAllCaches(); - } - - /// - /// Clears the item in umbraco's runtime cache with the given key - /// - /// Key - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public void ClearCacheItem(string key) - { - RuntimeCache.ClearCacheItem(key); - } - - - /// - /// Clears all objects in the System.Web.Cache with the System.Type name as the - /// input parameter. (using [object].GetType()) - /// - /// The name of the System.Type which should be cleared from cache ex "System.Xml.XmlDocument" - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - public void ClearCacheObjectTypes(string typeName) - { - RuntimeCache.ClearCacheObjectTypes(typeName); - } - - /// - /// Clears all objects in the System.Web.Cache with the System.Type specified - /// - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public void ClearCacheObjectTypes() - { - RuntimeCache.ClearCacheObjectTypes(); - } - - /// - /// Clears all cache items that starts with the key passed. - /// - /// The start of the key - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public void ClearCacheByKeySearch(string keyStartsWith) - { - RuntimeCache.ClearCacheByKeySearch(keyStartsWith); - } - - /// - /// Clears all cache items that have a key that matches the regular expression - /// - /// - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public void ClearCacheByKeyExpression(string regexString) - { - RuntimeCache.ClearCacheByKeyExpression(regexString); - } - - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) - { - return RuntimeCache.GetCacheItemsByKeySearch(keyStartsWith); - } - - /// - /// Returns a cache item by key, does not update the cache if it isn't there. - /// - /// - /// - /// - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public TT GetCacheItem(string cacheKey) - { - return RuntimeCache.GetCacheItem(cacheKey); - } - - /// - /// Gets (and adds if necessary) an item from the cache with all of the default parameters - /// - /// - /// - /// - /// - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public TT GetCacheItem(string cacheKey, Func getCacheItem) - { - return RuntimeCache.GetCacheItem(cacheKey, getCacheItem); - - } - - /// - /// Gets (and adds if necessary) an item from the cache with the specified absolute expiration date (from NOW) - /// - /// - /// - /// This will set an absolute expiration from now until the timeout - /// - /// - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public TT GetCacheItem(string cacheKey, - TimeSpan timeout, Func getCacheItem) - { - return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout); - - } - - /// - /// Gets (and adds if necessary) an item from the cache with the specified absolute expiration date (from NOW) - /// - /// - /// - /// - /// This will set an absolute expiration from now until the timeout - /// - /// - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public TT GetCacheItem(string cacheKey, - CacheItemRemovedCallback refreshAction, TimeSpan timeout, - Func getCacheItem) - { - return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, removedCallback: refreshAction); - - } - - /// - /// Gets (and adds if necessary) an item from the cache with the specified absolute expiration date (from NOW) - /// - /// - /// - /// - /// - /// This will set an absolute expiration from now until the timeout - /// - /// - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public TT GetCacheItem(string cacheKey, - CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan timeout, - Func getCacheItem) - { - return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction); - - } - - /// - /// Gets (and adds if necessary) an item from the cache with the specified absolute expiration date (from NOW) - /// - /// - /// - /// - /// - /// - /// This will set an absolute expiration from now until the timeout - /// - /// - [Obsolete("Do not use this method, we no longer support the caching overloads with references to CacheDependency, use the overloads specifying a file collection instead")] - public TT GetCacheItem(string cacheKey, - CacheItemPriority priority, - CacheItemRemovedCallback refreshAction, - CacheDependency cacheDependency, - TimeSpan timeout, - Func getCacheItem) - { - var cache = GetHttpRuntimeCacheProvider(RuntimeCache); - if (cache != null) - { - var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency); - return result == null ? default(TT) : result.TryConvertTo().Result; - } - throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); - } - - /// - /// Gets (and adds if necessary) an item from the cache - /// - /// - /// - /// - /// - /// - /// - [Obsolete("Do not use this method, we no longer support the caching overloads with references to CacheDependency, use the overloads specifying a file collection instead")] - public TT GetCacheItem(string cacheKey, - CacheItemPriority priority, - CacheDependency cacheDependency, - Func getCacheItem) - { - var cache = GetHttpRuntimeCacheProvider(RuntimeCache); - if (cache != null) - { - var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), null, false, priority, null, cacheDependency); - return result == null ? default(TT) : result.TryConvertTo().Result; - } - throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); - } - - /// - /// Inserts an item into the cache, if it already exists in the cache it will be replaced - /// - /// - /// - /// - /// - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public void InsertCacheItem(string cacheKey, - CacheItemPriority priority, - Func getCacheItem) - { - RuntimeCache.InsertCacheItem(cacheKey, getCacheItem, priority: priority); - - } - - /// - /// Inserts an item into the cache, if it already exists in the cache it will be replaced - /// - /// - /// - /// - /// This will set an absolute expiration from now until the timeout - /// - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public void InsertCacheItem(string cacheKey, - CacheItemPriority priority, - TimeSpan timeout, - Func getCacheItem) - { - RuntimeCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority); - } - - /// - /// Inserts an item into the cache, if it already exists in the cache it will be replaced - /// - /// - /// - /// - /// - /// This will set an absolute expiration from now until the timeout - /// - [Obsolete("Do not use this method, we no longer support the caching overloads with references to CacheDependency, use the overloads specifying a file collection instead")] - public void InsertCacheItem(string cacheKey, - CacheItemPriority priority, - CacheDependency cacheDependency, - TimeSpan timeout, - Func getCacheItem) - { - var cache = GetHttpRuntimeCacheProvider(RuntimeCache); - if (cache != null) - { - cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, null, cacheDependency); - } - throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); - } - - /// - /// Inserts an item into the cache, if it already exists in the cache it will be replaced - /// - /// - /// - /// - /// - /// - /// This will set an absolute expiration from now until the timeout - /// - [Obsolete("Do not use this method, we no longer support the caching overloads with references to CacheDependency, use the overloads specifying a file collection instead")] - public void InsertCacheItem(string cacheKey, - CacheItemPriority priority, - CacheItemRemovedCallback refreshAction, - CacheDependency cacheDependency, - TimeSpan? timeout, - Func getCacheItem) - { - var cache = GetHttpRuntimeCacheProvider(RuntimeCache); - if (cache != null) - { - cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency); - } - throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); - } - #endregion - - private HttpRuntimeCacheProvider GetHttpRuntimeCacheProvider(IRuntimeCacheProvider runtimeCache) - { - HttpRuntimeCacheProvider cache; - var wrapper = RuntimeCache as IRuntimeCacheProviderWrapper; - if (wrapper != null) - { - cache = wrapper.InnerProvider as HttpRuntimeCacheProvider; - } - else - { - cache = RuntimeCache as HttpRuntimeCacheProvider; - } - return cache; - } - } - -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Web; +using System.Web.Caching; + +namespace Umbraco.Core.Cache +{ + /// + /// Class that is exposed by the ApplicationContext for application wide caching purposes + /// + public class CacheHelper + { + private static readonly ICacheProvider NullRequestCache = new NullCacheProvider(); + private static readonly ICacheProvider NullStaticCache = new NullCacheProvider(); + private static readonly IRuntimeCacheProvider NullRuntimeCache = new NullCacheProvider(); + + /// + /// Creates a cache helper with disabled caches + /// + /// + /// + /// Good for unit testing + /// + public static CacheHelper CreateDisabledCacheHelper() + { + return new CacheHelper(NullRuntimeCache, NullStaticCache, NullRequestCache, new IsolatedRuntimeCache(t => NullRuntimeCache)); + } + + /// + /// Initializes a new instance for use in the web + /// + public CacheHelper() + : this( + new HttpRuntimeCacheProvider(HttpRuntime.Cache), + new StaticCacheProvider(), + new HttpRequestCacheProvider(), + new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider())) + { + } + + /// + /// Initializes a new instance for use in the web + /// + /// + public CacheHelper(System.Web.Caching.Cache cache) + : this( + new HttpRuntimeCacheProvider(cache), + new StaticCacheProvider(), + new HttpRequestCacheProvider(), + new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider())) + { + } + + [Obsolete("Use the constructor the specifies all dependencies")] + [EditorBrowsable(EditorBrowsableState.Never)] + public CacheHelper( + IRuntimeCacheProvider httpCacheProvider, + ICacheProvider staticCacheProvider, + ICacheProvider requestCacheProvider) + : this(httpCacheProvider, staticCacheProvider, requestCacheProvider, new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider())) + { + } + + /// + /// Initializes a new instance based on the provided providers + /// + /// + /// + /// + /// + public CacheHelper( + IRuntimeCacheProvider httpCacheProvider, + ICacheProvider staticCacheProvider, + ICacheProvider requestCacheProvider, + IsolatedRuntimeCache isolatedCacheManager) + { + if (httpCacheProvider == null) throw new ArgumentNullException("httpCacheProvider"); + if (staticCacheProvider == null) throw new ArgumentNullException("staticCacheProvider"); + if (requestCacheProvider == null) throw new ArgumentNullException("requestCacheProvider"); + if (isolatedCacheManager == null) throw new ArgumentNullException("isolatedCacheManager"); + RuntimeCache = httpCacheProvider; + StaticCache = staticCacheProvider; + RequestCache = requestCacheProvider; + IsolatedRuntimeCache = isolatedCacheManager; + } + + /// + /// Returns the current Request cache + /// + public ICacheProvider RequestCache { get; internal set; } + + /// + /// Returns the current Runtime cache + /// + public ICacheProvider StaticCache { get; internal set; } + + /// + /// Returns the current Runtime cache + /// + public IRuntimeCacheProvider RuntimeCache { get; internal set; } + + /// + /// Returns the current Isolated Runtime cache manager + /// + public IsolatedRuntimeCache IsolatedRuntimeCache { get; internal set; } + + #region Legacy Runtime/Http Cache accessors + + /// + /// Clears the item in umbraco's runtime cache + /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public void ClearAllCache() + { + RuntimeCache.ClearAllCache(); + IsolatedRuntimeCache.ClearAllCaches(); + } + + /// + /// Clears the item in umbraco's runtime cache with the given key + /// + /// Key + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public void ClearCacheItem(string key) + { + RuntimeCache.ClearCacheItem(key); + } + + + /// + /// Clears all objects in the System.Web.Cache with the System.Type name as the + /// input parameter. (using [object].GetType()) + /// + /// The name of the System.Type which should be cleared from cache ex "System.Xml.XmlDocument" + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + public void ClearCacheObjectTypes(string typeName) + { + RuntimeCache.ClearCacheObjectTypes(typeName); + } + + /// + /// Clears all objects in the System.Web.Cache with the System.Type specified + /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public void ClearCacheObjectTypes() + { + RuntimeCache.ClearCacheObjectTypes(); + } + + /// + /// Clears all cache items that starts with the key passed. + /// + /// The start of the key + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public void ClearCacheByKeySearch(string keyStartsWith) + { + RuntimeCache.ClearCacheByKeySearch(keyStartsWith); + } + + /// + /// Clears all cache items that have a key that matches the regular expression + /// + /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public void ClearCacheByKeyExpression(string regexString) + { + RuntimeCache.ClearCacheByKeyExpression(regexString); + } + + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) + { + return RuntimeCache.GetCacheItemsByKeySearch(keyStartsWith); + } + + /// + /// Returns a cache item by key, does not update the cache if it isn't there. + /// + /// + /// + /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public TT GetCacheItem(string cacheKey) + { + return RuntimeCache.GetCacheItem(cacheKey); + } + + /// + /// Gets (and adds if necessary) an item from the cache with all of the default parameters + /// + /// + /// + /// + /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public TT GetCacheItem(string cacheKey, Func getCacheItem) + { + return RuntimeCache.GetCacheItem(cacheKey, getCacheItem); + + } + + /// + /// Gets (and adds if necessary) an item from the cache with the specified absolute expiration date (from NOW) + /// + /// + /// + /// This will set an absolute expiration from now until the timeout + /// + /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public TT GetCacheItem(string cacheKey, + TimeSpan timeout, Func getCacheItem) + { + return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout); + + } + + /// + /// Gets (and adds if necessary) an item from the cache with the specified absolute expiration date (from NOW) + /// + /// + /// + /// + /// This will set an absolute expiration from now until the timeout + /// + /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public TT GetCacheItem(string cacheKey, + CacheItemRemovedCallback refreshAction, TimeSpan timeout, + Func getCacheItem) + { + return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, removedCallback: refreshAction); + + } + + /// + /// Gets (and adds if necessary) an item from the cache with the specified absolute expiration date (from NOW) + /// + /// + /// + /// + /// + /// This will set an absolute expiration from now until the timeout + /// + /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public TT GetCacheItem(string cacheKey, + CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan timeout, + Func getCacheItem) + { + return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction); + + } + + /// + /// Gets (and adds if necessary) an item from the cache with the specified absolute expiration date (from NOW) + /// + /// + /// + /// + /// + /// + /// This will set an absolute expiration from now until the timeout + /// + /// + [Obsolete("Do not use this method, we no longer support the caching overloads with references to CacheDependency, use the overloads specifying a file collection instead")] + public TT GetCacheItem(string cacheKey, + CacheItemPriority priority, + CacheItemRemovedCallback refreshAction, + CacheDependency cacheDependency, + TimeSpan timeout, + Func getCacheItem) + { + var cache = GetHttpRuntimeCacheProvider(RuntimeCache); + if (cache != null) + { + var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency); + return result == null ? default(TT) : result.TryConvertTo().Result; + } + throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); + } + + /// + /// Gets (and adds if necessary) an item from the cache + /// + /// + /// + /// + /// + /// + /// + [Obsolete("Do not use this method, we no longer support the caching overloads with references to CacheDependency, use the overloads specifying a file collection instead")] + public TT GetCacheItem(string cacheKey, + CacheItemPriority priority, + CacheDependency cacheDependency, + Func getCacheItem) + { + var cache = GetHttpRuntimeCacheProvider(RuntimeCache); + if (cache != null) + { + var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), null, false, priority, null, cacheDependency); + return result == null ? default(TT) : result.TryConvertTo().Result; + } + throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); + } + + /// + /// Inserts an item into the cache, if it already exists in the cache it will be replaced + /// + /// + /// + /// + /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public void InsertCacheItem(string cacheKey, + CacheItemPriority priority, + Func getCacheItem) + { + RuntimeCache.InsertCacheItem(cacheKey, getCacheItem, priority: priority); + + } + + /// + /// Inserts an item into the cache, if it already exists in the cache it will be replaced + /// + /// + /// + /// + /// This will set an absolute expiration from now until the timeout + /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public void InsertCacheItem(string cacheKey, + CacheItemPriority priority, + TimeSpan timeout, + Func getCacheItem) + { + RuntimeCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority); + } + + /// + /// Inserts an item into the cache, if it already exists in the cache it will be replaced + /// + /// + /// + /// + /// + /// This will set an absolute expiration from now until the timeout + /// + [Obsolete("Do not use this method, we no longer support the caching overloads with references to CacheDependency, use the overloads specifying a file collection instead")] + public void InsertCacheItem(string cacheKey, + CacheItemPriority priority, + CacheDependency cacheDependency, + TimeSpan timeout, + Func getCacheItem) + { + var cache = GetHttpRuntimeCacheProvider(RuntimeCache); + if (cache != null) + { + cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, null, cacheDependency); + } + throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); + } + + /// + /// Inserts an item into the cache, if it already exists in the cache it will be replaced + /// + /// + /// + /// + /// + /// + /// This will set an absolute expiration from now until the timeout + /// + [Obsolete("Do not use this method, we no longer support the caching overloads with references to CacheDependency, use the overloads specifying a file collection instead")] + public void InsertCacheItem(string cacheKey, + CacheItemPriority priority, + CacheItemRemovedCallback refreshAction, + CacheDependency cacheDependency, + TimeSpan? timeout, + Func getCacheItem) + { + var cache = GetHttpRuntimeCacheProvider(RuntimeCache); + if (cache != null) + { + cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency); + } + throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); + } + #endregion + + private HttpRuntimeCacheProvider GetHttpRuntimeCacheProvider(IRuntimeCacheProvider runtimeCache) + { + HttpRuntimeCacheProvider cache; + var wrapper = RuntimeCache as IRuntimeCacheProviderWrapper; + if (wrapper != null) + { + cache = wrapper.InnerProvider as HttpRuntimeCacheProvider; + } + else + { + cache = RuntimeCache as HttpRuntimeCacheProvider; + } + return cache; + } + } + +} diff --git a/src/Umbraco.Core/CacheRefreshersResolver.cs b/src/Umbraco.Core/Cache/CacheRefreshersResolver.cs similarity index 93% rename from src/Umbraco.Core/CacheRefreshersResolver.cs rename to src/Umbraco.Core/Cache/CacheRefreshersResolver.cs index 5ab724fbab..4f91739068 100644 --- a/src/Umbraco.Core/CacheRefreshersResolver.cs +++ b/src/Umbraco.Core/Cache/CacheRefreshersResolver.cs @@ -1,46 +1,45 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using LightInject; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; -using Umbraco.Core.Cache; - -namespace Umbraco.Core -{ - - - /// - /// A resolver to return all ICacheRefresher objects - /// - internal sealed class CacheRefreshersResolver : ContainerLazyManyObjectsResolver - { - /// - /// Constructor - /// - /// - /// - /// - internal CacheRefreshersResolver(IServiceContainer serviceContainer, ILogger logger, Func> refreshers) - : base(serviceContainer, logger, refreshers) - { - - } - - /// - /// Gets the implementations. - /// - public IEnumerable CacheRefreshers => Values; - - /// - /// Returns an instance for the type identified by its unique type identifier. - /// - /// The type identifier. - /// The value of the type uniquely identified by . - public ICacheRefresher GetById(Guid id) - { - return Values.FirstOrDefault(x => x.UniqueIdentifier == id); - } - - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using LightInject; +using Umbraco.Core.Logging; +using Umbraco.Core.ObjectResolution; + +namespace Umbraco.Core.Cache +{ + + + /// + /// A resolver to return all ICacheRefresher objects + /// + internal sealed class CacheRefreshersResolver : ContainerLazyManyObjectsResolver + { + /// + /// Constructor + /// + /// + /// + /// + internal CacheRefreshersResolver(IServiceContainer serviceContainer, ILogger logger, Func> refreshers) + : base(serviceContainer, logger, refreshers) + { + + } + + /// + /// Gets the implementations. + /// + public IEnumerable CacheRefreshers => Values; + + /// + /// Returns an instance for the type identified by its unique type identifier. + /// + /// The type identifier. + /// The value of the type uniquely identified by . + public ICacheRefresher GetById(Guid id) + { + return Values.FirstOrDefault(x => x.UniqueIdentifier == id); + } + + } +} diff --git a/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs b/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs index ae840d7b0e..1c5528dda1 100644 --- a/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs +++ b/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using Umbraco.Core.Plugins; namespace Umbraco.Core.Cache { diff --git a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs index 8afa5639f1..811a6e5310 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs @@ -8,6 +8,7 @@ using System.Text.RegularExpressions; using System.Threading; using System.Web.Caching; using Umbraco.Core.Logging; +using Umbraco.Core.Plugins; using CacheItemPriority = System.Web.Caching.CacheItemPriority; namespace Umbraco.Core.Cache diff --git a/src/Umbraco.Core/ConcurrentHashSet.cs b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs similarity index 97% rename from src/Umbraco.Core/ConcurrentHashSet.cs rename to src/Umbraco.Core/Collections/ConcurrentHashSet.cs index 1e318b36d1..9596c718a5 100644 --- a/src/Umbraco.Core/ConcurrentHashSet.cs +++ b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs @@ -1,167 +1,167 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Threading; - -namespace Umbraco.Core -{ - /// - /// A thread-safe representation of a . - /// Enumerating this collection is thread-safe and will only operate on a clone that is generated before returning the enumerator. - /// - /// - [Serializable] - public class ConcurrentHashSet : ICollection - { - private readonly HashSet _innerSet = new HashSet(); - private readonly ReaderWriterLockSlim _instanceLocker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// A that can be used to iterate through the collection. - /// - /// 1 - public IEnumerator GetEnumerator() - { - return GetThreadSafeClone().GetEnumerator(); - } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - /// 2 - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - /// - /// Removes the first occurrence of a specific object from the . - /// - /// - /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . - /// - /// The object to remove from the .The is read-only. - public bool Remove(T item) - { - using (new WriteLock(_instanceLocker)) - { - return _innerSet.Remove(item); - } - } - - - /// - /// Gets the number of elements contained in the . - /// - /// - /// The number of elements contained in the . - /// - /// 2 - public int Count - { - get { return GetThreadSafeClone().Count; } - } - - /// - /// Gets a value indicating whether the is read-only. - /// - /// - /// true if the is read-only; otherwise, false. - /// - public bool IsReadOnly - { - get { return false; } - } - - /// - /// Adds an item to the . - /// - /// The object to add to the .The is read-only. - public void Add(T item) - { - using (new WriteLock(_instanceLocker)) - { - _innerSet.Add(item); - } - } - - /// - /// Attempts to add an item to the collection - /// - /// - /// - public bool TryAdd(T item) - { - var clone = GetThreadSafeClone(); - if (clone.Contains(item)) return false; - using (new WriteLock(_instanceLocker)) - { - //double check - if (_innerSet.Contains(item)) return false; - _innerSet.Add(item); - return true; - } - } - - /// - /// Removes all items from the . - /// - /// The is read-only. - public void Clear() - { - using (new WriteLock(_instanceLocker)) - { - _innerSet.Clear(); - } - } - - /// - /// Determines whether the contains a specific value. - /// - /// - /// true if is found in the ; otherwise, false. - /// - /// The object to locate in the . - public bool Contains(T item) - { - return GetThreadSafeClone().Contains(item); - } - - /// - /// Copies the elements of the to an , starting at a specified index. - /// - /// The one-dimensional that is the destination of the elements copied from the . The array must have zero-based indexing.The zero-based index in at which copying begins. is a null reference (Nothing in Visual Basic). is less than zero. is equal to or greater than the length of the -or- The number of elements in the source is greater than the available space from to the end of the destination . - public void CopyTo(T[] array, int index) - { - var clone = GetThreadSafeClone(); - clone.CopyTo(array, index); - } - - private HashSet GetThreadSafeClone() - { - HashSet clone = null; - using (new WriteLock(_instanceLocker)) - { - clone = new HashSet(_innerSet, _innerSet.Comparer); - } - return clone; - } - - /// - /// Copies the elements of the to an , starting at a particular index. - /// - /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. The zero-based index in at which copying begins. is null. is less than zero. is multidimensional.-or- The number of elements in the source is greater than the available space from to the end of the destination . The type of the source cannot be cast automatically to the type of the destination . 2 - public void CopyTo(Array array, int index) - { - var clone = GetThreadSafeClone(); - Array.Copy(clone.ToArray(), 0, array, index, clone.Count); - } - } +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Umbraco.Core.Collections +{ + /// + /// A thread-safe representation of a . + /// Enumerating this collection is thread-safe and will only operate on a clone that is generated before returning the enumerator. + /// + /// + [Serializable] + public class ConcurrentHashSet : ICollection + { + private readonly HashSet _innerSet = new HashSet(); + private readonly ReaderWriterLockSlim _instanceLocker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// A that can be used to iterate through the collection. + /// + /// 1 + public IEnumerator GetEnumerator() + { + return GetThreadSafeClone().GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + /// 2 + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Removes the first occurrence of a specific object from the . + /// + /// + /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . + /// + /// The object to remove from the .The is read-only. + public bool Remove(T item) + { + using (new WriteLock(_instanceLocker)) + { + return _innerSet.Remove(item); + } + } + + + /// + /// Gets the number of elements contained in the . + /// + /// + /// The number of elements contained in the . + /// + /// 2 + public int Count + { + get { return GetThreadSafeClone().Count; } + } + + /// + /// Gets a value indicating whether the is read-only. + /// + /// + /// true if the is read-only; otherwise, false. + /// + public bool IsReadOnly + { + get { return false; } + } + + /// + /// Adds an item to the . + /// + /// The object to add to the .The is read-only. + public void Add(T item) + { + using (new WriteLock(_instanceLocker)) + { + _innerSet.Add(item); + } + } + + /// + /// Attempts to add an item to the collection + /// + /// + /// + public bool TryAdd(T item) + { + var clone = GetThreadSafeClone(); + if (clone.Contains(item)) return false; + using (new WriteLock(_instanceLocker)) + { + //double check + if (_innerSet.Contains(item)) return false; + _innerSet.Add(item); + return true; + } + } + + /// + /// Removes all items from the . + /// + /// The is read-only. + public void Clear() + { + using (new WriteLock(_instanceLocker)) + { + _innerSet.Clear(); + } + } + + /// + /// Determines whether the contains a specific value. + /// + /// + /// true if is found in the ; otherwise, false. + /// + /// The object to locate in the . + public bool Contains(T item) + { + return GetThreadSafeClone().Contains(item); + } + + /// + /// Copies the elements of the to an , starting at a specified index. + /// + /// The one-dimensional that is the destination of the elements copied from the . The array must have zero-based indexing.The zero-based index in at which copying begins. is a null reference (Nothing in Visual Basic). is less than zero. is equal to or greater than the length of the -or- The number of elements in the source is greater than the available space from to the end of the destination . + public void CopyTo(T[] array, int index) + { + var clone = GetThreadSafeClone(); + clone.CopyTo(array, index); + } + + private HashSet GetThreadSafeClone() + { + HashSet clone = null; + using (new WriteLock(_instanceLocker)) + { + clone = new HashSet(_innerSet, _innerSet.Comparer); + } + return clone; + } + + /// + /// Copies the elements of the to an , starting at a particular index. + /// + /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. The zero-based index in at which copying begins. is null. is less than zero. is multidimensional.-or- The number of elements in the source is greater than the available space from to the end of the destination . The type of the source cannot be cast automatically to the type of the destination . 2 + public void CopyTo(Array array, int index) + { + var clone = GetThreadSafeClone(); + Array.Copy(clone.ToArray(), 0, array, index, clone.Count); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/ObservableDictionary.cs b/src/Umbraco.Core/Collections/ObservableDictionary.cs similarity index 95% rename from src/Umbraco.Core/ObservableDictionary.cs rename to src/Umbraco.Core/Collections/ObservableDictionary.cs index 7e988e2b05..0907a9da0e 100644 --- a/src/Umbraco.Core/ObservableDictionary.cs +++ b/src/Umbraco.Core/Collections/ObservableDictionary.cs @@ -1,172 +1,170 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.Core -{ - /// - /// An ObservableDictionary - /// - /// - /// Assumes that the key will not change and is unique for each element in the collection. - /// Collection is not thread-safe, so calls should be made single-threaded. - /// - /// The type of elements contained in the BindableCollection - /// The type of the indexing key - public class ObservableDictionary : ObservableCollection - { - protected Dictionary Indecies = new Dictionary(); - protected Func KeySelector; - - /// - /// Create new ObservableDictionary - /// - /// Selector function to create key from value - public ObservableDictionary(Func keySelector) - : base() - { - if (keySelector == null) throw new ArgumentException("keySelector"); - KeySelector = keySelector; - } - - #region Protected Methods - protected override void InsertItem(int index, TValue item) - { - var key = KeySelector(item); - if (Indecies.ContainsKey(key)) - throw new DuplicateKeyException(key.ToString()); - - if (index != this.Count) - { - foreach (var k in Indecies.Keys.Where(k => Indecies[k] >= index).ToList()) - { - Indecies[k]++; - } - } - - base.InsertItem(index, item); - Indecies[key] = index; - - } - - protected override void ClearItems() - { - base.ClearItems(); - Indecies.Clear(); - } - - - protected override void RemoveItem(int index) - { - var item = this[index]; - var key = KeySelector(item); - - base.RemoveItem(index); - - Indecies.Remove(key); - - foreach (var k in Indecies.Keys.Where(k => Indecies[k] > index).ToList()) - { - Indecies[k]--; - } - } - #endregion - - public virtual bool ContainsKey(TKey key) - { - return Indecies.ContainsKey(key); - } - - /// - /// Gets or sets the element with the specified key. If setting a new value, new value must have same key. - /// - /// Key of element to replace - /// - public virtual TValue this[TKey key] - { - - get { return this[Indecies[key]]; } - set - { - //confirm key matches - if (!KeySelector(value).Equals(key)) - throw new InvalidOperationException("Key of new value does not match"); - - if (!Indecies.ContainsKey(key)) - { - this.Add(value); - } - else - { - this[Indecies[key]] = value; - } - } - } - - /// - /// Replaces element at given key with new value. New value must have same key. - /// - /// Key of element to replace - /// New value - /// - /// - /// False if key not found - public virtual bool Replace(TKey key, TValue value) - { - if (!Indecies.ContainsKey(key)) return false; - //confirm key matches - if (!KeySelector(value).Equals(key)) - throw new InvalidOperationException("Key of new value does not match"); - - this[Indecies[key]] = value; - return true; - - } - - public virtual bool Remove(TKey key) - { - if (!Indecies.ContainsKey(key)) return false; - - this.RemoveAt(Indecies[key]); - return true; - - } - - /// - /// Allows us to change the key of an item - /// - /// - /// - public virtual void ChangeKey(TKey currentKey, TKey newKey) - { - if (!Indecies.ContainsKey(currentKey)) - { - throw new InvalidOperationException("No item with the key " + currentKey + "was found in the collection"); - } - if (ContainsKey(newKey)) - { - throw new DuplicateKeyException(newKey.ToString()); - } - - var currentIndex = Indecies[currentKey]; - - Indecies.Remove(currentKey); - Indecies.Add(newKey, currentIndex); - } - - internal class DuplicateKeyException : Exception - { - - public string Key { get; private set; } - public DuplicateKeyException(string key) - : base("Attempted to insert duplicate key " + key + " in collection") - { - Key = key; - } - } - - } -} +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Umbraco.Core.Collections +{ + /// + /// An ObservableDictionary + /// + /// + /// Assumes that the key will not change and is unique for each element in the collection. + /// Collection is not thread-safe, so calls should be made single-threaded. + /// + /// The type of elements contained in the BindableCollection + /// The type of the indexing key + public class ObservableDictionary : ObservableCollection + { + protected Dictionary Indecies = new Dictionary(); + protected Func KeySelector; + + /// + /// Create new ObservableDictionary + /// + /// Selector function to create key from value + public ObservableDictionary(Func keySelector) + : base() + { + if (keySelector == null) throw new ArgumentException("keySelector"); + KeySelector = keySelector; + } + + #region Protected Methods + protected override void InsertItem(int index, TValue item) + { + var key = KeySelector(item); + if (Indecies.ContainsKey(key)) + throw new DuplicateKeyException(key.ToString()); + + if (index != this.Count) + { + foreach (var k in Indecies.Keys.Where(k => Indecies[k] >= index).ToList()) + { + Indecies[k]++; + } + } + + base.InsertItem(index, item); + Indecies[key] = index; + + } + + protected override void ClearItems() + { + base.ClearItems(); + Indecies.Clear(); + } + + + protected override void RemoveItem(int index) + { + var item = this[index]; + var key = KeySelector(item); + + base.RemoveItem(index); + + Indecies.Remove(key); + + foreach (var k in Indecies.Keys.Where(k => Indecies[k] > index).ToList()) + { + Indecies[k]--; + } + } + #endregion + + public virtual bool ContainsKey(TKey key) + { + return Indecies.ContainsKey(key); + } + + /// + /// Gets or sets the element with the specified key. If setting a new value, new value must have same key. + /// + /// Key of element to replace + /// + public virtual TValue this[TKey key] + { + + get { return this[Indecies[key]]; } + set + { + //confirm key matches + if (!KeySelector(value).Equals(key)) + throw new InvalidOperationException("Key of new value does not match"); + + if (!Indecies.ContainsKey(key)) + { + this.Add(value); + } + else + { + this[Indecies[key]] = value; + } + } + } + + /// + /// Replaces element at given key with new value. New value must have same key. + /// + /// Key of element to replace + /// New value + /// + /// + /// False if key not found + public virtual bool Replace(TKey key, TValue value) + { + if (!Indecies.ContainsKey(key)) return false; + //confirm key matches + if (!KeySelector(value).Equals(key)) + throw new InvalidOperationException("Key of new value does not match"); + + this[Indecies[key]] = value; + return true; + + } + + public virtual bool Remove(TKey key) + { + if (!Indecies.ContainsKey(key)) return false; + + this.RemoveAt(Indecies[key]); + return true; + + } + + /// + /// Allows us to change the key of an item + /// + /// + /// + public virtual void ChangeKey(TKey currentKey, TKey newKey) + { + if (!Indecies.ContainsKey(currentKey)) + { + throw new InvalidOperationException("No item with the key " + currentKey + "was found in the collection"); + } + if (ContainsKey(newKey)) + { + throw new DuplicateKeyException(newKey.ToString()); + } + + var currentIndex = Indecies[currentKey]; + + Indecies.Remove(currentKey); + Indecies.Add(newKey, currentIndex); + } + + internal class DuplicateKeyException : Exception + { + + public string Key { get; private set; } + public DuplicateKeyException(string key) + : base("Attempted to insert duplicate key " + key + " in collection") + { + Key = key; + } + } + + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs index 5d4acdc0f3..715c0be157 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Configuration; +using Umbraco.Core.Macros; namespace Umbraco.Core.Configuration.UmbracoSettings { @@ -183,19 +184,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings } } - [Obsolete("This is here so that if this config element exists we won't get a YSOD, it is not used whatsoever and will be removed in future versions")] - [ConfigurationProperty("DocumentTypeIconList")] - internal InnerTextConfigurationElement DocumentTypeIconList - { - get - { - return new OptionalInnerTextConfigurationElement( - (InnerTextConfigurationElement)this["DocumentTypeIconList"], - //set the default - IconPickerBehaviour.HideFileDuplicates); - } - } - [ConfigurationProperty("disallowedUploadFiles")] internal CommaDelimitedConfigurationElement DisallowedUploadFiles { diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs index c7e3d6b836..34e19379aa 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Umbraco.Core.Macros; namespace Umbraco.Core.Configuration.UmbracoSettings { diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index e46e87558c..5559bd29cc 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -17,7 +17,7 @@ using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Migrations; -using Umbraco.Core.Profiling; +using Umbraco.Core.Plugins; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Core.Sync; diff --git a/src/Umbraco.Core/DependencyInjection/RepositoryCompositionRoot.cs b/src/Umbraco.Core/DependencyInjection/RepositoryCompositionRoot.cs index 30647047c5..ce79017c5d 100644 --- a/src/Umbraco.Core/DependencyInjection/RepositoryCompositionRoot.cs +++ b/src/Umbraco.Core/DependencyInjection/RepositoryCompositionRoot.cs @@ -1,4 +1,5 @@ using LightInject; +using Umbraco.Core.Cache; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; @@ -6,6 +7,7 @@ using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Plugins; namespace Umbraco.Core.DependencyInjection { diff --git a/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs b/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs index 9d8bafa354..8caaaf56ae 100644 --- a/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs +++ b/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs @@ -2,6 +2,7 @@ using System; using System.IO; using System.Linq; using LightInject; +using Umbraco.Core.Cache; using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; diff --git a/src/Umbraco.Core/DisposableTimer.cs b/src/Umbraco.Core/DisposableTimer.cs index c7e8874449..4f8c29336f 100644 --- a/src/Umbraco.Core/DisposableTimer.cs +++ b/src/Umbraco.Core/DisposableTimer.cs @@ -4,7 +4,6 @@ using System.ComponentModel; using System.Diagnostics; using System.Web; using Umbraco.Core.Logging; -using Umbraco.Core.Profiling; namespace Umbraco.Core { diff --git a/src/Umbraco.Core/Dynamics/DynamicXml.cs b/src/Umbraco.Core/Dynamics/DynamicXml.cs index 6e1f178c97..bedb25a69c 100644 --- a/src/Umbraco.Core/Dynamics/DynamicXml.cs +++ b/src/Umbraco.Core/Dynamics/DynamicXml.cs @@ -11,6 +11,7 @@ using System.IO; using System.Web; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Xml; namespace Umbraco.Core.Dynamics { diff --git a/src/Umbraco.Core/Dynamics/DynamicXmlConverter.cs b/src/Umbraco.Core/Dynamics/DynamicXmlConverter.cs index 6af13d6887..291378ad51 100644 --- a/src/Umbraco.Core/Dynamics/DynamicXmlConverter.cs +++ b/src/Umbraco.Core/Dynamics/DynamicXmlConverter.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using System.Xml; using System.Xml.Linq; +using Umbraco.Core.Plugins; namespace Umbraco.Core.Dynamics { diff --git a/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs b/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs index 9772aaf5bd..33a938154a 100644 --- a/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs +++ b/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs @@ -9,6 +9,7 @@ using System.Text; using System.Web.Services.Description; using Umbraco.Core.Cache; using Umbraco.Core.Logging; +using Umbraco.Core.Plugins; namespace Umbraco.Core.Dynamics { diff --git a/src/Umbraco.Core/Events/MacroErrorEventArgs.cs b/src/Umbraco.Core/Events/MacroErrorEventArgs.cs index 52ac19aae2..c05800d2e0 100644 --- a/src/Umbraco.Core/Events/MacroErrorEventArgs.cs +++ b/src/Umbraco.Core/Events/MacroErrorEventArgs.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Umbraco.Core.Macros; namespace Umbraco.Core.Events { diff --git a/src/Umbraco.Core/IO/FileSystemProviderManager.cs b/src/Umbraco.Core/IO/FileSystemProviderManager.cs index b2bcaee61f..6d72a13090 100644 --- a/src/Umbraco.Core/IO/FileSystemProviderManager.cs +++ b/src/Umbraco.Core/IO/FileSystemProviderManager.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Text; using Umbraco.Core.CodeAnnotations; using Umbraco.Core.Configuration; +using Umbraco.Core.Plugins; namespace Umbraco.Core.IO { diff --git a/src/Umbraco.Core/IconPickerBehaviour.cs b/src/Umbraco.Core/IconPickerBehaviour.cs deleted file mode 100644 index 69196c857f..0000000000 --- a/src/Umbraco.Core/IconPickerBehaviour.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; - -namespace Umbraco.Core -{ - [Obsolete("This is no longer used and will be removed from the core in future versions")] - public enum IconPickerBehaviour - { - /// - /// Default umbraco behavior - show duplicates in files and sprites - /// - ShowDuplicates, - - /// - /// If a file exists on disk with the same name as one in the sprite - /// then the file on disk overrules the one in the sprite, the - /// sprite icon will not be shown - /// - HideSpriteDuplicates, - - /// - /// If a file exists on disk with the same name as one in the sprite - /// then the file in the sprite overrules the one on disk, the file - /// on disk will be shown - /// - HideFileDuplicates - } -} diff --git a/src/Umbraco.Core/Profiling/IProfiler.cs b/src/Umbraco.Core/Logging/IProfiler.cs similarity index 93% rename from src/Umbraco.Core/Profiling/IProfiler.cs rename to src/Umbraco.Core/Logging/IProfiler.cs index 4a6a32194d..94ba9a6af6 100644 --- a/src/Umbraco.Core/Profiling/IProfiler.cs +++ b/src/Umbraco.Core/Logging/IProfiler.cs @@ -1,43 +1,43 @@ -using System; - -namespace Umbraco.Core.Profiling -{ - /// - /// Defines an object for use in the application to profile operations - /// - public interface IProfiler - { - /// - /// Render the UI to display the profiler - /// - /// - /// - /// Generally used for HTML displays - /// - string Render(); - - /// - /// Profile an operation - /// - /// - /// - /// - /// Use the 'using(' syntax - /// - IDisposable Step(string name); - - /// - /// Start the profiler - /// - void Start(); - - /// - /// Start the profiler - /// - /// - /// set discardResults to false when you want to abandon all profiling, this is useful for - /// when someone is not authenticated or you want to clear the results based on some other mechanism. - /// - void Stop(bool discardResults = false); - } +using System; + +namespace Umbraco.Core.Logging +{ + /// + /// Defines an object for use in the application to profile operations + /// + public interface IProfiler + { + /// + /// Render the UI to display the profiler + /// + /// + /// + /// Generally used for HTML displays + /// + string Render(); + + /// + /// Profile an operation + /// + /// + /// + /// + /// Use the 'using(' syntax + /// + IDisposable Step(string name); + + /// + /// Start the profiler + /// + void Start(); + + /// + /// Start the profiler + /// + /// + /// set discardResults to false when you want to abandon all profiling, this is useful for + /// when someone is not authenticated or you want to clear the results based on some other mechanism. + /// + void Stop(bool discardResults = false); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Profiling/LogProfiler.cs b/src/Umbraco.Core/Logging/LogProfiler.cs similarity index 83% rename from src/Umbraco.Core/Profiling/LogProfiler.cs rename to src/Umbraco.Core/Logging/LogProfiler.cs index 117d177650..1f3b0f44f5 100644 --- a/src/Umbraco.Core/Profiling/LogProfiler.cs +++ b/src/Umbraco.Core/Logging/LogProfiler.cs @@ -1,42 +1,38 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Umbraco.Core.Logging; - -namespace Umbraco.Core.Profiling -{ - /// - /// A profiler that outputs its results to an ILogger - /// - internal class LogProfiler : IProfiler - { - private readonly ILogger _logger; - - public LogProfiler(ILogger logger) - { - _logger = logger; - } - - public string Render() - { - return string.Empty; - } - - public IDisposable Step(string name) - { - _logger.Debug(typeof(LogProfiler), "Starting - " + name); - return new DisposableTimer(l => _logger.Info(typeof(LogProfiler), () => name + " (took " + l + "ms)")); - } - - public void Start() - { - //the log will alwasy be started - } - - public void Stop(bool discardResults = false) - { - //we don't need to do anything here - } - } -} +using System; + +namespace Umbraco.Core.Logging +{ + /// + /// A profiler that outputs its results to an ILogger + /// + internal class LogProfiler : IProfiler + { + private readonly ILogger _logger; + + public LogProfiler(ILogger logger) + { + _logger = logger; + } + + public string Render() + { + return string.Empty; + } + + public IDisposable Step(string name) + { + _logger.Debug(typeof(LogProfiler), "Starting - " + name); + return new DisposableTimer(l => _logger.Info(typeof(LogProfiler), () => name + " (took " + l + "ms)")); + } + + public void Start() + { + //the log will alwasy be started + } + + public void Stop(bool discardResults = false) + { + //we don't need to do anything here + } + } +} diff --git a/src/Umbraco.Core/Profiling/ProfilerExtensions.cs b/src/Umbraco.Core/Logging/ProfilerExtensions.cs similarity index 94% rename from src/Umbraco.Core/Profiling/ProfilerExtensions.cs rename to src/Umbraco.Core/Logging/ProfilerExtensions.cs index d4bbeb6744..621cee65b2 100644 --- a/src/Umbraco.Core/Profiling/ProfilerExtensions.cs +++ b/src/Umbraco.Core/Logging/ProfilerExtensions.cs @@ -1,35 +1,35 @@ -using System; - -namespace Umbraco.Core.Profiling -{ - internal static class ProfilerExtensions - { - /// - /// Writes out a step prefixed with the type - /// - /// - /// - /// - /// - internal static IDisposable Step(this IProfiler profiler, string name) - { - if (profiler == null) throw new ArgumentNullException("profiler"); - return profiler.Step(typeof (T), name); - } - - /// - /// Writes out a step prefixed with the type - /// - /// - /// - /// - /// - internal static IDisposable Step(this IProfiler profiler, Type objectType, string name) - { - if (profiler == null) throw new ArgumentNullException("profiler"); - if (objectType == null) throw new ArgumentNullException("objectType"); - if (name == null) throw new ArgumentNullException("name"); - return profiler.Step(string.Format("[{0}] {1}", objectType.Name, name)); - } - } +using System; + +namespace Umbraco.Core.Logging +{ + internal static class ProfilerExtensions + { + /// + /// Writes out a step prefixed with the type + /// + /// + /// + /// + /// + internal static IDisposable Step(this IProfiler profiler, string name) + { + if (profiler == null) throw new ArgumentNullException("profiler"); + return profiler.Step(typeof (T), name); + } + + /// + /// Writes out a step prefixed with the type + /// + /// + /// + /// + /// + internal static IDisposable Step(this IProfiler profiler, Type objectType, string name) + { + if (profiler == null) throw new ArgumentNullException("profiler"); + if (objectType == null) throw new ArgumentNullException("objectType"); + if (name == null) throw new ArgumentNullException("name"); + return profiler.Step(string.Format("[{0}] {1}", objectType.Name, name)); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Profiling/ProfilerResolver.cs b/src/Umbraco.Core/Logging/ProfilerResolver.cs similarity index 93% rename from src/Umbraco.Core/Profiling/ProfilerResolver.cs rename to src/Umbraco.Core/Logging/ProfilerResolver.cs index 63f647af87..e7be06450d 100644 --- a/src/Umbraco.Core/Profiling/ProfilerResolver.cs +++ b/src/Umbraco.Core/Logging/ProfilerResolver.cs @@ -1,42 +1,42 @@ -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Core.Profiling -{ - /// - /// A resolver exposing the current profiler - /// - /// - /// NOTE: This is a 'special' resolver in that it gets initialized before most other things, it cannot use IoC so it cannot implement ContainerObjectResolverBase - /// - internal class ProfilerResolver : SingleObjectResolverBase - { - /// - /// Constructor - /// - /// - public ProfilerResolver(IProfiler profiler) - : base(profiler) - { - - } - - /// - /// Method allowing to change the profiler during startup - /// - /// - internal void SetProfiler(IProfiler profiler) - { - Value = profiler; - } - - /// - /// Gets the current profiler - /// - public IProfiler Profiler - { - get { return Value; } - } - - - } +using Umbraco.Core.ObjectResolution; + +namespace Umbraco.Core.Logging +{ + /// + /// A resolver exposing the current profiler + /// + /// + /// NOTE: This is a 'special' resolver in that it gets initialized before most other things, it cannot use IoC so it cannot implement ContainerObjectResolverBase + /// + internal class ProfilerResolver : SingleObjectResolverBase + { + /// + /// Constructor + /// + /// + public ProfilerResolver(IProfiler profiler) + : base(profiler) + { + + } + + /// + /// Method allowing to change the profiler during startup + /// + /// + internal void SetProfiler(IProfiler profiler) + { + Value = profiler; + } + + /// + /// Gets the current profiler + /// + public IProfiler Profiler + { + get { return Value; } + } + + + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Logging/ProfilingLogger.cs b/src/Umbraco.Core/Logging/ProfilingLogger.cs index 0d1efeb0d9..85c9e350ba 100644 --- a/src/Umbraco.Core/Logging/ProfilingLogger.cs +++ b/src/Umbraco.Core/Logging/ProfilingLogger.cs @@ -1,5 +1,4 @@ using System; -using Umbraco.Core.Profiling; namespace Umbraco.Core.Logging { diff --git a/src/Umbraco.Core/Profiling/WebProfiler.cs b/src/Umbraco.Core/Logging/WebProfiler.cs similarity index 96% rename from src/Umbraco.Core/Profiling/WebProfiler.cs rename to src/Umbraco.Core/Logging/WebProfiler.cs index 0592e25bc1..244835f792 100644 --- a/src/Umbraco.Core/Profiling/WebProfiler.cs +++ b/src/Umbraco.Core/Logging/WebProfiler.cs @@ -1,165 +1,164 @@ -using System; -using System.Web; -using StackExchange.Profiling; -using StackExchange.Profiling.SqlFormatters; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; - -namespace Umbraco.Core.Profiling -{ - /// - /// A profiler used for web based activity based on the MiniProfiler framework - /// - internal class WebProfiler : ApplicationEventHandler, IProfiler - { - /// - ///Binds to application events to enable the MiniProfiler - /// - /// - /// - protected override void ApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - UmbracoApplicationBase.ApplicationInit += UmbracoApplicationApplicationInit; - } - - - /// - /// Handle the Init event o fthe UmbracoApplication which allows us to subscribe to the HttpApplication events - /// - /// - /// - void UmbracoApplicationApplicationInit(object sender, EventArgs e) - { - var app = sender as HttpApplication; - if (app == null) return; - - if (SystemUtilities.GetCurrentTrustLevel() < AspNetHostingPermissionLevel.High) - { - //If we don't have a high enough trust level we cannot bind to the events - LogHelper.Info("Cannot start the WebProfiler since the application is running in Medium trust"); - } - else - { - app.BeginRequest += UmbracoApplicationBeginRequest; - app.EndRequest += UmbracoApplicationEndRequest; - } - } - - /// - /// Handle the begin request event - /// - /// - /// - void UmbracoApplicationEndRequest(object sender, EventArgs e) - { - if (CanPerformProfilingAction(sender)) - { - Stop(); - } - } - - /// - /// Handle the end request event - /// - /// - /// - void UmbracoApplicationBeginRequest(object sender, EventArgs e) - { - if (CanPerformProfilingAction(sender)) - { - Start(); - } - } - - private bool CanPerformProfilingAction(object sender) - { - if (GlobalSettings.DebugMode == false) - return false; - - //will not run in medium trust - if (SystemUtilities.GetCurrentTrustLevel() < AspNetHostingPermissionLevel.High) - return false; - - var request = TryGetRequest(sender); - - if (request.Success == false || request.Result.Url.IsClientSideRequest()) - return false; - - if (string.IsNullOrEmpty(request.Result.QueryString["umbDebug"])) - return true; - - if (request.Result.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath)) - return true; - - return true; - } - - /// - /// Render the UI to display the profiler - /// - /// - /// - /// Generally used for HTML displays - /// - public string Render() - { - return MiniProfiler.RenderIncludes(RenderPosition.Right).ToString(); - } - - /// - /// Profile a step - /// - /// - /// - /// - /// Use the 'using(' syntax - /// - public IDisposable Step(string name) - { - return GlobalSettings.DebugMode == false ? null : MiniProfiler.Current.Step(name); - } - - /// - /// Start the profiler - /// - public void Start() - { - MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter(); - MiniProfiler.Settings.StackMaxLength = 5000; - MiniProfiler.Start(); - } - - /// - /// Start the profiler - /// - /// - /// set discardResults to false when you want to abandon all profiling, this is useful for - /// when someone is not authenticated or you want to clear the results based on some other mechanism. - /// - public void Stop(bool discardResults = false) - { - MiniProfiler.Stop(discardResults); - } - - /// - /// Gets the request object from the app instance if it is available - /// - /// The application object - /// - private Attempt TryGetRequest(object sender) - { - var app = sender as HttpApplication; - if (app == null) return Attempt.Fail(); - - try - { - var req = app.Request; - return Attempt.Succeed(new HttpRequestWrapper(req)); - } - catch (HttpException ex) - { - return Attempt.Fail(ex); - } - } - } +using System; +using System.Web; +using StackExchange.Profiling; +using StackExchange.Profiling.SqlFormatters; +using Umbraco.Core.Configuration; + +namespace Umbraco.Core.Logging +{ + /// + /// A profiler used for web based activity based on the MiniProfiler framework + /// + internal class WebProfiler : ApplicationEventHandler, IProfiler + { + /// + ///Binds to application events to enable the MiniProfiler + /// + /// + /// + protected override void ApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + UmbracoApplicationBase.ApplicationInit += UmbracoApplicationApplicationInit; + } + + + /// + /// Handle the Init event o fthe UmbracoApplication which allows us to subscribe to the HttpApplication events + /// + /// + /// + void UmbracoApplicationApplicationInit(object sender, EventArgs e) + { + var app = sender as HttpApplication; + if (app == null) return; + + if (SystemUtilities.GetCurrentTrustLevel() < AspNetHostingPermissionLevel.High) + { + //If we don't have a high enough trust level we cannot bind to the events + LogHelper.Info("Cannot start the WebProfiler since the application is running in Medium trust"); + } + else + { + app.BeginRequest += UmbracoApplicationBeginRequest; + app.EndRequest += UmbracoApplicationEndRequest; + } + } + + /// + /// Handle the begin request event + /// + /// + /// + void UmbracoApplicationEndRequest(object sender, EventArgs e) + { + if (CanPerformProfilingAction(sender)) + { + Stop(); + } + } + + /// + /// Handle the end request event + /// + /// + /// + void UmbracoApplicationBeginRequest(object sender, EventArgs e) + { + if (CanPerformProfilingAction(sender)) + { + Start(); + } + } + + private bool CanPerformProfilingAction(object sender) + { + if (GlobalSettings.DebugMode == false) + return false; + + //will not run in medium trust + if (SystemUtilities.GetCurrentTrustLevel() < AspNetHostingPermissionLevel.High) + return false; + + var request = TryGetRequest(sender); + + if (request.Success == false || request.Result.Url.IsClientSideRequest()) + return false; + + if (string.IsNullOrEmpty(request.Result.QueryString["umbDebug"])) + return true; + + if (request.Result.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath)) + return true; + + return true; + } + + /// + /// Render the UI to display the profiler + /// + /// + /// + /// Generally used for HTML displays + /// + public string Render() + { + return MiniProfiler.RenderIncludes(RenderPosition.Right).ToString(); + } + + /// + /// Profile a step + /// + /// + /// + /// + /// Use the 'using(' syntax + /// + public IDisposable Step(string name) + { + return GlobalSettings.DebugMode == false ? null : MiniProfiler.Current.Step(name); + } + + /// + /// Start the profiler + /// + public void Start() + { + MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter(); + MiniProfiler.Settings.StackMaxLength = 5000; + MiniProfiler.Start(); + } + + /// + /// Start the profiler + /// + /// + /// set discardResults to false when you want to abandon all profiling, this is useful for + /// when someone is not authenticated or you want to clear the results based on some other mechanism. + /// + public void Stop(bool discardResults = false) + { + MiniProfiler.Stop(discardResults); + } + + /// + /// Gets the request object from the app instance if it is available + /// + /// The application object + /// + private Attempt TryGetRequest(object sender) + { + var app = sender as HttpApplication; + if (app == null) return Attempt.Fail(); + + try + { + var req = app.Request; + return Attempt.Succeed(new HttpRequestWrapper(req)); + } + catch (HttpException ex) + { + return Attempt.Fail(ex); + } + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/MacroErrorBehaviour.cs b/src/Umbraco.Core/Macros/MacroErrorBehaviour.cs similarity index 93% rename from src/Umbraco.Core/MacroErrorBehaviour.cs rename to src/Umbraco.Core/Macros/MacroErrorBehaviour.cs index 50d9faa19c..be63c36d13 100644 --- a/src/Umbraco.Core/MacroErrorBehaviour.cs +++ b/src/Umbraco.Core/Macros/MacroErrorBehaviour.cs @@ -1,23 +1,23 @@ -namespace Umbraco.Core -{ - public enum MacroErrorBehaviour - { - /// - /// Default umbraco behavior - show an inline error within the - /// macro but allow the page to continue rendering. - /// - Inline, - - /// - /// Silently eat the error and do not display the offending macro. - /// - Silent, - - /// - /// Throw an exception which can be caught by the global error handler - /// defined in Application_OnError. If no such error handler is defined - /// then you'll see the Yellow Screen Of Death (YSOD) error page. - /// - Throw - } -} +namespace Umbraco.Core.Macros +{ + public enum MacroErrorBehaviour + { + /// + /// Default umbraco behavior - show an inline error within the + /// macro but allow the page to continue rendering. + /// + Inline, + + /// + /// Silently eat the error and do not display the offending macro. + /// + Silent, + + /// + /// Throw an exception which can be caught by the global error handler + /// defined in Application_OnError. If no such error handler is defined + /// then you'll see the Yellow Screen Of Death (YSOD) error page. + /// + Throw + } +} diff --git a/src/Umbraco.Core/Macros/MacroTagParser.cs b/src/Umbraco.Core/Macros/MacroTagParser.cs index 66f8a8a568..ec88b1cce6 100644 --- a/src/Umbraco.Core/Macros/MacroTagParser.cs +++ b/src/Umbraco.Core/Macros/MacroTagParser.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; using HtmlAgilityPack; +using Umbraco.Core.Xml; namespace Umbraco.Core.Macros { diff --git a/src/Umbraco.Core/Models/DeepCloneHelper.cs b/src/Umbraco.Core/Models/DeepCloneHelper.cs index c1b45f63ce..9c0cd1d47f 100644 --- a/src/Umbraco.Core/Models/DeepCloneHelper.cs +++ b/src/Umbraco.Core/Models/DeepCloneHelper.cs @@ -4,6 +4,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; +using Umbraco.Core.Plugins; namespace Umbraco.Core.Models { diff --git a/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs b/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs index 0d5378d253..8fa9bfe5ff 100644 --- a/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs +++ b/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs @@ -5,6 +5,7 @@ using System.ComponentModel; using System.Linq; using System.Reflection; using System.Runtime.Serialization; +using Umbraco.Core.Plugins; namespace Umbraco.Core.Models.EntityBase { diff --git a/src/Umbraco.Core/Models/MacroPropertyCollection.cs b/src/Umbraco.Core/Models/MacroPropertyCollection.cs index de23d60e7c..45b5f087cc 100644 --- a/src/Umbraco.Core/Models/MacroPropertyCollection.cs +++ b/src/Umbraco.Core/Models/MacroPropertyCollection.cs @@ -2,6 +2,7 @@ using System.Collections.ObjectModel; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Collections; namespace Umbraco.Core.Models { diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs index 1459264972..f27b827cc3 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs @@ -6,6 +6,7 @@ using System.Xml.Linq; using System.Xml.XPath; using Umbraco.Core.Dynamics; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Xml; namespace Umbraco.Core.Models.PublishedContent { diff --git a/src/Umbraco.Core/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs index 907a91fe10..620eb7eccb 100644 --- a/src/Umbraco.Core/ObjectExtensions.cs +++ b/src/Umbraco.Core/ObjectExtensions.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Xml; +using Umbraco.Core.Plugins; namespace Umbraco.Core { diff --git a/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs b/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs index d74d03ea05..03c0146d9b 100644 --- a/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs +++ b/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading; using System.Web; using Umbraco.Core.Logging; +using Umbraco.Core.Plugins; namespace Umbraco.Core.ObjectResolution { diff --git a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs index 50e1ea0106..cdd6c9cf24 100644 --- a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Linq.Expressions; using System.Text; using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Plugins; namespace Umbraco.Core.Persistence.Querying { diff --git a/src/Umbraco.Core/Persistence/Repositories/AuditRepository.cs b/src/Umbraco.Core/Persistence/Repositories/AuditRepository.cs index 0d933aec1e..4ef27371b5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/AuditRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/AuditRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using LightInject; using NPoco; +using Umbraco.Core.Cache; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Logging; using Umbraco.Core.Models; diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs index 6dfa55f569..dfb8dc4c09 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Xml.Linq; using NPoco; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepositoryBase.cs index cd1ef66168..4d9b32f1e8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepositoryBase.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using NPoco; +using Umbraco.Core.Cache; using Umbraco.Core.Events; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs index 7175623dae..65387a7892 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Xml.Linq; using NPoco; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; diff --git a/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs index 9c67f2473e..40af744ae8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.AspNet.Identity; using NPoco; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Identity; diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/DataTypeContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/DataTypeContainerRepository.cs index b98868c73a..5eb8e44fbb 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/DataTypeContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/DataTypeContainerRepository.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Logging; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.UnitOfWork; diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/DocumentTypeContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/DocumentTypeContainerRepository.cs index b1bedc9a96..64b2bb685b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/DocumentTypeContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/DocumentTypeContainerRepository.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Logging; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.UnitOfWork; diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/MediaTypeContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/MediaTypeContainerRepository.cs index a20e73338a..38429f2996 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/MediaTypeContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/MediaTypeContainerRepository.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Logging; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.UnitOfWork; diff --git a/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs index c9881a9fdd..29d8c73943 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using NPoco; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index ebbbe4db3a..edd28deecd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text.RegularExpressions; using System.Xml.Linq; using NPoco; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models; diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index 74a82efa08..97ce8642e3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using System.Xml.Linq; using NPoco; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; diff --git a/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs index ad8a1cf24c..86e2c61e9d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs @@ -4,6 +4,7 @@ using System.Linq; using LightInject; using NPoco; using Semver; +using Umbraco.Core.Cache; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Logging; using Umbraco.Core.Models; diff --git a/src/Umbraco.Core/Persistence/Repositories/NPocoRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/NPocoRepositoryBase.cs index d9392417b6..d2b19faef3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/NPocoRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/NPocoRepositoryBase.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Data.SqlServerCe; using NPoco; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence.Mappers; diff --git a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs index d07d55303a..0be15ede71 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; diff --git a/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs index 1b291cfeaa..6c58cd3b79 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using LightInject; using NPoco; +using Umbraco.Core.Cache; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Logging; using Umbraco.Core.Models; diff --git a/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs index 8a14d4c70c..989f8aee58 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using LightInject; using NPoco; +using Umbraco.Core.Cache; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Logging; using Umbraco.Core.Models; diff --git a/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs index 89ee102299..2947fb1724 100644 --- a/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using NPoco; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence.Mappers; diff --git a/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs index 5b016a80dc..36da4bab17 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using NPoco; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; diff --git a/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs index f6d24c717b..4765b72060 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs @@ -4,6 +4,7 @@ using System.Linq; using AutoMapper; using LightInject; using NPoco; +using Umbraco.Core.Cache; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Logging; using Umbraco.Core.Models; diff --git a/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs index 3cf4ba95ab..d94475c280 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using LightInject; using NPoco; +using Umbraco.Core.Cache; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Logging; using Umbraco.Core.Models; diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index 695810412c..f36e69d747 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Linq.Expressions; using NPoco; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; diff --git a/src/Umbraco.Core/Persistence/Repositories/UserTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserTypeRepository.cs index ab163ec4d9..d2ee75dbf0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserTypeRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using NPoco; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 3b0550a8a6..adc3ee0311 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -6,6 +6,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using NPoco; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; diff --git a/src/Umbraco.Core/HideFromTypeFinderAttribute.cs b/src/Umbraco.Core/Plugins/HideFromTypeFinderAttribute.cs similarity index 90% rename from src/Umbraco.Core/HideFromTypeFinderAttribute.cs rename to src/Umbraco.Core/Plugins/HideFromTypeFinderAttribute.cs index cc32654c9c..1568bc2995 100644 --- a/src/Umbraco.Core/HideFromTypeFinderAttribute.cs +++ b/src/Umbraco.Core/Plugins/HideFromTypeFinderAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Core +namespace Umbraco.Core.Plugins { /// /// Used to notify the TypeFinder to ignore any class attributed with this during it's discovery diff --git a/src/Umbraco.Core/PluginManager.cs b/src/Umbraco.Core/Plugins/PluginManager.cs similarity index 97% rename from src/Umbraco.Core/PluginManager.cs rename to src/Umbraco.Core/Plugins/PluginManager.cs index 6145fec8e2..3b13926dfb 100644 --- a/src/Umbraco.Core/PluginManager.cs +++ b/src/Umbraco.Core/Plugins/PluginManager.cs @@ -1,789 +1,784 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading; -using System.Web.Compilation; -using System.Xml.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.Migrations; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Profiling; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Cache; -using Umbraco.Core._Legacy.PackageActions; -using File = System.IO.File; - -namespace Umbraco.Core -{ - /// - /// Used to resolve all plugin types and cache them and is also used to instantiate plugin types - /// - /// - /// - /// This class should be used to resolve all plugin types, the TypeFinder should not be used directly! - /// - /// This class can expose extension methods to resolve custom plugins - /// - /// Before this class resolves any plugins it checks if the hash has changed for the DLLs in the /bin folder, if it hasn't - /// it will use the cached resolved plugins that it has already found which means that no assembly scanning is necessary. This leads - /// to much faster startup times. - /// - public class PluginManager - { - /// - /// Creates a new PluginManager with an ApplicationContext instance which ensures that the plugin xml - /// file is cached temporarily until app startup completes. - /// - /// - /// - /// - /// - public PluginManager(IServiceProvider serviceProvider, IRuntimeCacheProvider runtimeCache, ProfilingLogger logger, bool detectChanges = true) - { - if (serviceProvider == null) throw new ArgumentNullException("serviceProvider"); - if (runtimeCache == null) throw new ArgumentNullException("runtimeCache"); - if (logger == null) throw new ArgumentNullException("logger"); - - _serviceProvider = serviceProvider; - _runtimeCache = runtimeCache; - _logger = logger; - - _tempFolder = IOHelper.MapPath("~/App_Data/TEMP/PluginCache"); - //create the folder if it doesn't exist - if (Directory.Exists(_tempFolder) == false) - { - Directory.CreateDirectory(_tempFolder); - } - - var pluginListFile = GetPluginListFilePath(); - - //this is a check for legacy changes, before we didn't store the TypeResolutionKind in the file which was a mistake, - //so we need to detect if the old file is there without this attribute, if it is then we delete it - if (DetectLegacyPluginListFile()) - { - File.Delete(pluginListFile); - } - - if (detectChanges) - { - //first check if the cached hash is 0, if it is then we ne - //do the check if they've changed - RequiresRescanning = (CachedAssembliesHash != CurrentAssembliesHash) || CachedAssembliesHash == 0; - //if they have changed, we need to write the new file - if (RequiresRescanning) - { - //if the hash has changed, clear out the persisted list no matter what, this will force - // rescanning of all plugin types including lazy ones. - // http://issues.umbraco.org/issue/U4-4789 - File.Delete(pluginListFile); - - WriteCachePluginsHash(); - } - } - else - { - - //if the hash has changed, clear out the persisted list no matter what, this will force - // rescanning of all plugin types including lazy ones. - // http://issues.umbraco.org/issue/U4-4789 - File.Delete(pluginListFile); - - //always set to true if we're not detecting (generally only for testing) - RequiresRescanning = true; - } - } - - private readonly IServiceProvider _serviceProvider; - private readonly IRuntimeCacheProvider _runtimeCache; - private readonly ProfilingLogger _logger; - private const string CacheKey = "umbraco-plugins.list"; - static PluginManager _resolver; - private readonly string _tempFolder; - private long _cachedAssembliesHash = -1; - private long _currentAssembliesHash = -1; - private static bool _initialized = false; - private static object _singletonLock = new object(); - - /// - /// We will ensure that no matter what, only one of these is created, this is to ensure that caching always takes place - /// - /// - /// The setter is generally only used for unit tests - /// - public static PluginManager Current - { - get - { - return LazyInitializer.EnsureInitialized(ref _resolver, ref _initialized, ref _singletonLock, () => - { - if (ApplicationContext.Current == null) - { - var logger = LoggerResolver.HasCurrent ? LoggerResolver.Current.Logger : new DebugDiagnosticsLogger(); - var profiler = ProfilerResolver.HasCurrent ? ProfilerResolver.Current.Profiler : new LogProfiler(logger); - return new PluginManager( - new ActivatorServiceProvider(), - new NullCacheProvider(), - new ProfilingLogger(logger, profiler)); - } - return new PluginManager( - new ActivatorServiceProvider(), - ApplicationContext.Current.ApplicationCache.RuntimeCache, - ApplicationContext.Current.ProfilingLogger); - }); - } - set - { - _initialized = true; - _resolver = value; - } - } - - #region Hash checking methods - - - /// - /// Returns a bool if the assemblies in the /bin, app_code, global.asax, etc... have changed since they were last hashed. - /// - internal bool RequiresRescanning { get; private set; } - - /// - /// Returns the currently cached hash value of the scanned assemblies in the /bin folder. Returns 0 - /// if no cache is found. - /// - /// - internal long CachedAssembliesHash - { - get - { - if (_cachedAssembliesHash != -1) - return _cachedAssembliesHash; - - var filePath = GetPluginHashFilePath(); - if (!File.Exists(filePath)) - return 0; - var hash = File.ReadAllText(filePath, Encoding.UTF8); - Int64 val; - if (Int64.TryParse(hash, out val)) - { - _cachedAssembliesHash = val; - return _cachedAssembliesHash; - } - //it could not parse for some reason so we'll return 0. - return 0; - } - } - - /// - /// Returns the current assemblies hash based on creating a hash from the assemblies in the /bin - /// - /// - internal long CurrentAssembliesHash - { - get - { - if (_currentAssembliesHash != -1) - return _currentAssembliesHash; - - _currentAssembliesHash = GetFileHash( - new List> - { - //add the bin folder and everything in it - new Tuple(new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Bin)), false), - //add the app code folder and everything in it - new Tuple(new DirectoryInfo(IOHelper.MapPath("~/App_Code")), false), - //add the global.asax (the app domain also monitors this, if it changes will do a full restart) - new Tuple(new FileInfo(IOHelper.MapPath("~/global.asax")), false), - - //add the trees.config - use the contents to create the has since this gets resaved on every app startup! - new Tuple(new FileInfo(IOHelper.MapPath(SystemDirectories.Config + "/trees.config")), true) - }, _logger - ); - return _currentAssembliesHash; - } - } - - /// - /// Writes the assembly hash file - /// - private void WriteCachePluginsHash() - { - var filePath = GetPluginHashFilePath(); - File.WriteAllText(filePath, CurrentAssembliesHash.ToString(), Encoding.UTF8); - } - - /// - /// Returns a unique hash for the combination of FileInfo objects passed in - /// - /// - /// A collection of files and whether or not to use their file contents to determine the hash or the file's properties - /// (true will make a hash based on it's contents) - /// - /// - internal static long GetFileHash(IEnumerable> filesAndFolders, ProfilingLogger logger) - { - using (logger.TraceDuration("Determining hash of code files on disk", "Hash determined")) - { - var hashCombiner = new HashCodeCombiner(); - - //get the file info's to check - var fileInfos = filesAndFolders.Where(x => x.Item2 == false).ToArray(); - var fileContents = filesAndFolders.Except(fileInfos); - - //add each unique folder/file to the hash - foreach (var i in fileInfos.Select(x => x.Item1).DistinctBy(x => x.FullName)) - { - hashCombiner.AddFileSystemItem(i); - } - - //add each unique file's contents to the hash - foreach (var i in fileContents.Select(x => x.Item1).DistinctBy(x => x.FullName)) - { - if (File.Exists(i.FullName)) - { - var content = File.ReadAllText(i.FullName).Replace("\r\n", string.Empty).Replace("\n", string.Empty).Replace("\r", string.Empty); - hashCombiner.AddCaseInsensitiveString(content); - } - - } - - return ConvertPluginsHashFromHex(hashCombiner.GetCombinedHashCode()); - } - } - - internal static long GetFileHash(IEnumerable filesAndFolders, ProfilingLogger logger) - { - using (logger.TraceDuration("Determining hash of code files on disk", "Hash determined")) - { - var hashCombiner = new HashCodeCombiner(); - - //add each unique folder/file to the hash - foreach (var i in filesAndFolders.DistinctBy(x => x.FullName)) - { - hashCombiner.AddFileSystemItem(i); - } - return ConvertPluginsHashFromHex(hashCombiner.GetCombinedHashCode()); - } - } - - /// - /// Converts the hash value of current plugins to long from string - /// - /// - /// - internal static long ConvertPluginsHashFromHex(string val) - { - long outVal; - if (Int64.TryParse(val, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out outVal)) - { - return outVal; - } - return 0; - } - - /// - /// Attempts to resolve the list of plugin + assemblies found in the runtime for the base type 'T' passed in. - /// If the cache file doesn't exist, fails to load, is corrupt or the type 'T' element is not found then - /// a false attempt is returned. - /// - /// - /// - internal Attempt> TryGetCachedPluginsFromFile(TypeResolutionKind resolutionType) - { - var filePath = GetPluginListFilePath(); - if (!File.Exists(filePath)) - return Attempt>.Fail(); - - try - { - //we will load the xml document, if the app context exist, we will load it from the cache (which is only around for 5 minutes) - //while the app boots up, this should save some IO time on app startup when the app context is there (which is always unless in unit tests) - var xml = _runtimeCache.GetCacheItem(CacheKey, - () => XDocument.Load(filePath), - new TimeSpan(0, 0, 5, 0)); - - if (xml.Root == null) - return Attempt>.Fail(); - - var typeElement = xml.Root.Elements() - .SingleOrDefault(x => - x.Name.LocalName == "baseType" - && ((string)x.Attribute("type")) == typeof(T).FullName - && ((string)x.Attribute("resolutionType")) == resolutionType.ToString()); - - //return false but specify this exception type so we can detect it - if (typeElement == null) - return Attempt>.Fail(new CachedPluginNotFoundInFileException()); - - //return success - return Attempt.Succeed(typeElement.Elements("add") - .Select(x => (string)x.Attribute("type"))); - } - catch (Exception ex) - { - //if the file is corrupted, etc... return false - return Attempt>.Fail(ex); - } - } - - /// - /// Removes cache files and internal cache as well - /// - /// - /// Generally only used for resetting cache, for example during the install process - /// - public void ClearPluginCache() - { - var path = GetPluginListFilePath(); - if (File.Exists(path)) - File.Delete(path); - path = GetPluginHashFilePath(); - if (File.Exists(path)) - File.Delete(path); - - _runtimeCache.ClearCacheItem(CacheKey); - } - - private string GetPluginListFilePath() - { - return Path.Combine(_tempFolder, string.Format("umbraco-plugins.{0}.list", NetworkHelper.FileSafeMachineName)); - } - - private string GetPluginHashFilePath() - { - return Path.Combine(_tempFolder, string.Format("umbraco-plugins.{0}.hash", NetworkHelper.FileSafeMachineName)); - } - - /// - /// This will return true if the plugin list file is a legacy one - /// - /// - /// - /// This method exists purely due to an error in 4.11. We were writing the plugin list file without the - /// type resolution kind which will have caused some problems. Now we detect this legacy file and if it is detected - /// we remove it so it can be recreated properly. - /// - internal bool DetectLegacyPluginListFile() - { - var filePath = GetPluginListFilePath(); - if (!File.Exists(filePath)) - return false; - - try - { - var xml = XDocument.Load(filePath); - if (xml.Root == null) - return false; - - var typeElement = xml.Root.Elements() - .FirstOrDefault(x => x.Name.LocalName == "baseType"); - - if (typeElement == null) - return false; - - //now check if the typeElement is missing the resolutionType attribute - return typeElement.Attributes().All(x => x.Name.LocalName != "resolutionType"); - } - catch (Exception) - { - //if the file is corrupted, etc... return true so it is removed - return true; - } - } - - /// - /// Adds/Updates the type list for the base type 'T' in the cached file - /// - /// - /// - /// - /// - /// THIS METHOD IS NOT THREAD SAFE - /// - /// - /// - /// - /// - /// - /// - /// - /// ]]> - /// - internal void UpdateCachedPluginsFile(IEnumerable typesFound, TypeResolutionKind resolutionType) - { - var filePath = GetPluginListFilePath(); - XDocument xml; - try - { - xml = XDocument.Load(filePath); - } - catch - { - //if there's an exception loading then this is somehow corrupt, we'll just replace it. - File.Delete(filePath); - //create the document and the root - xml = new XDocument(new XElement("plugins")); - } - if (xml.Root == null) - { - //if for some reason there is no root, create it - xml.Add(new XElement("plugins")); - } - //find the type 'T' element to add or update - var typeElement = xml.Root.Elements() - .SingleOrDefault(x => - x.Name.LocalName == "baseType" - && ((string)x.Attribute("type")) == typeof(T).FullName - && ((string)x.Attribute("resolutionType")) == resolutionType.ToString()); - - if (typeElement == null) - { - //create the type element - typeElement = new XElement("baseType", - new XAttribute("type", typeof(T).FullName), - new XAttribute("resolutionType", resolutionType.ToString())); - //then add it to the root - xml.Root.Add(typeElement); - } - - - //now we have the type element, we need to clear any previous types as children and add/update it with new ones - typeElement.ReplaceNodes(typesFound.Select(x => new XElement("add", new XAttribute("type", x.AssemblyQualifiedName)))); - //save the xml file - xml.Save(filePath); - } - - #endregion - - private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); - private readonly HashSet _types = new HashSet(); - private IEnumerable _assemblies; - - /// - /// Returns all found property editors (based on the resolved Iparameter editors - this saves a scan) - /// - internal IEnumerable ResolvePropertyEditors() - { - //return all proeprty editor types found except for the base property editor type - return ResolveTypes() - .Where(x => x.IsType()) - .Except(new[] { typeof(PropertyEditor) }); - } - - /// - /// Returns all found parameter editors (which includes property editors) - /// - internal IEnumerable ResolveParameterEditors() - { - //return all paramter editor types found except for the base property editor type - return ResolveTypes() - .Except(new[] { typeof(ParameterEditor), typeof(PropertyEditor) }); - } - - /// - /// Returns all available IApplicationStartupHandler objects - /// - /// - internal IEnumerable ResolveApplicationStartupHandlers() - { - return ResolveTypes(); - } - - /// - /// Returns all classes of type ICacheRefresher - /// - /// - internal IEnumerable ResolveCacheRefreshers() - { - return ResolveTypes(); - } - - /// - /// Returns all available IPackageAction in application - /// - /// - internal IEnumerable ResolvePackageActions() - { - return ResolveTypes(); - } - - - - /// - /// Returns all mapper types that have a MapperFor attribute defined - /// - /// - internal IEnumerable ResolveAssignedMapperTypes() - { - return ResolveTypesWithAttribute(); - } - - /// - /// Gets/sets which assemblies to scan when type finding, generally used for unit testing, if not explicitly set - /// this will search all assemblies known to have plugins and exclude ones known to not have them. - /// - internal IEnumerable AssembliesToScan - { - get { return _assemblies ?? (_assemblies = TypeFinder.GetAssembliesWithKnownExclusions()); } - set { _assemblies = value; } - } - - private IEnumerable ResolveTypes( - Func> finder, - TypeResolutionKind resolutionType, - bool cacheResult) - { - using (var readLock = new UpgradeableReadLock(Locker)) - { - var typesFound = new List(); - - using (_logger.TraceDuration( - String.Format("Starting resolution types of {0}", typeof(T).FullName), - String.Format("Completed resolution of types of {0}, found {1}", typeof(T).FullName, typesFound.Count))) - { - //check if the TypeList already exists, if so return it, if not we'll create it - var typeList = _types.SingleOrDefault(x => x.IsTypeList(resolutionType)); - - //need to put some logging here to try to figure out why this is happening: http://issues.umbraco.org/issue/U4-3505 - if (cacheResult && typeList != null) - { - _logger.Logger.Debug("Existing typeList found for {0} with resolution type {1}", () => typeof(T), () => resolutionType); - } - - //if we're not caching the result then proceed, or if the type list doesn't exist then proceed - if (cacheResult == false || typeList == null) - { - //upgrade to a write lock since we're adding to the collection - readLock.UpgradeToWriteLock(); - - typeList = new TypeList(resolutionType); - - //we first need to look into our cache file (this has nothing to do with the 'cacheResult' parameter which caches in memory). - //if assemblies have not changed and the cache file actually exists, then proceed to try to lookup by the cache file. - if (RequiresRescanning == false && File.Exists(GetPluginListFilePath())) - { - var fileCacheResult = TryGetCachedPluginsFromFile(resolutionType); - - //here we need to identify if the CachedPluginNotFoundInFile was the exception, if it was then we need to re-scan - //in some cases the plugin will not have been scanned for on application startup, but the assemblies haven't changed - //so in this instance there will never be a result. - if (fileCacheResult.Exception != null && fileCacheResult.Exception is CachedPluginNotFoundInFileException) - { - _logger.Logger.Debug("Tried to find typelist for type {0} and resolution {1} in file cache but the type was not found so loading types by assembly scan ", () => typeof(T), () => resolutionType); - - //we don't have a cache for this so proceed to look them up by scanning - LoadViaScanningAndUpdateCacheFile(typeList, resolutionType, finder); - } - else - { - if (fileCacheResult.Success) - { - var successfullyLoadedFromCache = true; - //we have a previous cache for this so we don't need to scan we just load what has been found in the file - foreach (var t in fileCacheResult.Result) - { - try - { - //we use the build manager to ensure we get all types loaded, this is slightly slower than - //Type.GetType but if the types in the assembly aren't loaded yet then we have problems with that. - var type = BuildManager.GetType(t, true); - typeList.AddType(type); - } - catch (Exception ex) - { - //if there are any exceptions loading types, we have to exist, this should never happen so - //we will need to revert to scanning for types. - successfullyLoadedFromCache = false; - _logger.Logger.Error("Could not load a cached plugin type: " + t + " now reverting to re-scanning assemblies for the base type: " + typeof(T).FullName, ex); - break; - } - } - if (successfullyLoadedFromCache == false) - { - //we need to manually load by scanning if loading from the file was not successful. - LoadViaScanningAndUpdateCacheFile(typeList, resolutionType, finder); - } - else - { - _logger.Logger.Debug("Loaded plugin types {0} with resolution {1} from persisted cache", () => typeof(T), () => resolutionType); - } - } - } - } - else - { - _logger.Logger.Debug("Assembly changes detected, loading types {0} for resolution {1} by assembly scan", () => typeof(T), () => resolutionType); - - //we don't have a cache for this so proceed to look them up by scanning - LoadViaScanningAndUpdateCacheFile(typeList, resolutionType, finder); - } - - //only add the cache if we are to cache the results - if (cacheResult) - { - //add the type list to the collection - var added = _types.Add(typeList); - - _logger.Logger.Debug("Caching of typelist for type {0} and resolution {1} was successful = {2}", () => typeof(T), () => resolutionType, () => added); - - } - } - typesFound = typeList.GetTypes().ToList(); - } - - return typesFound; - } - } - - /// - /// This method invokes the finder which scans the assemblies for the types and then loads the result into the type finder. - /// Once the results are loaded, we update the cached type xml file - /// - /// - /// - /// - /// - /// THIS METHODS IS NOT THREAD SAFE - /// - private void LoadViaScanningAndUpdateCacheFile(TypeList typeList, TypeResolutionKind resolutionKind, Func> finder) - { - //we don't have a cache for this so proceed to look them up by scanning - foreach (var t in finder()) - { - typeList.AddType(t); - } - UpdateCachedPluginsFile(typeList.GetTypes(), resolutionKind); - } - - #region Public Methods - /// - /// Generic method to find the specified type and cache the result - /// - /// - /// - public IEnumerable ResolveTypes(bool cacheResult = true, IEnumerable specificAssemblies = null) - { - return ResolveTypes( - () => TypeFinder.FindClassesOfType(specificAssemblies ?? AssembliesToScan), - TypeResolutionKind.FindAllTypes, - cacheResult); - } - - /// - /// Generic method to find the specified type that has an attribute and cache the result - /// - /// - /// - /// - public IEnumerable ResolveTypesWithAttribute(bool cacheResult = true, IEnumerable specificAssemblies = null) - where TAttribute : Attribute - { - return ResolveTypes( - () => TypeFinder.FindClassesOfTypeWithAttribute(specificAssemblies ?? AssembliesToScan), - TypeResolutionKind.FindTypesWithAttribute, - cacheResult); - } - - /// - /// Generic method to find any type that has the specified attribute - /// - /// - /// - public IEnumerable ResolveAttributedTypes(bool cacheResult = true, IEnumerable specificAssemblies = null) - where TAttribute : Attribute - { - return ResolveTypes( - () => TypeFinder.FindClassesWithAttribute(specificAssemblies ?? AssembliesToScan), - TypeResolutionKind.FindAttributedTypes, - cacheResult); - } - #endregion - - /// - /// Used for unit tests - /// - /// - internal HashSet GetTypeLists() - { - return _types; - } - - - - #region Private classes/Enums - - /// - /// The type of resolution being invoked - /// - internal enum TypeResolutionKind - { - FindAllTypes, - FindAttributedTypes, - FindTypesWithAttribute - } - - internal abstract class TypeList - { - public abstract void AddType(Type t); - public abstract bool IsTypeList(TypeResolutionKind resolutionType); - public abstract IEnumerable GetTypes(); - } - - internal class TypeList : TypeList - { - private readonly TypeResolutionKind _resolutionType; - - public TypeList(TypeResolutionKind resolutionType) - { - _resolutionType = resolutionType; - } - - private readonly List _types = new List(); - - public override void AddType(Type t) - { - //if the type is an attribute type we won't do the type check because typeof is going to be the - //attribute type whereas the 't' type is the object type found with the attribute. - if (_resolutionType == TypeResolutionKind.FindAttributedTypes || t.IsType()) - { - _types.Add(t); - } - } - - /// - /// Returns true if the current TypeList is of the same lookup type - /// - /// - /// - /// - public override bool IsTypeList(TypeResolutionKind resolutionType) - { - return _resolutionType == resolutionType && (typeof(T)) == typeof(TLookup); - } - - public override IEnumerable GetTypes() - { - return _types; - } - } - - /// - /// This class is used simply to determine that a plugin was not found in the cache plugin list with the specified - /// TypeResolutionKind. - /// - internal class CachedPluginNotFoundInFileException : Exception - { - - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Web.Compilation; +using System.Xml.Linq; +using Umbraco.Core.Cache; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core._Legacy.PackageActions; +using File = System.IO.File; + +namespace Umbraco.Core.Plugins +{ + /// + /// Used to resolve all plugin types and cache them and is also used to instantiate plugin types + /// + /// + /// + /// This class should be used to resolve all plugin types, the TypeFinder should not be used directly! + /// + /// This class can expose extension methods to resolve custom plugins + /// + /// Before this class resolves any plugins it checks if the hash has changed for the DLLs in the /bin folder, if it hasn't + /// it will use the cached resolved plugins that it has already found which means that no assembly scanning is necessary. This leads + /// to much faster startup times. + /// + public class PluginManager + { + /// + /// Creates a new PluginManager with an ApplicationContext instance which ensures that the plugin xml + /// file is cached temporarily until app startup completes. + /// + /// + /// + /// + /// + public PluginManager(IServiceProvider serviceProvider, IRuntimeCacheProvider runtimeCache, ProfilingLogger logger, bool detectChanges = true) + { + if (serviceProvider == null) throw new ArgumentNullException("serviceProvider"); + if (runtimeCache == null) throw new ArgumentNullException("runtimeCache"); + if (logger == null) throw new ArgumentNullException("logger"); + + _serviceProvider = serviceProvider; + _runtimeCache = runtimeCache; + _logger = logger; + + _tempFolder = IOHelper.MapPath("~/App_Data/TEMP/PluginCache"); + //create the folder if it doesn't exist + if (Directory.Exists(_tempFolder) == false) + { + Directory.CreateDirectory(_tempFolder); + } + + var pluginListFile = GetPluginListFilePath(); + + //this is a check for legacy changes, before we didn't store the TypeResolutionKind in the file which was a mistake, + //so we need to detect if the old file is there without this attribute, if it is then we delete it + if (DetectLegacyPluginListFile()) + { + File.Delete(pluginListFile); + } + + if (detectChanges) + { + //first check if the cached hash is 0, if it is then we ne + //do the check if they've changed + RequiresRescanning = (CachedAssembliesHash != CurrentAssembliesHash) || CachedAssembliesHash == 0; + //if they have changed, we need to write the new file + if (RequiresRescanning) + { + //if the hash has changed, clear out the persisted list no matter what, this will force + // rescanning of all plugin types including lazy ones. + // http://issues.umbraco.org/issue/U4-4789 + File.Delete(pluginListFile); + + WriteCachePluginsHash(); + } + } + else + { + + //if the hash has changed, clear out the persisted list no matter what, this will force + // rescanning of all plugin types including lazy ones. + // http://issues.umbraco.org/issue/U4-4789 + File.Delete(pluginListFile); + + //always set to true if we're not detecting (generally only for testing) + RequiresRescanning = true; + } + } + + private readonly IServiceProvider _serviceProvider; + private readonly IRuntimeCacheProvider _runtimeCache; + private readonly ProfilingLogger _logger; + private const string CacheKey = "umbraco-plugins.list"; + static PluginManager _resolver; + private readonly string _tempFolder; + private long _cachedAssembliesHash = -1; + private long _currentAssembliesHash = -1; + private static bool _initialized = false; + private static object _singletonLock = new object(); + + /// + /// We will ensure that no matter what, only one of these is created, this is to ensure that caching always takes place + /// + /// + /// The setter is generally only used for unit tests + /// + public static PluginManager Current + { + get + { + return LazyInitializer.EnsureInitialized(ref _resolver, ref _initialized, ref _singletonLock, () => + { + if (ApplicationContext.Current == null) + { + var logger = LoggerResolver.HasCurrent ? LoggerResolver.Current.Logger : new DebugDiagnosticsLogger(); + var profiler = ProfilerResolver.HasCurrent ? ProfilerResolver.Current.Profiler : new LogProfiler(logger); + return new PluginManager( + new ActivatorServiceProvider(), + new NullCacheProvider(), + new ProfilingLogger(logger, profiler)); + } + return new PluginManager( + new ActivatorServiceProvider(), + ApplicationContext.Current.ApplicationCache.RuntimeCache, + ApplicationContext.Current.ProfilingLogger); + }); + } + set + { + _initialized = true; + _resolver = value; + } + } + + #region Hash checking methods + + + /// + /// Returns a bool if the assemblies in the /bin, app_code, global.asax, etc... have changed since they were last hashed. + /// + internal bool RequiresRescanning { get; private set; } + + /// + /// Returns the currently cached hash value of the scanned assemblies in the /bin folder. Returns 0 + /// if no cache is found. + /// + /// + internal long CachedAssembliesHash + { + get + { + if (_cachedAssembliesHash != -1) + return _cachedAssembliesHash; + + var filePath = GetPluginHashFilePath(); + if (!File.Exists(filePath)) + return 0; + var hash = File.ReadAllText(filePath, Encoding.UTF8); + Int64 val; + if (Int64.TryParse(hash, out val)) + { + _cachedAssembliesHash = val; + return _cachedAssembliesHash; + } + //it could not parse for some reason so we'll return 0. + return 0; + } + } + + /// + /// Returns the current assemblies hash based on creating a hash from the assemblies in the /bin + /// + /// + internal long CurrentAssembliesHash + { + get + { + if (_currentAssembliesHash != -1) + return _currentAssembliesHash; + + _currentAssembliesHash = GetFileHash( + new List> + { + //add the bin folder and everything in it + new Tuple(new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Bin)), false), + //add the app code folder and everything in it + new Tuple(new DirectoryInfo(IOHelper.MapPath("~/App_Code")), false), + //add the global.asax (the app domain also monitors this, if it changes will do a full restart) + new Tuple(new FileInfo(IOHelper.MapPath("~/global.asax")), false), + + //add the trees.config - use the contents to create the has since this gets resaved on every app startup! + new Tuple(new FileInfo(IOHelper.MapPath(SystemDirectories.Config + "/trees.config")), true) + }, _logger + ); + return _currentAssembliesHash; + } + } + + /// + /// Writes the assembly hash file + /// + private void WriteCachePluginsHash() + { + var filePath = GetPluginHashFilePath(); + File.WriteAllText(filePath, CurrentAssembliesHash.ToString(), Encoding.UTF8); + } + + /// + /// Returns a unique hash for the combination of FileInfo objects passed in + /// + /// + /// A collection of files and whether or not to use their file contents to determine the hash or the file's properties + /// (true will make a hash based on it's contents) + /// + /// + internal static long GetFileHash(IEnumerable> filesAndFolders, ProfilingLogger logger) + { + using (logger.TraceDuration("Determining hash of code files on disk", "Hash determined")) + { + var hashCombiner = new HashCodeCombiner(); + + //get the file info's to check + var fileInfos = filesAndFolders.Where(x => x.Item2 == false).ToArray(); + var fileContents = filesAndFolders.Except(fileInfos); + + //add each unique folder/file to the hash + foreach (var i in fileInfos.Select(x => x.Item1).DistinctBy(x => x.FullName)) + { + hashCombiner.AddFileSystemItem(i); + } + + //add each unique file's contents to the hash + foreach (var i in fileContents.Select(x => x.Item1).DistinctBy(x => x.FullName)) + { + if (File.Exists(i.FullName)) + { + var content = File.ReadAllText(i.FullName).Replace("\r\n", string.Empty).Replace("\n", string.Empty).Replace("\r", string.Empty); + hashCombiner.AddCaseInsensitiveString(content); + } + + } + + return ConvertPluginsHashFromHex(hashCombiner.GetCombinedHashCode()); + } + } + + internal static long GetFileHash(IEnumerable filesAndFolders, ProfilingLogger logger) + { + using (logger.TraceDuration("Determining hash of code files on disk", "Hash determined")) + { + var hashCombiner = new HashCodeCombiner(); + + //add each unique folder/file to the hash + foreach (var i in filesAndFolders.DistinctBy(x => x.FullName)) + { + hashCombiner.AddFileSystemItem(i); + } + return ConvertPluginsHashFromHex(hashCombiner.GetCombinedHashCode()); + } + } + + /// + /// Converts the hash value of current plugins to long from string + /// + /// + /// + internal static long ConvertPluginsHashFromHex(string val) + { + long outVal; + if (Int64.TryParse(val, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out outVal)) + { + return outVal; + } + return 0; + } + + /// + /// Attempts to resolve the list of plugin + assemblies found in the runtime for the base type 'T' passed in. + /// If the cache file doesn't exist, fails to load, is corrupt or the type 'T' element is not found then + /// a false attempt is returned. + /// + /// + /// + internal Attempt> TryGetCachedPluginsFromFile(TypeResolutionKind resolutionType) + { + var filePath = GetPluginListFilePath(); + if (!File.Exists(filePath)) + return Attempt>.Fail(); + + try + { + //we will load the xml document, if the app context exist, we will load it from the cache (which is only around for 5 minutes) + //while the app boots up, this should save some IO time on app startup when the app context is there (which is always unless in unit tests) + var xml = _runtimeCache.GetCacheItem(CacheKey, + () => XDocument.Load(filePath), + new TimeSpan(0, 0, 5, 0)); + + if (xml.Root == null) + return Attempt>.Fail(); + + var typeElement = xml.Root.Elements() + .SingleOrDefault(x => + x.Name.LocalName == "baseType" + && ((string)x.Attribute("type")) == typeof(T).FullName + && ((string)x.Attribute("resolutionType")) == resolutionType.ToString()); + + //return false but specify this exception type so we can detect it + if (typeElement == null) + return Attempt>.Fail(new CachedPluginNotFoundInFileException()); + + //return success + return Attempt.Succeed(typeElement.Elements("add") + .Select(x => (string)x.Attribute("type"))); + } + catch (Exception ex) + { + //if the file is corrupted, etc... return false + return Attempt>.Fail(ex); + } + } + + /// + /// Removes cache files and internal cache as well + /// + /// + /// Generally only used for resetting cache, for example during the install process + /// + public void ClearPluginCache() + { + var path = GetPluginListFilePath(); + if (File.Exists(path)) + File.Delete(path); + path = GetPluginHashFilePath(); + if (File.Exists(path)) + File.Delete(path); + + _runtimeCache.ClearCacheItem(CacheKey); + } + + private string GetPluginListFilePath() + { + return Path.Combine(_tempFolder, string.Format("umbraco-plugins.{0}.list", NetworkHelper.FileSafeMachineName)); + } + + private string GetPluginHashFilePath() + { + return Path.Combine(_tempFolder, string.Format("umbraco-plugins.{0}.hash", NetworkHelper.FileSafeMachineName)); + } + + /// + /// This will return true if the plugin list file is a legacy one + /// + /// + /// + /// This method exists purely due to an error in 4.11. We were writing the plugin list file without the + /// type resolution kind which will have caused some problems. Now we detect this legacy file and if it is detected + /// we remove it so it can be recreated properly. + /// + internal bool DetectLegacyPluginListFile() + { + var filePath = GetPluginListFilePath(); + if (!File.Exists(filePath)) + return false; + + try + { + var xml = XDocument.Load(filePath); + if (xml.Root == null) + return false; + + var typeElement = xml.Root.Elements() + .FirstOrDefault(x => x.Name.LocalName == "baseType"); + + if (typeElement == null) + return false; + + //now check if the typeElement is missing the resolutionType attribute + return typeElement.Attributes().All(x => x.Name.LocalName != "resolutionType"); + } + catch (Exception) + { + //if the file is corrupted, etc... return true so it is removed + return true; + } + } + + /// + /// Adds/Updates the type list for the base type 'T' in the cached file + /// + /// + /// + /// + /// + /// THIS METHOD IS NOT THREAD SAFE + /// + /// + /// + /// + /// + /// + /// + /// + /// ]]> + /// + internal void UpdateCachedPluginsFile(IEnumerable typesFound, TypeResolutionKind resolutionType) + { + var filePath = GetPluginListFilePath(); + XDocument xml; + try + { + xml = XDocument.Load(filePath); + } + catch + { + //if there's an exception loading then this is somehow corrupt, we'll just replace it. + File.Delete(filePath); + //create the document and the root + xml = new XDocument(new XElement("plugins")); + } + if (xml.Root == null) + { + //if for some reason there is no root, create it + xml.Add(new XElement("plugins")); + } + //find the type 'T' element to add or update + var typeElement = xml.Root.Elements() + .SingleOrDefault(x => + x.Name.LocalName == "baseType" + && ((string)x.Attribute("type")) == typeof(T).FullName + && ((string)x.Attribute("resolutionType")) == resolutionType.ToString()); + + if (typeElement == null) + { + //create the type element + typeElement = new XElement("baseType", + new XAttribute("type", typeof(T).FullName), + new XAttribute("resolutionType", resolutionType.ToString())); + //then add it to the root + xml.Root.Add(typeElement); + } + + + //now we have the type element, we need to clear any previous types as children and add/update it with new ones + typeElement.ReplaceNodes(typesFound.Select(x => new XElement("add", new XAttribute("type", x.AssemblyQualifiedName)))); + //save the xml file + xml.Save(filePath); + } + + #endregion + + private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); + private readonly HashSet _types = new HashSet(); + private IEnumerable _assemblies; + + /// + /// Returns all found property editors (based on the resolved Iparameter editors - this saves a scan) + /// + internal IEnumerable ResolvePropertyEditors() + { + //return all proeprty editor types found except for the base property editor type + return ResolveTypes() + .Where(x => x.IsType()) + .Except(new[] { typeof(PropertyEditor) }); + } + + /// + /// Returns all found parameter editors (which includes property editors) + /// + internal IEnumerable ResolveParameterEditors() + { + //return all paramter editor types found except for the base property editor type + return ResolveTypes() + .Except(new[] { typeof(ParameterEditor), typeof(PropertyEditor) }); + } + + /// + /// Returns all available IApplicationStartupHandler objects + /// + /// + internal IEnumerable ResolveApplicationStartupHandlers() + { + return ResolveTypes(); + } + + /// + /// Returns all classes of type ICacheRefresher + /// + /// + internal IEnumerable ResolveCacheRefreshers() + { + return ResolveTypes(); + } + + /// + /// Returns all available IPackageAction in application + /// + /// + internal IEnumerable ResolvePackageActions() + { + return ResolveTypes(); + } + + + + /// + /// Returns all mapper types that have a MapperFor attribute defined + /// + /// + internal IEnumerable ResolveAssignedMapperTypes() + { + return ResolveTypesWithAttribute(); + } + + /// + /// Gets/sets which assemblies to scan when type finding, generally used for unit testing, if not explicitly set + /// this will search all assemblies known to have plugins and exclude ones known to not have them. + /// + internal IEnumerable AssembliesToScan + { + get { return _assemblies ?? (_assemblies = TypeFinder.GetAssembliesWithKnownExclusions()); } + set { _assemblies = value; } + } + + private IEnumerable ResolveTypes( + Func> finder, + TypeResolutionKind resolutionType, + bool cacheResult) + { + using (var readLock = new UpgradeableReadLock(Locker)) + { + var typesFound = new List(); + + using (_logger.TraceDuration( + String.Format("Starting resolution types of {0}", typeof(T).FullName), + String.Format("Completed resolution of types of {0}, found {1}", typeof(T).FullName, typesFound.Count))) + { + //check if the TypeList already exists, if so return it, if not we'll create it + var typeList = _types.SingleOrDefault(x => x.IsTypeList(resolutionType)); + + //need to put some logging here to try to figure out why this is happening: http://issues.umbraco.org/issue/U4-3505 + if (cacheResult && typeList != null) + { + _logger.Logger.Debug("Existing typeList found for {0} with resolution type {1}", () => typeof(T), () => resolutionType); + } + + //if we're not caching the result then proceed, or if the type list doesn't exist then proceed + if (cacheResult == false || typeList == null) + { + //upgrade to a write lock since we're adding to the collection + readLock.UpgradeToWriteLock(); + + typeList = new TypeList(resolutionType); + + //we first need to look into our cache file (this has nothing to do with the 'cacheResult' parameter which caches in memory). + //if assemblies have not changed and the cache file actually exists, then proceed to try to lookup by the cache file. + if (RequiresRescanning == false && File.Exists(GetPluginListFilePath())) + { + var fileCacheResult = TryGetCachedPluginsFromFile(resolutionType); + + //here we need to identify if the CachedPluginNotFoundInFile was the exception, if it was then we need to re-scan + //in some cases the plugin will not have been scanned for on application startup, but the assemblies haven't changed + //so in this instance there will never be a result. + if (fileCacheResult.Exception != null && fileCacheResult.Exception is CachedPluginNotFoundInFileException) + { + _logger.Logger.Debug("Tried to find typelist for type {0} and resolution {1} in file cache but the type was not found so loading types by assembly scan ", () => typeof(T), () => resolutionType); + + //we don't have a cache for this so proceed to look them up by scanning + LoadViaScanningAndUpdateCacheFile(typeList, resolutionType, finder); + } + else + { + if (fileCacheResult.Success) + { + var successfullyLoadedFromCache = true; + //we have a previous cache for this so we don't need to scan we just load what has been found in the file + foreach (var t in fileCacheResult.Result) + { + try + { + //we use the build manager to ensure we get all types loaded, this is slightly slower than + //Type.GetType but if the types in the assembly aren't loaded yet then we have problems with that. + var type = BuildManager.GetType(t, true); + typeList.AddType(type); + } + catch (Exception ex) + { + //if there are any exceptions loading types, we have to exist, this should never happen so + //we will need to revert to scanning for types. + successfullyLoadedFromCache = false; + _logger.Logger.Error("Could not load a cached plugin type: " + t + " now reverting to re-scanning assemblies for the base type: " + typeof(T).FullName, ex); + break; + } + } + if (successfullyLoadedFromCache == false) + { + //we need to manually load by scanning if loading from the file was not successful. + LoadViaScanningAndUpdateCacheFile(typeList, resolutionType, finder); + } + else + { + _logger.Logger.Debug("Loaded plugin types {0} with resolution {1} from persisted cache", () => typeof(T), () => resolutionType); + } + } + } + } + else + { + _logger.Logger.Debug("Assembly changes detected, loading types {0} for resolution {1} by assembly scan", () => typeof(T), () => resolutionType); + + //we don't have a cache for this so proceed to look them up by scanning + LoadViaScanningAndUpdateCacheFile(typeList, resolutionType, finder); + } + + //only add the cache if we are to cache the results + if (cacheResult) + { + //add the type list to the collection + var added = _types.Add(typeList); + + _logger.Logger.Debug("Caching of typelist for type {0} and resolution {1} was successful = {2}", () => typeof(T), () => resolutionType, () => added); + + } + } + typesFound = typeList.GetTypes().ToList(); + } + + return typesFound; + } + } + + /// + /// This method invokes the finder which scans the assemblies for the types and then loads the result into the type finder. + /// Once the results are loaded, we update the cached type xml file + /// + /// + /// + /// + /// + /// THIS METHODS IS NOT THREAD SAFE + /// + private void LoadViaScanningAndUpdateCacheFile(TypeList typeList, TypeResolutionKind resolutionKind, Func> finder) + { + //we don't have a cache for this so proceed to look them up by scanning + foreach (var t in finder()) + { + typeList.AddType(t); + } + UpdateCachedPluginsFile(typeList.GetTypes(), resolutionKind); + } + + #region Public Methods + /// + /// Generic method to find the specified type and cache the result + /// + /// + /// + public IEnumerable ResolveTypes(bool cacheResult = true, IEnumerable specificAssemblies = null) + { + return ResolveTypes( + () => TypeFinder.FindClassesOfType(specificAssemblies ?? AssembliesToScan), + TypeResolutionKind.FindAllTypes, + cacheResult); + } + + /// + /// Generic method to find the specified type that has an attribute and cache the result + /// + /// + /// + /// + public IEnumerable ResolveTypesWithAttribute(bool cacheResult = true, IEnumerable specificAssemblies = null) + where TAttribute : Attribute + { + return ResolveTypes( + () => TypeFinder.FindClassesOfTypeWithAttribute(specificAssemblies ?? AssembliesToScan), + TypeResolutionKind.FindTypesWithAttribute, + cacheResult); + } + + /// + /// Generic method to find any type that has the specified attribute + /// + /// + /// + public IEnumerable ResolveAttributedTypes(bool cacheResult = true, IEnumerable specificAssemblies = null) + where TAttribute : Attribute + { + return ResolveTypes( + () => TypeFinder.FindClassesWithAttribute(specificAssemblies ?? AssembliesToScan), + TypeResolutionKind.FindAttributedTypes, + cacheResult); + } + #endregion + + /// + /// Used for unit tests + /// + /// + internal HashSet GetTypeLists() + { + return _types; + } + + + + #region Private classes/Enums + + /// + /// The type of resolution being invoked + /// + internal enum TypeResolutionKind + { + FindAllTypes, + FindAttributedTypes, + FindTypesWithAttribute + } + + internal abstract class TypeList + { + public abstract void AddType(Type t); + public abstract bool IsTypeList(TypeResolutionKind resolutionType); + public abstract IEnumerable GetTypes(); + } + + internal class TypeList : TypeList + { + private readonly TypeResolutionKind _resolutionType; + + public TypeList(TypeResolutionKind resolutionType) + { + _resolutionType = resolutionType; + } + + private readonly List _types = new List(); + + public override void AddType(Type t) + { + //if the type is an attribute type we won't do the type check because typeof is going to be the + //attribute type whereas the 't' type is the object type found with the attribute. + if (_resolutionType == TypeResolutionKind.FindAttributedTypes || t.IsType()) + { + _types.Add(t); + } + } + + /// + /// Returns true if the current TypeList is of the same lookup type + /// + /// + /// + /// + public override bool IsTypeList(TypeResolutionKind resolutionType) + { + return _resolutionType == resolutionType && (typeof(T)) == typeof(TLookup); + } + + public override IEnumerable GetTypes() + { + return _types; + } + } + + /// + /// This class is used simply to determine that a plugin was not found in the cache plugin list with the specified + /// TypeResolutionKind. + /// + internal class CachedPluginNotFoundInFileException : Exception + { + + } + + #endregion + } +} diff --git a/src/Umbraco.Core/TypeFinder.cs b/src/Umbraco.Core/Plugins/TypeFinder.cs similarity index 97% rename from src/Umbraco.Core/TypeFinder.cs rename to src/Umbraco.Core/Plugins/TypeFinder.cs index f970cf225b..2d02589154 100644 --- a/src/Umbraco.Core/TypeFinder.cs +++ b/src/Umbraco.Core/Plugins/TypeFinder.cs @@ -1,689 +1,682 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Security; -using System.Text; -using System.Threading; -using System.Web; -using System.Web.Compilation; -using System.Web.Hosting; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; - -namespace Umbraco.Core -{ - - /// - /// A utility class to find all classes of a certain type by reflection in the current bin folder - /// of the web application. - /// - public static class TypeFinder - { - private static volatile HashSet _localFilteredAssemblyCache = null; - private static readonly object LocalFilteredAssemblyCacheLocker = new object(); - - /// - /// lazily load a reference to all assemblies and only local assemblies. - /// This is a modified version of: http://www.dominicpettifer.co.uk/Blog/44/how-to-get-a-reference-to-all-assemblies-in-the--bin-folder - /// - /// - /// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been - /// loaded in the CLR, not all assemblies. - /// See these threads: - /// http://issues.umbraco.org/issue/U5-198 - /// http://stackoverflow.com/questions/3552223/asp-net-appdomain-currentdomain-getassemblies-assemblies-missing-after-app - /// http://stackoverflow.com/questions/2477787/difference-between-appdomain-getassemblies-and-buildmanager-getreferencedassembl - /// - internal static HashSet GetAllAssemblies() - { - return AllAssemblies.Value; - } - - //Lazy access to the all assemblies list - private static readonly Lazy> AllAssemblies = new Lazy>(() => - { - HashSet assemblies = null; - try - { - var isHosted = HttpContext.Current != null; - - try - { - if (isHosted) - { - assemblies = new HashSet(BuildManager.GetReferencedAssemblies().Cast()); - } - } - catch (InvalidOperationException e) - { - if (!(e.InnerException is SecurityException)) - throw; - } - - if (assemblies == null) - { - //NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have - // already been loaded in to the app domain, instead we will look directly into the bin folder and load each one. - var binFolder = IOHelper.GetRootDirectoryBinFolder(); - var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - //var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; - //var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - assemblies = new HashSet(); - foreach (var a in binAssemblyFiles) - { - try - { - var assName = AssemblyName.GetAssemblyName(a); - var ass = Assembly.Load(assName); - assemblies.Add(ass); - } - catch (Exception e) - { - if (e is SecurityException || e is BadImageFormatException) - { - //swallow these exceptions - } - else - { - throw; - } - } - } - } - - //if for some reason they are still no assemblies, then use the AppDomain to load in already loaded assemblies. - if (!assemblies.Any()) - { - foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) - { - assemblies.Add(a); - } - } - - //here we are trying to get the App_Code assembly - var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported - var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code"))); - //check if the folder exists and if there are any files in it with the supported file extensions - if (appCodeFolder.Exists && (fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any()))) - { - try - { - var appCodeAssembly = Assembly.Load("App_Code"); - if (!assemblies.Contains(appCodeAssembly)) // BuildManager will find App_Code already - assemblies.Add(appCodeAssembly); - } - catch (FileNotFoundException ex) - { - //this will occur if it cannot load the assembly - LogHelper.Error(typeof(TypeFinder), "Could not load assembly App_Code", ex); - } - } - } - catch (InvalidOperationException e) - { - if (!(e.InnerException is SecurityException)) - throw; - } - - return assemblies; - }); - - /// - /// Return a list of found local Assemblies excluding the known assemblies we don't want to scan - /// and exluding the ones passed in and excluding the exclusion list filter, the results of this are - /// cached for perforance reasons. - /// - /// - /// - internal static HashSet GetAssembliesWithKnownExclusions( - IEnumerable excludeFromResults = null) - { - if (_localFilteredAssemblyCache == null) - { - lock (LocalFilteredAssemblyCacheLocker) - { - //double check - if (_localFilteredAssemblyCache == null) - { - _localFilteredAssemblyCache = new HashSet(); - var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter); - foreach (var a in assemblies) - { - _localFilteredAssemblyCache.Add(a); - } - } - } - } - return _localFilteredAssemblyCache; - } - - /// - /// Return a distinct list of found local Assemblies and exluding the ones passed in and excluding the exclusion list filter - /// - /// - /// - /// - private static IEnumerable GetFilteredAssemblies( - IEnumerable excludeFromResults = null, - string[] exclusionFilter = null) - { - if (excludeFromResults == null) - excludeFromResults = new HashSet(); - if (exclusionFilter == null) - exclusionFilter = new string[] { }; - - return GetAllAssemblies() - .Where(x => !excludeFromResults.Contains(x) - && !x.GlobalAssemblyCache - && !exclusionFilter.Any(f => x.FullName.StartsWith(f))); - } - - /// - /// this is our assembly filter to filter out known types that def dont contain types we'd like to find or plugins - /// - /// - /// NOTE the comma vs period... comma delimits the name in an Assembly FullName property so if it ends with comma then its an exact name match - /// NOTE this means that "foo." will NOT exclude "foo.dll" but only "foo.*.dll" - /// - internal static readonly string[] KnownAssemblyExclusionFilter = new[] - { - "mscorlib,", - "System.", - "Antlr3.", - "Autofac.", - "Autofac,", - "Castle.", - "ClientDependency.", - "DataAnnotationsExtensions.", - "DataAnnotationsExtensions,", - "Dynamic,", - "HtmlDiff,", - "Iesi.Collections,", - "log4net,", - "Microsoft.", - "Newtonsoft.", - "NHibernate.", - "NHibernate,", - "NuGet.", - "RouteDebugger,", - "SqlCE4Umbraco,", - "umbraco.datalayer,", - "umbraco.interfaces,", - //"umbraco.providers,", - //"Umbraco.Web.UI,", - "umbraco.webservices", - "Lucene.", - "Examine,", - "Examine.", - "ServiceStack.", - "MySql.", - "HtmlAgilityPack.", - "TidyNet.", - "ICSharpCode.", - "CookComputing.", - "AutoMapper,", - "AutoMapper.", - "AzureDirectory,", - "itextsharp,", - "UrlRewritingNet.", - "HtmlAgilityPack,", - "MiniProfiler,", - "Moq,", - "nunit.framework,", - "TidyNet,", - "WebDriver," - }; - - /// - /// Finds any classes derived from the type T that contain the attribute TAttribute - /// - /// - /// - /// - public static IEnumerable FindClassesOfTypeWithAttribute() - where TAttribute : Attribute - { - return FindClassesOfTypeWithAttribute(GetAssembliesWithKnownExclusions(), true); - } - - /// - /// Finds any classes derived from the type T that contain the attribute TAttribute - /// - /// - /// - /// - /// - public static IEnumerable FindClassesOfTypeWithAttribute(IEnumerable assemblies) - where TAttribute : Attribute - { - return FindClassesOfTypeWithAttribute(assemblies, true); - } - - /// - /// Finds any classes derived from the type T that contain the attribute TAttribute - /// - /// - /// - /// - /// - /// - public static IEnumerable FindClassesOfTypeWithAttribute( - IEnumerable assemblies, - bool onlyConcreteClasses) - where TAttribute : Attribute - { - return FindClassesOfTypeWithAttribute(typeof(T), assemblies, onlyConcreteClasses); - } - - /// - /// Finds any classes derived from the assignTypeFrom Type that contain the attribute TAttribute - /// - /// - /// - /// - /// - /// - public static IEnumerable FindClassesOfTypeWithAttribute( - Type assignTypeFrom, - IEnumerable assemblies, - bool onlyConcreteClasses) - where TAttribute : Attribute - { - if (assemblies == null) throw new ArgumentNullException("assemblies"); - - return GetClasses(assignTypeFrom, assemblies, onlyConcreteClasses, - //the additional filter will ensure that any found types also have the attribute applied. - t => t.GetCustomAttributes(false).Any()); - } - - /// - /// Searches all filtered local assemblies specified for classes of the type passed in. - /// - /// - /// - public static IEnumerable FindClassesOfType() - { - return FindClassesOfType(GetAssembliesWithKnownExclusions(), true); - } - - /// - /// Returns all types found of in the assemblies specified of type T - /// - /// - /// - /// - /// - public static IEnumerable FindClassesOfType(IEnumerable assemblies, bool onlyConcreteClasses) - { - if (assemblies == null) throw new ArgumentNullException("assemblies"); - - return GetClasses(typeof(T), assemblies, onlyConcreteClasses); - } - - /// - /// Returns all types found of in the assemblies specified of type T - /// - /// - /// - /// - public static IEnumerable FindClassesOfType(IEnumerable assemblies) - { - return FindClassesOfType(assemblies, true); - } - - /// - /// Finds the classes with attribute. - /// - /// - /// The assemblies. - /// if set to true only concrete classes. - /// - public static IEnumerable FindClassesWithAttribute(IEnumerable assemblies, bool onlyConcreteClasses) - where T : Attribute - { - return FindClassesWithAttribute(typeof(T), assemblies, onlyConcreteClasses); - } - - /// - /// Finds any classes with the attribute. - /// - /// The attribute type - /// The assemblies. - /// if set to true only concrete classes. - /// - public static IEnumerable FindClassesWithAttribute( - Type attributeType, - IEnumerable assemblies, - bool onlyConcreteClasses) - { - if (assemblies == null) throw new ArgumentNullException("assemblies"); - - if (TypeHelper.IsTypeAssignableFrom(attributeType) == false) - throw new ArgumentException("The type specified: " + attributeType + " is not an Attribute type"); - - var foundAttributedTypes = new HashSet(); - - var assemblyList = assemblies.ToArray(); - - //find all assembly references that are referencing the attribute type's assembly since we - //should only be scanning those assemblies because any other assembly will definitely not - //contain a class that has this attribute. - var referencedAssemblies = TypeHelper.GetReferencedAssemblies(attributeType, assemblyList); - - //get a list of non-referenced assemblies (we'll use this when we recurse below) - var otherAssemblies = assemblyList.Where(x => referencedAssemblies.Contains(x) == false).ToArray(); - - //loop through the referenced assemblies - foreach (var a in referencedAssemblies) - { - //get all types in this assembly - var allTypes = GetTypesWithFormattedException(a) - .ToArray(); - - var attributedTypes = new Type[] { }; - try - { - //now filter the types based on the onlyConcreteClasses flag, not interfaces, not static classes but have - //the specified attribute - attributedTypes = allTypes - .Where(t => (TypeHelper.IsNonStaticClass(t) - && (onlyConcreteClasses == false || t.IsAbstract == false)) - //the type must have this attribute - && t.GetCustomAttributes(attributeType, false).Any()) - .ToArray(); - } - catch (TypeLoadException ex) - { - LogHelper.Error(typeof(TypeFinder), string.Format("Could not query types on {0} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", a), ex); - continue; - } - - //add the types to our list to return - foreach (var t in attributedTypes) - { - foundAttributedTypes.Add(t); - } - - //get all attributes of the type being searched for - var allAttributeTypes = allTypes.Where(attributeType.IsAssignableFrom); - - //now we need to include types that may be inheriting from sub classes of the attribute type being searched for - //so we will search in assemblies that reference those types too. - foreach (var subTypesInAssembly in allAttributeTypes.GroupBy(x => x.Assembly)) - { - - //So that we are not scanning too much, we need to group the sub types: - // * if there is more than 1 sub type in the same assembly then we should only search on the 'lowest base' type. - // * We should also not search for sub types if the type is sealed since you cannot inherit from a sealed class - // * We should not search for sub types if the type is static since you cannot inherit from them. - var subTypeList = subTypesInAssembly - .Where(t => t.IsSealed == false && TypeHelper.IsStaticClass(t) == false) - .ToArray(); - - var baseClassAttempt = TypeHelper.GetLowestBaseType(subTypeList); - - //if there's a base class amongst the types then we'll only search for that type. - //otherwise we'll have to search for all of them. - var subTypesToSearch = new HashSet(); - if (baseClassAttempt.Success) - { - subTypesToSearch.Add(baseClassAttempt.Result); - } - else - { - foreach (var t in subTypeList) - { - subTypesToSearch.Add(t); - } - } - - foreach (var typeToSearch in subTypesToSearch) - { - //recursively find the types inheriting from this sub type in the other non-scanned assemblies. - var foundTypes = FindClassesWithAttribute(typeToSearch, otherAssemblies, onlyConcreteClasses); - - foreach (var f in foundTypes) - { - foundAttributedTypes.Add(f); - } - } - - } - } - - return foundAttributedTypes; - } - - - /// - /// Finds the classes with attribute. - /// - /// - /// The assemblies. - /// - public static IEnumerable FindClassesWithAttribute(IEnumerable assemblies) - where T : Attribute - { - return FindClassesWithAttribute(assemblies, true); - } - - /// - /// Finds the classes with attribute in filtered local assemblies - /// - /// - /// - public static IEnumerable FindClassesWithAttribute() - where T : Attribute - { - return FindClassesWithAttribute(GetAssembliesWithKnownExclusions()); - } - - - #region Private methods - - /// - /// Finds types that are assignable from the assignTypeFrom parameter and will scan for these types in the assembly - /// list passed in, however we will only scan assemblies that have a reference to the assignTypeFrom Type or any type - /// deriving from the base type. - /// - /// - /// - /// - /// An additional filter to apply for what types will actually be included in the return value - /// - private static IEnumerable GetClasses( - Type assignTypeFrom, - IEnumerable assemblies, - bool onlyConcreteClasses, - Func additionalFilter = null) - { - //the default filter will always return true. - if (additionalFilter == null) - { - additionalFilter = type => true; - } - - var foundAssignableTypes = new HashSet(); - - var assemblyList = assemblies.ToArray(); - - //find all assembly references that are referencing the current type's assembly since we - //should only be scanning those assemblies because any other assembly will definitely not - //contain sub type's of the one we're currently looking for - var referencedAssemblies = TypeHelper.GetReferencedAssemblies(assignTypeFrom, assemblyList); - - //get a list of non-referenced assemblies (we'll use this when we recurse below) - var otherAssemblies = assemblyList.Where(x => referencedAssemblies.Contains(x) == false).ToArray(); - - //loop through the referenced assemblies - foreach (var a in referencedAssemblies) - { - //get all types in the assembly that are sub types of the current type - var allSubTypes = GetTypesWithFormattedException(a) - .Where(assignTypeFrom.IsAssignableFrom) - .ToArray(); - - var filteredTypes = new Type[] { }; - try - { - //now filter the types based on the onlyConcreteClasses flag, not interfaces, not static classes - filteredTypes = allSubTypes - .Where(t => (TypeHelper.IsNonStaticClass(t) - //Do not include nested private classes - since we are in full trust now this will find those too! - && t.IsNestedPrivate == false - && (onlyConcreteClasses == false || t.IsAbstract == false) - //Do not include classes that are flagged to hide from the type finder - && t.GetCustomAttribute() == null - && additionalFilter(t))) - .ToArray(); - } - catch (TypeLoadException ex) - { - LogHelper.Error(typeof(TypeFinder), string.Format("Could not query types on {0} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", a), ex); - continue; - } - - //add the types to our list to return - foreach (var t in filteredTypes) - { - foundAssignableTypes.Add(t); - } - - //now we need to include types that may be inheriting from sub classes of the type being searched for - //so we will search in assemblies that reference those types too. - foreach (var subTypesInAssembly in allSubTypes.GroupBy(x => x.Assembly)) - { - - //So that we are not scanning too much, we need to group the sub types: - // * if there is more than 1 sub type in the same assembly then we should only search on the 'lowest base' type. - // * We should also not search for sub types if the type is sealed since you cannot inherit from a sealed class - // * We should not search for sub types if the type is static since you cannot inherit from them. - var subTypeList = subTypesInAssembly - .Where(t => t.IsSealed == false && TypeHelper.IsStaticClass(t) == false) - .ToArray(); - - var baseClassAttempt = TypeHelper.GetLowestBaseType(subTypeList); - - //if there's a base class amongst the types then we'll only search for that type. - //otherwise we'll have to search for all of them. - var subTypesToSearch = new HashSet(); - if (baseClassAttempt.Success) - { - subTypesToSearch.Add(baseClassAttempt.Result); - } - else - { - foreach (var t in subTypeList) - { - subTypesToSearch.Add(t); - } - } - - foreach (var typeToSearch in subTypesToSearch) - { - //recursively find the types inheriting from this sub type in the other non-scanned assemblies. - var foundTypes = GetClasses(typeToSearch, otherAssemblies, onlyConcreteClasses, additionalFilter); - - foreach (var f in foundTypes) - { - foundAssignableTypes.Add(f); - } - } - - } - - } - return foundAssignableTypes; - } - - internal static IEnumerable GetTypesWithFormattedException(Assembly a) - { - //if the assembly is dynamic, do not try to scan it - if (a.IsDynamic) - return Enumerable.Empty(); - - var getAll = a.GetCustomAttribute() == null; - - try - { - //we need to detect if an assembly is partially trusted, if so we cannot go interrogating all of it's types - //only its exported types, otherwise we'll get exceptions. - return getAll ? a.GetTypes() : a.GetExportedTypes(); - } - catch (TypeLoadException ex) // GetExportedTypes *can* throw TypeLoadException! - { - var sb = new StringBuilder(); - AppendCouldNotLoad(sb, a, getAll); - AppendLoaderException(sb, ex); - - // rethrow as ReflectionTypeLoadException (for consistency) with new message - throw new ReflectionTypeLoadException(new Type[0], new Exception[] { ex }, sb.ToString()); - } - catch (ReflectionTypeLoadException rex) // GetTypes throws ReflectionTypeLoadException - { - var sb = new StringBuilder(); - AppendCouldNotLoad(sb, a, getAll); - foreach (var loaderException in rex.LoaderExceptions.WhereNotNull()) - AppendLoaderException(sb, loaderException); - - // rethrow with new message - throw new ReflectionTypeLoadException(rex.Types, rex.LoaderExceptions, sb.ToString()); - } - } - - private static void AppendCouldNotLoad(StringBuilder sb, Assembly a, bool getAll) - { - sb.Append("Could not load "); - sb.Append(getAll ? "all" : "exported"); - sb.Append(" types from \""); - sb.Append(a.FullName); - sb.AppendLine("\" due to LoaderExceptions, skipping:"); - } - - private static void AppendLoaderException(StringBuilder sb, Exception loaderException) - { - sb.Append(". "); - sb.Append(loaderException.GetType().FullName); - - var tloadex = loaderException as TypeLoadException; - if (tloadex != null) - { - sb.Append(" on "); - sb.Append(tloadex.TypeName); - } - - sb.Append(": "); - sb.Append(loaderException.Message); - sb.AppendLine(); - } - - #endregion - - - public static Type GetTypeByName(string typeName) - { - var type = BuildManager.GetType(typeName, false); - if (type != null) return type; - - //TODO: This isn't very elegant, and will have issues since the AppDomain.CurrentDomain - // doesn't actualy load in all assemblies, only the types that have been referenced so far. - // However, in a web context, the BuildManager will have executed which will force all assemblies - // to be loaded so it's fine for now. - - //now try fall back procedures. - type = Type.GetType(typeName); - if (type != null) return type; - return AppDomain.CurrentDomain.GetAssemblies() - .Select(x => x.GetType(typeName)) - .FirstOrDefault(x => x != null); - } - - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security; +using System.Text; +using System.Web; +using System.Web.Compilation; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Plugins +{ + + /// + /// A utility class to find all classes of a certain type by reflection in the current bin folder + /// of the web application. + /// + public static class TypeFinder + { + private static volatile HashSet _localFilteredAssemblyCache = null; + private static readonly object LocalFilteredAssemblyCacheLocker = new object(); + + /// + /// lazily load a reference to all assemblies and only local assemblies. + /// This is a modified version of: http://www.dominicpettifer.co.uk/Blog/44/how-to-get-a-reference-to-all-assemblies-in-the--bin-folder + /// + /// + /// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been + /// loaded in the CLR, not all assemblies. + /// See these threads: + /// http://issues.umbraco.org/issue/U5-198 + /// http://stackoverflow.com/questions/3552223/asp-net-appdomain-currentdomain-getassemblies-assemblies-missing-after-app + /// http://stackoverflow.com/questions/2477787/difference-between-appdomain-getassemblies-and-buildmanager-getreferencedassembl + /// + internal static HashSet GetAllAssemblies() + { + return AllAssemblies.Value; + } + + //Lazy access to the all assemblies list + private static readonly Lazy> AllAssemblies = new Lazy>(() => + { + HashSet assemblies = null; + try + { + var isHosted = HttpContext.Current != null; + + try + { + if (isHosted) + { + assemblies = new HashSet(BuildManager.GetReferencedAssemblies().Cast()); + } + } + catch (InvalidOperationException e) + { + if (!(e.InnerException is SecurityException)) + throw; + } + + if (assemblies == null) + { + //NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have + // already been loaded in to the app domain, instead we will look directly into the bin folder and load each one. + var binFolder = IOHelper.GetRootDirectoryBinFolder(); + var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList(); + //var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; + //var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); + assemblies = new HashSet(); + foreach (var a in binAssemblyFiles) + { + try + { + var assName = AssemblyName.GetAssemblyName(a); + var ass = Assembly.Load(assName); + assemblies.Add(ass); + } + catch (Exception e) + { + if (e is SecurityException || e is BadImageFormatException) + { + //swallow these exceptions + } + else + { + throw; + } + } + } + } + + //if for some reason they are still no assemblies, then use the AppDomain to load in already loaded assemblies. + if (!assemblies.Any()) + { + foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) + { + assemblies.Add(a); + } + } + + //here we are trying to get the App_Code assembly + var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported + var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code"))); + //check if the folder exists and if there are any files in it with the supported file extensions + if (appCodeFolder.Exists && (fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any()))) + { + try + { + var appCodeAssembly = Assembly.Load("App_Code"); + if (!assemblies.Contains(appCodeAssembly)) // BuildManager will find App_Code already + assemblies.Add(appCodeAssembly); + } + catch (FileNotFoundException ex) + { + //this will occur if it cannot load the assembly + LogHelper.Error(typeof(TypeFinder), "Could not load assembly App_Code", ex); + } + } + } + catch (InvalidOperationException e) + { + if (!(e.InnerException is SecurityException)) + throw; + } + + return assemblies; + }); + + /// + /// Return a list of found local Assemblies excluding the known assemblies we don't want to scan + /// and exluding the ones passed in and excluding the exclusion list filter, the results of this are + /// cached for perforance reasons. + /// + /// + /// + internal static HashSet GetAssembliesWithKnownExclusions( + IEnumerable excludeFromResults = null) + { + if (_localFilteredAssemblyCache == null) + { + lock (LocalFilteredAssemblyCacheLocker) + { + //double check + if (_localFilteredAssemblyCache == null) + { + _localFilteredAssemblyCache = new HashSet(); + var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter); + foreach (var a in assemblies) + { + _localFilteredAssemblyCache.Add(a); + } + } + } + } + return _localFilteredAssemblyCache; + } + + /// + /// Return a distinct list of found local Assemblies and exluding the ones passed in and excluding the exclusion list filter + /// + /// + /// + /// + private static IEnumerable GetFilteredAssemblies( + IEnumerable excludeFromResults = null, + string[] exclusionFilter = null) + { + if (excludeFromResults == null) + excludeFromResults = new HashSet(); + if (exclusionFilter == null) + exclusionFilter = new string[] { }; + + return GetAllAssemblies() + .Where(x => !excludeFromResults.Contains(x) + && !x.GlobalAssemblyCache + && !exclusionFilter.Any(f => x.FullName.StartsWith(f))); + } + + /// + /// this is our assembly filter to filter out known types that def dont contain types we'd like to find or plugins + /// + /// + /// NOTE the comma vs period... comma delimits the name in an Assembly FullName property so if it ends with comma then its an exact name match + /// NOTE this means that "foo." will NOT exclude "foo.dll" but only "foo.*.dll" + /// + internal static readonly string[] KnownAssemblyExclusionFilter = new[] + { + "mscorlib,", + "System.", + "Antlr3.", + "Autofac.", + "Autofac,", + "Castle.", + "ClientDependency.", + "DataAnnotationsExtensions.", + "DataAnnotationsExtensions,", + "Dynamic,", + "HtmlDiff,", + "Iesi.Collections,", + "log4net,", + "Microsoft.", + "Newtonsoft.", + "NHibernate.", + "NHibernate,", + "NuGet.", + "RouteDebugger,", + "SqlCE4Umbraco,", + "umbraco.datalayer,", + "umbraco.interfaces,", + //"umbraco.providers,", + //"Umbraco.Web.UI,", + "umbraco.webservices", + "Lucene.", + "Examine,", + "Examine.", + "ServiceStack.", + "MySql.", + "HtmlAgilityPack.", + "TidyNet.", + "ICSharpCode.", + "CookComputing.", + "AutoMapper,", + "AutoMapper.", + "AzureDirectory,", + "itextsharp,", + "UrlRewritingNet.", + "HtmlAgilityPack,", + "MiniProfiler,", + "Moq,", + "nunit.framework,", + "TidyNet,", + "WebDriver," + }; + + /// + /// Finds any classes derived from the type T that contain the attribute TAttribute + /// + /// + /// + /// + public static IEnumerable FindClassesOfTypeWithAttribute() + where TAttribute : Attribute + { + return FindClassesOfTypeWithAttribute(GetAssembliesWithKnownExclusions(), true); + } + + /// + /// Finds any classes derived from the type T that contain the attribute TAttribute + /// + /// + /// + /// + /// + public static IEnumerable FindClassesOfTypeWithAttribute(IEnumerable assemblies) + where TAttribute : Attribute + { + return FindClassesOfTypeWithAttribute(assemblies, true); + } + + /// + /// Finds any classes derived from the type T that contain the attribute TAttribute + /// + /// + /// + /// + /// + /// + public static IEnumerable FindClassesOfTypeWithAttribute( + IEnumerable assemblies, + bool onlyConcreteClasses) + where TAttribute : Attribute + { + return FindClassesOfTypeWithAttribute(typeof(T), assemblies, onlyConcreteClasses); + } + + /// + /// Finds any classes derived from the assignTypeFrom Type that contain the attribute TAttribute + /// + /// + /// + /// + /// + /// + public static IEnumerable FindClassesOfTypeWithAttribute( + Type assignTypeFrom, + IEnumerable assemblies, + bool onlyConcreteClasses) + where TAttribute : Attribute + { + if (assemblies == null) throw new ArgumentNullException("assemblies"); + + return GetClasses(assignTypeFrom, assemblies, onlyConcreteClasses, + //the additional filter will ensure that any found types also have the attribute applied. + t => t.GetCustomAttributes(false).Any()); + } + + /// + /// Searches all filtered local assemblies specified for classes of the type passed in. + /// + /// + /// + public static IEnumerable FindClassesOfType() + { + return FindClassesOfType(GetAssembliesWithKnownExclusions(), true); + } + + /// + /// Returns all types found of in the assemblies specified of type T + /// + /// + /// + /// + /// + public static IEnumerable FindClassesOfType(IEnumerable assemblies, bool onlyConcreteClasses) + { + if (assemblies == null) throw new ArgumentNullException("assemblies"); + + return GetClasses(typeof(T), assemblies, onlyConcreteClasses); + } + + /// + /// Returns all types found of in the assemblies specified of type T + /// + /// + /// + /// + public static IEnumerable FindClassesOfType(IEnumerable assemblies) + { + return FindClassesOfType(assemblies, true); + } + + /// + /// Finds the classes with attribute. + /// + /// + /// The assemblies. + /// if set to true only concrete classes. + /// + public static IEnumerable FindClassesWithAttribute(IEnumerable assemblies, bool onlyConcreteClasses) + where T : Attribute + { + return FindClassesWithAttribute(typeof(T), assemblies, onlyConcreteClasses); + } + + /// + /// Finds any classes with the attribute. + /// + /// The attribute type + /// The assemblies. + /// if set to true only concrete classes. + /// + public static IEnumerable FindClassesWithAttribute( + Type attributeType, + IEnumerable assemblies, + bool onlyConcreteClasses) + { + if (assemblies == null) throw new ArgumentNullException("assemblies"); + + if (TypeHelper.IsTypeAssignableFrom(attributeType) == false) + throw new ArgumentException("The type specified: " + attributeType + " is not an Attribute type"); + + var foundAttributedTypes = new HashSet(); + + var assemblyList = assemblies.ToArray(); + + //find all assembly references that are referencing the attribute type's assembly since we + //should only be scanning those assemblies because any other assembly will definitely not + //contain a class that has this attribute. + var referencedAssemblies = TypeHelper.GetReferencedAssemblies(attributeType, assemblyList); + + //get a list of non-referenced assemblies (we'll use this when we recurse below) + var otherAssemblies = assemblyList.Where(x => referencedAssemblies.Contains(x) == false).ToArray(); + + //loop through the referenced assemblies + foreach (var a in referencedAssemblies) + { + //get all types in this assembly + var allTypes = GetTypesWithFormattedException(a) + .ToArray(); + + var attributedTypes = new Type[] { }; + try + { + //now filter the types based on the onlyConcreteClasses flag, not interfaces, not static classes but have + //the specified attribute + attributedTypes = allTypes + .Where(t => (TypeHelper.IsNonStaticClass(t) + && (onlyConcreteClasses == false || t.IsAbstract == false)) + //the type must have this attribute + && t.GetCustomAttributes(attributeType, false).Any()) + .ToArray(); + } + catch (TypeLoadException ex) + { + LogHelper.Error(typeof(TypeFinder), string.Format("Could not query types on {0} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", a), ex); + continue; + } + + //add the types to our list to return + foreach (var t in attributedTypes) + { + foundAttributedTypes.Add(t); + } + + //get all attributes of the type being searched for + var allAttributeTypes = allTypes.Where(attributeType.IsAssignableFrom); + + //now we need to include types that may be inheriting from sub classes of the attribute type being searched for + //so we will search in assemblies that reference those types too. + foreach (var subTypesInAssembly in allAttributeTypes.GroupBy(x => x.Assembly)) + { + + //So that we are not scanning too much, we need to group the sub types: + // * if there is more than 1 sub type in the same assembly then we should only search on the 'lowest base' type. + // * We should also not search for sub types if the type is sealed since you cannot inherit from a sealed class + // * We should not search for sub types if the type is static since you cannot inherit from them. + var subTypeList = subTypesInAssembly + .Where(t => t.IsSealed == false && TypeHelper.IsStaticClass(t) == false) + .ToArray(); + + var baseClassAttempt = TypeHelper.GetLowestBaseType(subTypeList); + + //if there's a base class amongst the types then we'll only search for that type. + //otherwise we'll have to search for all of them. + var subTypesToSearch = new HashSet(); + if (baseClassAttempt.Success) + { + subTypesToSearch.Add(baseClassAttempt.Result); + } + else + { + foreach (var t in subTypeList) + { + subTypesToSearch.Add(t); + } + } + + foreach (var typeToSearch in subTypesToSearch) + { + //recursively find the types inheriting from this sub type in the other non-scanned assemblies. + var foundTypes = FindClassesWithAttribute(typeToSearch, otherAssemblies, onlyConcreteClasses); + + foreach (var f in foundTypes) + { + foundAttributedTypes.Add(f); + } + } + + } + } + + return foundAttributedTypes; + } + + + /// + /// Finds the classes with attribute. + /// + /// + /// The assemblies. + /// + public static IEnumerable FindClassesWithAttribute(IEnumerable assemblies) + where T : Attribute + { + return FindClassesWithAttribute(assemblies, true); + } + + /// + /// Finds the classes with attribute in filtered local assemblies + /// + /// + /// + public static IEnumerable FindClassesWithAttribute() + where T : Attribute + { + return FindClassesWithAttribute(GetAssembliesWithKnownExclusions()); + } + + + #region Private methods + + /// + /// Finds types that are assignable from the assignTypeFrom parameter and will scan for these types in the assembly + /// list passed in, however we will only scan assemblies that have a reference to the assignTypeFrom Type or any type + /// deriving from the base type. + /// + /// + /// + /// + /// An additional filter to apply for what types will actually be included in the return value + /// + private static IEnumerable GetClasses( + Type assignTypeFrom, + IEnumerable assemblies, + bool onlyConcreteClasses, + Func additionalFilter = null) + { + //the default filter will always return true. + if (additionalFilter == null) + { + additionalFilter = type => true; + } + + var foundAssignableTypes = new HashSet(); + + var assemblyList = assemblies.ToArray(); + + //find all assembly references that are referencing the current type's assembly since we + //should only be scanning those assemblies because any other assembly will definitely not + //contain sub type's of the one we're currently looking for + var referencedAssemblies = TypeHelper.GetReferencedAssemblies(assignTypeFrom, assemblyList); + + //get a list of non-referenced assemblies (we'll use this when we recurse below) + var otherAssemblies = assemblyList.Where(x => referencedAssemblies.Contains(x) == false).ToArray(); + + //loop through the referenced assemblies + foreach (var a in referencedAssemblies) + { + //get all types in the assembly that are sub types of the current type + var allSubTypes = GetTypesWithFormattedException(a) + .Where(assignTypeFrom.IsAssignableFrom) + .ToArray(); + + var filteredTypes = new Type[] { }; + try + { + //now filter the types based on the onlyConcreteClasses flag, not interfaces, not static classes + filteredTypes = allSubTypes + .Where(t => (TypeHelper.IsNonStaticClass(t) + //Do not include nested private classes - since we are in full trust now this will find those too! + && t.IsNestedPrivate == false + && (onlyConcreteClasses == false || t.IsAbstract == false) + //Do not include classes that are flagged to hide from the type finder + && t.GetCustomAttribute() == null + && additionalFilter(t))) + .ToArray(); + } + catch (TypeLoadException ex) + { + LogHelper.Error(typeof(TypeFinder), string.Format("Could not query types on {0} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", a), ex); + continue; + } + + //add the types to our list to return + foreach (var t in filteredTypes) + { + foundAssignableTypes.Add(t); + } + + //now we need to include types that may be inheriting from sub classes of the type being searched for + //so we will search in assemblies that reference those types too. + foreach (var subTypesInAssembly in allSubTypes.GroupBy(x => x.Assembly)) + { + + //So that we are not scanning too much, we need to group the sub types: + // * if there is more than 1 sub type in the same assembly then we should only search on the 'lowest base' type. + // * We should also not search for sub types if the type is sealed since you cannot inherit from a sealed class + // * We should not search for sub types if the type is static since you cannot inherit from them. + var subTypeList = subTypesInAssembly + .Where(t => t.IsSealed == false && TypeHelper.IsStaticClass(t) == false) + .ToArray(); + + var baseClassAttempt = TypeHelper.GetLowestBaseType(subTypeList); + + //if there's a base class amongst the types then we'll only search for that type. + //otherwise we'll have to search for all of them. + var subTypesToSearch = new HashSet(); + if (baseClassAttempt.Success) + { + subTypesToSearch.Add(baseClassAttempt.Result); + } + else + { + foreach (var t in subTypeList) + { + subTypesToSearch.Add(t); + } + } + + foreach (var typeToSearch in subTypesToSearch) + { + //recursively find the types inheriting from this sub type in the other non-scanned assemblies. + var foundTypes = GetClasses(typeToSearch, otherAssemblies, onlyConcreteClasses, additionalFilter); + + foreach (var f in foundTypes) + { + foundAssignableTypes.Add(f); + } + } + + } + + } + return foundAssignableTypes; + } + + internal static IEnumerable GetTypesWithFormattedException(Assembly a) + { + //if the assembly is dynamic, do not try to scan it + if (a.IsDynamic) + return Enumerable.Empty(); + + var getAll = a.GetCustomAttribute() == null; + + try + { + //we need to detect if an assembly is partially trusted, if so we cannot go interrogating all of it's types + //only its exported types, otherwise we'll get exceptions. + return getAll ? a.GetTypes() : a.GetExportedTypes(); + } + catch (TypeLoadException ex) // GetExportedTypes *can* throw TypeLoadException! + { + var sb = new StringBuilder(); + AppendCouldNotLoad(sb, a, getAll); + AppendLoaderException(sb, ex); + + // rethrow as ReflectionTypeLoadException (for consistency) with new message + throw new ReflectionTypeLoadException(new Type[0], new Exception[] { ex }, sb.ToString()); + } + catch (ReflectionTypeLoadException rex) // GetTypes throws ReflectionTypeLoadException + { + var sb = new StringBuilder(); + AppendCouldNotLoad(sb, a, getAll); + foreach (var loaderException in rex.LoaderExceptions.WhereNotNull()) + AppendLoaderException(sb, loaderException); + + // rethrow with new message + throw new ReflectionTypeLoadException(rex.Types, rex.LoaderExceptions, sb.ToString()); + } + } + + private static void AppendCouldNotLoad(StringBuilder sb, Assembly a, bool getAll) + { + sb.Append("Could not load "); + sb.Append(getAll ? "all" : "exported"); + sb.Append(" types from \""); + sb.Append(a.FullName); + sb.AppendLine("\" due to LoaderExceptions, skipping:"); + } + + private static void AppendLoaderException(StringBuilder sb, Exception loaderException) + { + sb.Append(". "); + sb.Append(loaderException.GetType().FullName); + + var tloadex = loaderException as TypeLoadException; + if (tloadex != null) + { + sb.Append(" on "); + sb.Append(tloadex.TypeName); + } + + sb.Append(": "); + sb.Append(loaderException.Message); + sb.AppendLine(); + } + + #endregion + + + public static Type GetTypeByName(string typeName) + { + var type = BuildManager.GetType(typeName, false); + if (type != null) return type; + + //TODO: This isn't very elegant, and will have issues since the AppDomain.CurrentDomain + // doesn't actualy load in all assemblies, only the types that have been referenced so far. + // However, in a web context, the BuildManager will have executed which will force all assemblies + // to be loaded so it's fine for now. + + //now try fall back procedures. + type = Type.GetType(typeName); + if (type != null) return type; + return AppDomain.CurrentDomain.GetAssemblies() + .Select(x => x.GetType(typeName)) + .FirstOrDefault(x => x != null); + } + + } +} diff --git a/src/Umbraco.Core/TypeHelper.cs b/src/Umbraco.Core/Plugins/TypeHelper.cs similarity index 97% rename from src/Umbraco.Core/TypeHelper.cs rename to src/Umbraco.Core/Plugins/TypeHelper.cs index a42449ce94..e5407de195 100644 --- a/src/Umbraco.Core/TypeHelper.cs +++ b/src/Umbraco.Core/Plugins/TypeHelper.cs @@ -1,311 +1,310 @@ -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace Umbraco.Core -{ - /// - /// A utility class for type checking, this provides internal caching so that calls to these methods will be faster - /// than doing a manual type check in c# - /// - internal static class TypeHelper - { - - private static readonly ConcurrentDictionary GetFieldsCache = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary, PropertyInfo[]> GetPropertiesCache = new ConcurrentDictionary, PropertyInfo[]>(); - - /// - /// Find all assembly references that are referencing the assignTypeFrom Type's assembly found in the assemblyList - /// - /// - /// - /// - /// - /// If the assembly of the assignTypeFrom Type is in the App_Code assembly, then we return nothing since things cannot - /// reference that assembly, same with the global.asax assembly. - /// - public static Assembly[] GetReferencedAssemblies(Type assignTypeFrom, IEnumerable assemblies) - { - //check if it is the app_code assembly. - //check if it is App_global.asax assembly - if (assignTypeFrom.Assembly.IsAppCodeAssembly() || assignTypeFrom.Assembly.IsGlobalAsaxAssembly()) - { - return Enumerable.Empty().ToArray(); - } - - //find all assembly references that are referencing the current type's assembly since we - //should only be scanning those assemblies because any other assembly will definitely not - //contain sub type's of the one we're currently looking for - return assemblies - .Where(assembly => - assembly == assignTypeFrom.Assembly - || HasReferenceToAssemblyWithName(assembly, assignTypeFrom.Assembly.GetName().Name)) - .ToArray(); - } - - /// - /// checks if the assembly has a reference with the same name as the expected assembly name. - /// - /// - /// - /// - private static bool HasReferenceToAssemblyWithName(Assembly assembly, string expectedAssemblyName) - { - return assembly - .GetReferencedAssemblies() - .Select(a => a.Name) - .Contains(expectedAssemblyName, StringComparer.Ordinal); - } - - /// - /// Returns true if the type is a class and is not static - /// - /// - /// - public static bool IsNonStaticClass(Type t) - { - return t.IsClass && IsStaticClass(t) == false; - } - - /// - /// Returns true if the type is a static class - /// - /// - /// - /// - /// In IL a static class is abstract and sealed - /// see: http://stackoverflow.com/questions/1175888/determine-if-a-type-is-static - /// - public static bool IsStaticClass(Type type) - { - return type.IsAbstract && type.IsSealed; - } - - /// - /// Finds a lowest base class amongst a collection of types - /// - /// - /// - /// - /// The term 'lowest' refers to the most base class of the type collection. - /// If a base type is not found amongst the type collection then an invalid attempt is returned. - /// - public static Attempt GetLowestBaseType(params Type[] types) - { - if (types.Length == 0) - { - return Attempt.Fail(); - } - if (types.Length == 1) - { - return Attempt.Succeed(types[0]); - } - - foreach (var curr in types) - { - var others = types.Except(new[] {curr}); - - //is the curr type a common denominator for all others ? - var isBase = others.All(curr.IsAssignableFrom); - - //if this type is the base for all others - if (isBase) - { - return Attempt.Succeed(curr); - } - } - - return Attempt.Fail(); - } - - /// - /// Determines whether the type is assignable from the specified implementation, - /// and caches the result across the application using a . - /// - /// The type of the contract. - /// The implementation. - /// - /// true if [is type assignable from] [the specified contract]; otherwise, false. - /// - public static bool IsTypeAssignableFrom(Type contract, Type implementation) - { - return contract.IsAssignableFrom(implementation); - } - - /// - /// Determines whether the type is assignable from the specified implementation , - /// and caches the result across the application using a . - /// - /// The type of the contract. - /// The implementation. - public static bool IsTypeAssignableFrom(Type implementation) - { - return IsTypeAssignableFrom(typeof(TContract), implementation); - } - - /// - /// Determines whether the object instance is assignable from the specified implementation , - /// and caches the result across the application using a . - /// - /// The type of the contract. - /// The implementation. - public static bool IsTypeAssignableFrom(object implementation) - { - if (implementation == null) throw new ArgumentNullException("implementation"); - return IsTypeAssignableFrom(implementation.GetType()); - } - - - /// - /// Returns (and caches) a PropertyInfo from a type - /// - /// - /// - /// - /// - /// - /// - /// - public static PropertyInfo GetProperty(Type type, string name, - bool mustRead = true, - bool mustWrite = true, - bool includeIndexed = false, - bool caseSensitive = true) - { - return CachedDiscoverableProperties(type, mustRead, mustWrite, includeIndexed) - .FirstOrDefault(x => - { - if (caseSensitive) - return x.Name == name; - return x.Name.InvariantEquals(name); - }); - } - - /// - /// Gets (and caches) discoverable in the current for a given . - /// - /// The source. - /// true if the properties discovered are readable - /// true if the properties discovered are writable - /// true if the properties discovered are indexable - /// - public static PropertyInfo[] CachedDiscoverableProperties(Type type, bool mustRead = true, bool mustWrite = true, bool includeIndexed = false) - { - return GetPropertiesCache.GetOrAdd( - new Tuple(type, mustRead, mustWrite, includeIndexed), - x => type - .GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(y => (!mustRead || y.CanRead) - && (!mustWrite || y.CanWrite) - && (includeIndexed || !y.GetIndexParameters().Any())) - .ToArray()); - } - - - #region Match Type - - //TODO: Need to determine if these methods should replace/combine/merge etc with IsTypeAssignableFrom, IsAssignableFromGeneric - - // readings: - // http://stackoverflow.com/questions/2033912/c-sharp-variance-problem-assigning-listderived-as-listbase - // http://stackoverflow.com/questions/2208043/generic-variance-in-c-sharp-4-0 - // http://stackoverflow.com/questions/8401738/c-sharp-casting-generics-covariance-and-contravariance - // http://stackoverflow.com/questions/1827425/how-to-check-programatically-if-a-type-is-a-struct-or-a-class - // http://stackoverflow.com/questions/74616/how-to-detect-if-type-is-another-generic-type/1075059#1075059 - - private static bool MatchGeneric(Type implementation, Type contract, IDictionary bindings) - { - // trying to match eg List with List - // or List>> with List>> - // classes are NOT invariant so List does not match List - - if (implementation.IsGenericType == false) return false; - - // must have the same generic type definition - var implDef = implementation.GetGenericTypeDefinition(); - var contDef = contract.GetGenericTypeDefinition(); - if (implDef != contDef) return false; - - // must have the same number of generic arguments - var implArgs = implementation.GetGenericArguments(); - var contArgs = contract.GetGenericArguments(); - if (implArgs.Length != contArgs.Length) return false; - - // generic arguments must match - // in insta we should have actual types (eg int, string...) - // in typea we can have generic parameters (eg ) - for (var i = 0; i < implArgs.Length; i++) - { - const bool variance = false; // classes are NOT invariant - if (MatchType(implArgs[i], contArgs[i], bindings, variance) == false) - return false; - } - - return true; - } - - public static bool MatchType(Type implementation, Type contract) - { - return MatchType(implementation, contract, new Dictionary()); - } - - internal static bool MatchType(Type implementation, Type contract, IDictionary bindings, bool variance = true) - { - if (contract.IsGenericType) - { - // eg type is List or List - // if we have variance then List can match IList - // if we don't have variance it can't - must have exact type - - // try to match implementation against contract - if (MatchGeneric(implementation, contract, bindings)) return true; - - // if no variance, fail - if (variance == false) return false; - - // try to match an ancestor of implementation against contract - var t = implementation.BaseType; - while (t != null) - { - if (MatchGeneric(t, contract, bindings)) return true; - t = t.BaseType; - } - - // try to match an interface of implementation against contract - return implementation.GetInterfaces().Any(i => MatchGeneric(i, contract, bindings)); - } - - if (contract.IsGenericParameter) - { - // eg - - if (bindings.ContainsKey(contract.Name)) - { - // already bound: ensure it's compatible - return bindings[contract.Name] == implementation; - } - - // not already bound: bind - bindings[contract.Name] = implementation; - return true; - } - - // not a generic type, not a generic parameter - // so normal class or interface - // fixme structs? enums? array types? - // about primitive types, value types, etc: - // http://stackoverflow.com/questions/1827425/how-to-check-programatically-if-a-type-is-a-struct-or-a-class - - if (implementation == contract) return true; - if (contract.IsClass && implementation.IsClass && implementation.IsSubclassOf(contract)) return true; - if (contract.IsInterface && implementation.GetInterfaces().Contains(contract)) return true; - - return false; - } - - #endregion - } +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Umbraco.Core.Plugins +{ + /// + /// A utility class for type checking, this provides internal caching so that calls to these methods will be faster + /// than doing a manual type check in c# + /// + internal static class TypeHelper + { + + private static readonly ConcurrentDictionary GetFieldsCache = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary, PropertyInfo[]> GetPropertiesCache = new ConcurrentDictionary, PropertyInfo[]>(); + + /// + /// Find all assembly references that are referencing the assignTypeFrom Type's assembly found in the assemblyList + /// + /// + /// + /// + /// + /// If the assembly of the assignTypeFrom Type is in the App_Code assembly, then we return nothing since things cannot + /// reference that assembly, same with the global.asax assembly. + /// + public static Assembly[] GetReferencedAssemblies(Type assignTypeFrom, IEnumerable assemblies) + { + //check if it is the app_code assembly. + //check if it is App_global.asax assembly + if (assignTypeFrom.Assembly.IsAppCodeAssembly() || assignTypeFrom.Assembly.IsGlobalAsaxAssembly()) + { + return Enumerable.Empty().ToArray(); + } + + //find all assembly references that are referencing the current type's assembly since we + //should only be scanning those assemblies because any other assembly will definitely not + //contain sub type's of the one we're currently looking for + return assemblies + .Where(assembly => + assembly == assignTypeFrom.Assembly + || HasReferenceToAssemblyWithName(assembly, assignTypeFrom.Assembly.GetName().Name)) + .ToArray(); + } + + /// + /// checks if the assembly has a reference with the same name as the expected assembly name. + /// + /// + /// + /// + private static bool HasReferenceToAssemblyWithName(Assembly assembly, string expectedAssemblyName) + { + return assembly + .GetReferencedAssemblies() + .Select(a => a.Name) + .Contains(expectedAssemblyName, StringComparer.Ordinal); + } + + /// + /// Returns true if the type is a class and is not static + /// + /// + /// + public static bool IsNonStaticClass(Type t) + { + return t.IsClass && IsStaticClass(t) == false; + } + + /// + /// Returns true if the type is a static class + /// + /// + /// + /// + /// In IL a static class is abstract and sealed + /// see: http://stackoverflow.com/questions/1175888/determine-if-a-type-is-static + /// + public static bool IsStaticClass(Type type) + { + return type.IsAbstract && type.IsSealed; + } + + /// + /// Finds a lowest base class amongst a collection of types + /// + /// + /// + /// + /// The term 'lowest' refers to the most base class of the type collection. + /// If a base type is not found amongst the type collection then an invalid attempt is returned. + /// + public static Attempt GetLowestBaseType(params Type[] types) + { + if (types.Length == 0) + { + return Attempt.Fail(); + } + if (types.Length == 1) + { + return Attempt.Succeed(types[0]); + } + + foreach (var curr in types) + { + var others = types.Except(new[] {curr}); + + //is the curr type a common denominator for all others ? + var isBase = others.All(curr.IsAssignableFrom); + + //if this type is the base for all others + if (isBase) + { + return Attempt.Succeed(curr); + } + } + + return Attempt.Fail(); + } + + /// + /// Determines whether the type is assignable from the specified implementation, + /// and caches the result across the application using a . + /// + /// The type of the contract. + /// The implementation. + /// + /// true if [is type assignable from] [the specified contract]; otherwise, false. + /// + public static bool IsTypeAssignableFrom(Type contract, Type implementation) + { + return contract.IsAssignableFrom(implementation); + } + + /// + /// Determines whether the type is assignable from the specified implementation , + /// and caches the result across the application using a . + /// + /// The type of the contract. + /// The implementation. + public static bool IsTypeAssignableFrom(Type implementation) + { + return IsTypeAssignableFrom(typeof(TContract), implementation); + } + + /// + /// Determines whether the object instance is assignable from the specified implementation , + /// and caches the result across the application using a . + /// + /// The type of the contract. + /// The implementation. + public static bool IsTypeAssignableFrom(object implementation) + { + if (implementation == null) throw new ArgumentNullException("implementation"); + return IsTypeAssignableFrom(implementation.GetType()); + } + + + /// + /// Returns (and caches) a PropertyInfo from a type + /// + /// + /// + /// + /// + /// + /// + /// + public static PropertyInfo GetProperty(Type type, string name, + bool mustRead = true, + bool mustWrite = true, + bool includeIndexed = false, + bool caseSensitive = true) + { + return CachedDiscoverableProperties(type, mustRead, mustWrite, includeIndexed) + .FirstOrDefault(x => + { + if (caseSensitive) + return x.Name == name; + return x.Name.InvariantEquals(name); + }); + } + + /// + /// Gets (and caches) discoverable in the current for a given . + /// + /// The source. + /// true if the properties discovered are readable + /// true if the properties discovered are writable + /// true if the properties discovered are indexable + /// + public static PropertyInfo[] CachedDiscoverableProperties(Type type, bool mustRead = true, bool mustWrite = true, bool includeIndexed = false) + { + return GetPropertiesCache.GetOrAdd( + new Tuple(type, mustRead, mustWrite, includeIndexed), + x => type + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(y => (!mustRead || y.CanRead) + && (!mustWrite || y.CanWrite) + && (includeIndexed || !y.GetIndexParameters().Any())) + .ToArray()); + } + + + #region Match Type + + //TODO: Need to determine if these methods should replace/combine/merge etc with IsTypeAssignableFrom, IsAssignableFromGeneric + + // readings: + // http://stackoverflow.com/questions/2033912/c-sharp-variance-problem-assigning-listderived-as-listbase + // http://stackoverflow.com/questions/2208043/generic-variance-in-c-sharp-4-0 + // http://stackoverflow.com/questions/8401738/c-sharp-casting-generics-covariance-and-contravariance + // http://stackoverflow.com/questions/1827425/how-to-check-programatically-if-a-type-is-a-struct-or-a-class + // http://stackoverflow.com/questions/74616/how-to-detect-if-type-is-another-generic-type/1075059#1075059 + + private static bool MatchGeneric(Type implementation, Type contract, IDictionary bindings) + { + // trying to match eg List with List + // or List>> with List>> + // classes are NOT invariant so List does not match List + + if (implementation.IsGenericType == false) return false; + + // must have the same generic type definition + var implDef = implementation.GetGenericTypeDefinition(); + var contDef = contract.GetGenericTypeDefinition(); + if (implDef != contDef) return false; + + // must have the same number of generic arguments + var implArgs = implementation.GetGenericArguments(); + var contArgs = contract.GetGenericArguments(); + if (implArgs.Length != contArgs.Length) return false; + + // generic arguments must match + // in insta we should have actual types (eg int, string...) + // in typea we can have generic parameters (eg ) + for (var i = 0; i < implArgs.Length; i++) + { + const bool variance = false; // classes are NOT invariant + if (MatchType(implArgs[i], contArgs[i], bindings, variance) == false) + return false; + } + + return true; + } + + public static bool MatchType(Type implementation, Type contract) + { + return MatchType(implementation, contract, new Dictionary()); + } + + internal static bool MatchType(Type implementation, Type contract, IDictionary bindings, bool variance = true) + { + if (contract.IsGenericType) + { + // eg type is List or List + // if we have variance then List can match IList + // if we don't have variance it can't - must have exact type + + // try to match implementation against contract + if (MatchGeneric(implementation, contract, bindings)) return true; + + // if no variance, fail + if (variance == false) return false; + + // try to match an ancestor of implementation against contract + var t = implementation.BaseType; + while (t != null) + { + if (MatchGeneric(t, contract, bindings)) return true; + t = t.BaseType; + } + + // try to match an interface of implementation against contract + return implementation.GetInterfaces().Any(i => MatchGeneric(i, contract, bindings)); + } + + if (contract.IsGenericParameter) + { + // eg + + if (bindings.ContainsKey(contract.Name)) + { + // already bound: ensure it's compatible + return bindings[contract.Name] == implementation; + } + + // not already bound: bind + bindings[contract.Name] = implementation; + return true; + } + + // not a generic type, not a generic parameter + // so normal class or interface + // fixme structs? enums? array types? + // about primitive types, value types, etc: + // http://stackoverflow.com/questions/1827425/how-to-check-programatically-if-a-type-is-a-struct-or-a-class + + if (implementation == contract) return true; + if (contract.IsClass && implementation.IsClass && implementation.IsSubclassOf(contract)) return true; + if (contract.IsInterface && implementation.GetInterfaces().Contains(contract)) return true; + + return false; + } + + #endregion + } } \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs b/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs index d773eed2e9..939c57d066 100644 --- a/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs @@ -6,6 +6,7 @@ using Newtonsoft.Json; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Plugins; namespace Umbraco.Core.PropertyEditors { diff --git a/src/Umbraco.Core/ServiceProviderExtensions.cs b/src/Umbraco.Core/ServiceProviderExtensions.cs index 335396ede6..8cbe6d01da 100644 --- a/src/Umbraco.Core/ServiceProviderExtensions.cs +++ b/src/Umbraco.Core/ServiceProviderExtensions.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core.Logging; +using Umbraco.Core.Plugins; namespace Umbraco.Core { diff --git a/src/Umbraco.Core/TypeExtensions.cs b/src/Umbraco.Core/TypeExtensions.cs index 3208c1fbe8..a8d4495879 100644 --- a/src/Umbraco.Core/TypeExtensions.cs +++ b/src/Umbraco.Core/TypeExtensions.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; +using Umbraco.Core.Plugins; using Umbraco.Core.Strings; namespace Umbraco.Core diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 823a77b2e5..e31ae0bed8 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -148,7 +148,7 @@ - + @@ -185,7 +185,7 @@ - + @@ -335,7 +335,7 @@ True Files.resx - + @@ -577,9 +577,8 @@ - - + @@ -759,7 +758,7 @@ - + @@ -1070,11 +1069,11 @@ - - - - - + + + + + @@ -1180,7 +1179,7 @@ - + @@ -1320,8 +1319,8 @@ - - + + @@ -1357,7 +1356,7 @@ - + @@ -1375,7 +1374,7 @@ - + @@ -1402,6 +1401,7 @@ Files.Designer.cs + ", "", "]]>"); - } - - /// - /// Determines whether the specified string appears to be XML. - /// - /// The XML string. - /// - /// true if the specified string appears to be XML; otherwise, false. - /// - public static bool CouldItBeXml(string xml) - { - if (string.IsNullOrEmpty(xml)) return false; - - xml = xml.Trim(); - return xml.StartsWith("<") && xml.EndsWith(">") && xml.Contains('/'); - } - - /// - /// Splits the specified delimited string into an XML document. - /// - /// The data. - /// The separator. - /// Name of the root. - /// Name of the element. - /// Returns an System.Xml.XmlDocument representation of the delimited string data. - public static XmlDocument Split(string data, string[] separator, string rootName, string elementName) - { - return Split(new XmlDocument(), data, separator, rootName, elementName); - } - - /// - /// Splits the specified delimited string into an XML document. - /// - /// The XML document. - /// The delimited string data. - /// The separator. - /// Name of the root node. - /// Name of the element node. - /// Returns an System.Xml.XmlDocument representation of the delimited string data. - public static XmlDocument Split(XmlDocument xml, string data, string[] separator, string rootName, string elementName) - { - // load new XML document. - xml.LoadXml(string.Concat("<", rootName, "/>")); - - // get the data-value, check it isn't empty. - if (!string.IsNullOrEmpty(data)) - { - // explode the values into an array - var values = data.Split(separator, StringSplitOptions.None); - - // loop through the array items. - foreach (string value in values) - { - // add each value to the XML document. - var xn = XmlHelper.AddTextNode(xml, elementName, value); - xml.DocumentElement.AppendChild(xn); - } - } - - // return the XML node. - return xml; - } - - /// - /// Return a dictionary of attributes found for a string based tag - /// - /// - /// - public static Dictionary GetAttributesFromElement(string tag) - { - var m = - Regex.Matches(tag, "(?\\S*)=\"(?[^\"]*)\"", - RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - // fix for issue 14862: return lowercase attributes for case insensitive matching - var d = m.Cast().ToDictionary(attributeSet => attributeSet.Groups["attributeName"].Value.ToString().ToLower(), attributeSet => attributeSet.Groups["attributeValue"].Value.ToString()); - return d; - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml; +using System.Xml.Linq; +using System.Xml.XPath; +using Umbraco.Core.IO; + +namespace Umbraco.Core.Xml +{ + /// + /// The XmlHelper class contains general helper methods for working with xml in umbraco. + /// + public class XmlHelper + { + /// + /// Gets a value indicating whether a specified string contains only xml whitespace characters. + /// + /// The string. + /// true if the string contains only xml whitespace characters. + /// As per XML 1.1 specs, space, \t, \r and \n. + public static bool IsXmlWhitespace(string s) + { + // as per xml 1.1 specs - anything else is significant whitespace + s = s.Trim(' ', '\t', '\r', '\n'); + return s.Length == 0; + } + + /// + /// Creates a new XPathDocument from an xml string. + /// + /// The xml string. + /// An XPathDocument created from the xml string. + public static XPathDocument CreateXPathDocument(string xml) + { + return new XPathDocument(new XmlTextReader(new StringReader(xml))); + } + + /// + /// Tries to create a new XPathDocument from an xml string. + /// + /// The xml string. + /// The XPath document. + /// A value indicating whether it has been possible to create the document. + public static bool TryCreateXPathDocument(string xml, out XPathDocument doc) + { + try + { + doc = CreateXPathDocument(xml); + return true; + } + catch (Exception) + { + doc = null; + return false; + } + } + + /// + /// Tries to create a new XPathDocument from a property value. + /// + /// The value of the property. + /// The XPath document. + /// A value indicating whether it has been possible to create the document. + /// The value can be anything... Performance-wise, this is bad. + public static bool TryCreateXPathDocumentFromPropertyValue(object value, out XPathDocument doc) + { + // DynamicNode.ConvertPropertyValueByDataType first cleans the value by calling + // XmlHelper.StripDashesInElementOrAttributeName - this is because the XML is + // to be returned as a DynamicXml and element names such as "value-item" are + // invalid and must be converted to "valueitem". But we don't have that sort of + // problem here - and we don't need to bother with dashes nor dots, etc. + + doc = null; + var xml = value as string; + if (xml == null) return false; // no a string + if (CouldItBeXml(xml) == false) return false; // string does not look like it's xml + if (IsXmlWhitespace(xml)) return false; // string is whitespace, xml-wise + if (TryCreateXPathDocument(xml, out doc) == false) return false; // string can't be parsed into xml + + var nav = doc.CreateNavigator(); + if (nav.MoveToFirstChild()) + { + //SD: This used to do this but the razor macros and the entire razor macros section is gone, it was all legacy, it seems this method isn't even + // used apart from for tests so don't think this matters. In any case, we no longer check for this! + + //var name = nav.LocalName; // must not match an excluded tag + //if (UmbracoConfig.For.UmbracoSettings().Scripting.NotDynamicXmlDocumentElements.All(x => x.Element.InvariantEquals(name) == false)) return true; + + return true; + } + + doc = null; + return false; + } + + /// + /// Tries to create a new XElement from a property value. + /// + /// The value of the property. + /// The Xml element. + /// A value indicating whether it has been possible to create the element. + /// The value can be anything... Performance-wise, this is bad. + public static bool TryCreateXElementFromPropertyValue(object value, out XElement elt) + { + // see note above in TryCreateXPathDocumentFromPropertyValue... + + elt = null; + var xml = value as string; + if (xml == null) return false; // not a string + if (CouldItBeXml(xml) == false) return false; // string does not look like it's xml + if (IsXmlWhitespace(xml)) return false; // string is whitespace, xml-wise + + try + { + elt = XElement.Parse(xml, LoadOptions.None); + } + catch + { + elt = null; + return false; // string can't be parsed into xml + } + + //SD: This used to do this but the razor macros and the entire razor macros section is gone, it was all legacy, it seems this method isn't even + // used apart from for tests so don't think this matters. In any case, we no longer check for this! + + //var name = elt.Name.LocalName; // must not match an excluded tag + //if (UmbracoConfig.For.UmbracoSettings().Scripting.NotDynamicXmlDocumentElements.All(x => x.Element.InvariantEquals(name) == false)) + // return true; + //elt = null; + //return false; + + return true; + } + + /// + /// Sorts the children of a parentNode. + /// + /// The parent node. + /// An XPath expression to select children of to sort. + /// A function returning the value to order the nodes by. + internal static void SortNodes( + XmlNode parentNode, + string childNodesXPath, + Func orderBy) + { + var sortedChildNodes = parentNode.SelectNodes(childNodesXPath).Cast() + .OrderBy(orderBy) + .ToArray(); + + // append child nodes to last position, in sort-order + // so all child nodes will go after the property nodes + foreach (var node in sortedChildNodes) + parentNode.AppendChild(node); // moves the node to the last position + } + + /// + /// Sorts the children of a parentNode if needed. + /// + /// The parent node. + /// An XPath expression to select children of to sort. + /// A function returning the value to order the nodes by. + /// A value indicating whether sorting was needed. + /// same as SortNodes but will do nothing if nodes are already sorted - should improve performances. + internal static bool SortNodesIfNeeded( + XmlNode parentNode, + string childNodesXPath, + Func orderBy) + { + // ensure orderBy runs only once per node + // checks whether nodes are already ordered + // and actually sorts only if needed + + var childNodesAndOrder = parentNode.SelectNodes(childNodesXPath).Cast() + .Select(x => Tuple.Create(x, orderBy(x))).ToArray(); + + var a = 0; + foreach (var x in childNodesAndOrder) + { + if (a > x.Item2) + { + a = -1; + break; + } + a = x.Item2; + } + + if (a >= 0) + return false; + + // append child nodes to last position, in sort-order + // so all child nodes will go after the property nodes + foreach (var x in childNodesAndOrder.OrderBy(x => x.Item2)) + parentNode.AppendChild(x.Item1); // moves the node to the last position + + return true; + } + + /// + /// Sorts a single child node of a parentNode. + /// + /// The parent node. + /// An XPath expression to select children of to sort. + /// The child node to sort. + /// A function returning the value to order the nodes by. + /// A value indicating whether sorting was needed. + /// Assuming all nodes but are sorted, this will move the node to + /// the right position without moving all the nodes (as SortNodes would do) - should improve perfs. + internal static bool SortNode( + XmlNode parentNode, + string childNodesXPath, + XmlNode node, + Func orderBy) + { + var nodeSortOrder = orderBy(node); + var childNodesAndOrder = parentNode.SelectNodes(childNodesXPath).Cast() + .Select(x => Tuple.Create(x, orderBy(x))).ToArray(); + + // find the first node with a sortOrder > node.sortOrder + var i = 0; + while (i < childNodesAndOrder.Length && childNodesAndOrder[i].Item2 <= nodeSortOrder) + i++; + + // if one was found + if (i < childNodesAndOrder.Length) + { + // and node is just before, we're done already + // else we need to move it right before the node that was found + if (i > 0 && childNodesAndOrder[i - 1].Item1 != node) + { + parentNode.InsertBefore(node, childNodesAndOrder[i].Item1); + return true; + } + } + else + { + // and node is the last one, we're done already + // else we need to append it as the last one + if (i > 0 && childNodesAndOrder[i - 1].Item1 != node) + { + parentNode.AppendChild(node); + return true; + } + } + return false; + } + + // used by DynamicNode only, see note in TryCreateXPathDocumentFromPropertyValue + public static string StripDashesInElementOrAttributeNames(string xml) + { + using (var outputms = new MemoryStream()) + { + using (TextWriter outputtw = new StreamWriter(outputms)) + { + using (var ms = new MemoryStream()) + { + using (var tw = new StreamWriter(ms)) + { + tw.Write(xml); + tw.Flush(); + ms.Position = 0; + using (var tr = new StreamReader(ms)) + { + bool IsInsideElement = false, IsInsideQuotes = false; + int ic = 0; + while ((ic = tr.Read()) != -1) + { + if (ic == (int)'<' && !IsInsideQuotes) + { + if (tr.Peek() != (int)'!') + { + IsInsideElement = true; + } + } + if (ic == (int)'>' && !IsInsideQuotes) + { + IsInsideElement = false; + } + if (ic == (int)'"') + { + IsInsideQuotes = !IsInsideQuotes; + } + if (!IsInsideElement || ic != (int)'-' || IsInsideQuotes) + { + outputtw.Write((char)ic); + } + } + + } + } + } + outputtw.Flush(); + outputms.Position = 0; + using (TextReader outputtr = new StreamReader(outputms)) + { + return outputtr.ReadToEnd(); + } + } + } + } + + + /// + /// Imports a XML node from text. + /// + /// The text. + /// The XML doc. + /// + public static XmlNode ImportXmlNodeFromText(string text, ref XmlDocument xmlDoc) + { + xmlDoc.LoadXml(text); + return xmlDoc.FirstChild; + } + + /// + /// Opens a file as a XmlDocument. + /// + /// The relative file path. ei. /config/umbraco.config + /// Returns a XmlDocument class + public static XmlDocument OpenAsXmlDocument(string filePath) + { + + var reader = new XmlTextReader(IOHelper.MapPath(filePath)) {WhitespaceHandling = WhitespaceHandling.All}; + + var xmlDoc = new XmlDocument(); + //Load the file into the XmlDocument + xmlDoc.Load(reader); + //Close off the connection to the file. + reader.Close(); + + return xmlDoc; + } + + /// + /// creates a XmlAttribute with the specified name and value + /// + /// The xmldocument. + /// The name of the attribute. + /// The value of the attribute. + /// a XmlAttribute + public static XmlAttribute AddAttribute(XmlDocument xd, string name, string value) + { + var temp = xd.CreateAttribute(name); + temp.Value = value; + return temp; + } + + /// + /// Creates a text XmlNode with the specified name and value + /// + /// The xmldocument. + /// The node name. + /// The node value. + /// a XmlNode + public static XmlNode AddTextNode(XmlDocument xd, string name, string value) + { + var temp = xd.CreateNode(XmlNodeType.Element, name, ""); + temp.AppendChild(xd.CreateTextNode(value)); + return temp; + } + + /// + /// Creates a cdata XmlNode with the specified name and value + /// + /// The xmldocument. + /// The node name. + /// The node value. + /// A XmlNode + public static XmlNode AddCDataNode(XmlDocument xd, string name, string value) + { + var temp = xd.CreateNode(XmlNodeType.Element, name, ""); + temp.AppendChild(xd.CreateCDataSection(value)); + return temp; + } + + /// + /// Gets the value of a XmlNode + /// + /// The XmlNode. + /// the value as a string + public static string GetNodeValue(XmlNode n) + { + var value = string.Empty; + if (n == null || n.FirstChild == null) + return value; + value = n.FirstChild.Value ?? n.InnerXml; + return value.Replace("", "", "]]>"); + } + + /// + /// Determines whether the specified string appears to be XML. + /// + /// The XML string. + /// + /// true if the specified string appears to be XML; otherwise, false. + /// + public static bool CouldItBeXml(string xml) + { + if (string.IsNullOrEmpty(xml)) return false; + + xml = xml.Trim(); + return xml.StartsWith("<") && xml.EndsWith(">") && xml.Contains('/'); + } + + /// + /// Splits the specified delimited string into an XML document. + /// + /// The data. + /// The separator. + /// Name of the root. + /// Name of the element. + /// Returns an System.Xml.XmlDocument representation of the delimited string data. + public static XmlDocument Split(string data, string[] separator, string rootName, string elementName) + { + return Split(new XmlDocument(), data, separator, rootName, elementName); + } + + /// + /// Splits the specified delimited string into an XML document. + /// + /// The XML document. + /// The delimited string data. + /// The separator. + /// Name of the root node. + /// Name of the element node. + /// Returns an System.Xml.XmlDocument representation of the delimited string data. + public static XmlDocument Split(XmlDocument xml, string data, string[] separator, string rootName, string elementName) + { + // load new XML document. + xml.LoadXml(string.Concat("<", rootName, "/>")); + + // get the data-value, check it isn't empty. + if (!string.IsNullOrEmpty(data)) + { + // explode the values into an array + var values = data.Split(separator, StringSplitOptions.None); + + // loop through the array items. + foreach (string value in values) + { + // add each value to the XML document. + var xn = XmlHelper.AddTextNode(xml, elementName, value); + xml.DocumentElement.AppendChild(xn); + } + } + + // return the XML node. + return xml; + } + + /// + /// Return a dictionary of attributes found for a string based tag + /// + /// + /// + public static Dictionary GetAttributesFromElement(string tag) + { + var m = + Regex.Matches(tag, "(?\\S*)=\"(?[^\"]*)\"", + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + // fix for issue 14862: return lowercase attributes for case insensitive matching + var d = m.Cast().ToDictionary(attributeSet => attributeSet.Groups["attributeName"].Value.ToString().ToLower(), attributeSet => attributeSet.Groups["attributeValue"].Value.ToString()); + return d; + } + } +} diff --git a/src/Umbraco.Tests/ApplicationContextTests.cs b/src/Umbraco.Tests/ApplicationContextTests.cs index b27b496fbb..85e59403e0 100644 --- a/src/Umbraco.Tests/ApplicationContextTests.cs +++ b/src/Umbraco.Tests/ApplicationContextTests.cs @@ -4,6 +4,7 @@ using Moq; using NUnit.Framework; using Semver; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; diff --git a/src/Umbraco.Tests/ApplicationUrlHelperTests.cs b/src/Umbraco.Tests/ApplicationUrlHelperTests.cs index 578a569104..4b222f6607 100644 --- a/src/Umbraco.Tests/ApplicationUrlHelperTests.cs +++ b/src/Umbraco.Tests/ApplicationUrlHelperTests.cs @@ -4,6 +4,7 @@ using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.ObjectResolution; diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs index 3025207a77..bdd20fbada 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Macros; namespace Umbraco.Tests.Configurations.UmbracoSettings { diff --git a/src/Umbraco.Tests/Migrations/Upgrades/BaseUpgradeTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/BaseUpgradeTest.cs index 0f11e0b4f5..5168fa220b 100644 --- a/src/Umbraco.Tests/Migrations/Upgrades/BaseUpgradeTest.cs +++ b/src/Umbraco.Tests/Migrations/Upgrades/BaseUpgradeTest.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Migrations; using Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix; using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Plugins; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; diff --git a/src/Umbraco.Tests/MockTests.cs b/src/Umbraco.Tests/MockTests.cs index 3b4f72aa4e..5033a22f15 100644 --- a/src/Umbraco.Tests/MockTests.cs +++ b/src/Umbraco.Tests/MockTests.cs @@ -16,6 +16,7 @@ using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Profiling; using Umbraco.Core.Services; using Moq; +using Umbraco.Core.Cache; using Umbraco.Tests.TestHelpers; using Umbraco.Web; using Umbraco.Web.Routing; diff --git a/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs b/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs index 8bc09292c4..d01c473fe0 100644 --- a/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs @@ -5,6 +5,7 @@ using System.Linq; using AutoMapper; using Moq; using NUnit.Framework; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; using Umbraco.Core.PropertyEditors; @@ -27,8 +28,8 @@ namespace Umbraco.Tests.Models.Mapping Logger, typeListProducerList, new ManifestBuilder( - Core.CacheHelper.CreateDisabledCacheHelper().RuntimeCache, - new ManifestParser(Logger, new DirectoryInfo(TestHelper.CurrentAssemblyDirectory), Core.CacheHelper.CreateDisabledCacheHelper().RuntimeCache))); + CacheHelper.CreateDisabledCacheHelper().RuntimeCache, + new ManifestParser(Logger, new DirectoryInfo(TestHelper.CurrentAssemblyDirectory), CacheHelper.CreateDisabledCacheHelper().RuntimeCache))); PropertyEditorResolver.Current = propertyEditorResolver; diff --git a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs index 08b8d5aa24..d9bb13ddb1 100644 --- a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs +++ b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs @@ -6,6 +6,7 @@ using Moq; using NPoco; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; diff --git a/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs b/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs index ab87151da7..8f0ca1cfb1 100644 --- a/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs +++ b/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs @@ -10,6 +10,7 @@ using NPoco; using NUnit.Framework; using Semver; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index b49fefc06c..bfb79a3c1b 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -5,6 +5,7 @@ using AutoMapper; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; diff --git a/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs index c99c3349ed..48b0def02b 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs @@ -3,6 +3,7 @@ using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence; diff --git a/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs index f6583e7c94..27d5965493 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs @@ -3,6 +3,7 @@ using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence; diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs index 9e4e4ae8a2..f9cccc1ed6 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs @@ -3,6 +3,7 @@ using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence; diff --git a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs index 721b4e3485..68853083a2 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs @@ -3,6 +3,7 @@ using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; diff --git a/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs index 14cd5d83de..a68f2fcebd 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs @@ -3,6 +3,7 @@ using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index 6293b19332..5d85e35f9c 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -4,6 +4,7 @@ using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence; diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserTypeRepositoryTest.cs index f8bad1b272..81e7177bb7 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserTypeRepositoryTest.cs @@ -2,6 +2,7 @@ using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence; diff --git a/src/Umbraco.Tests/Plugins/PluginManagerExtensions.cs b/src/Umbraco.Tests/Plugins/PluginManagerExtensions.cs index b8ff7d20c2..8eea84a16c 100644 --- a/src/Umbraco.Tests/Plugins/PluginManagerExtensions.cs +++ b/src/Umbraco.Tests/Plugins/PluginManagerExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Umbraco.Core; +using Umbraco.Core.Plugins; namespace Umbraco.Tests.Plugins { diff --git a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs index b8f5e305a6..6c61c3571f 100644 --- a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs +++ b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs @@ -18,6 +18,7 @@ using Umbraco.Core.Profiling; using Umbraco.Core.PropertyEditors; using umbraco.DataLayer; using umbraco.uicontrols; +using Umbraco.Core.Plugins; using Umbraco.Web; using Umbraco.Web.PropertyEditors; diff --git a/src/Umbraco.Tests/Plugins/TypeFinderTests.cs b/src/Umbraco.Tests/Plugins/TypeFinderTests.cs index d2161bdfe5..fc1fc71ada 100644 --- a/src/Umbraco.Tests/Plugins/TypeFinderTests.cs +++ b/src/Umbraco.Tests/Plugins/TypeFinderTests.cs @@ -19,6 +19,7 @@ using Umbraco.Core; using Umbraco.Core.IO; using umbraco.DataLayer; using umbraco.uicontrols; +using Umbraco.Core.Plugins; using Umbraco.Web; using Umbraco.Web.Models.Trees; using Umbraco.Web.Trees; diff --git a/src/Umbraco.Tests/Plugins/TypeHelperTests.cs b/src/Umbraco.Tests/Plugins/TypeHelperTests.cs index be7ed38915..f4842673e7 100644 --- a/src/Umbraco.Tests/Plugins/TypeHelperTests.cs +++ b/src/Umbraco.Tests/Plugins/TypeHelperTests.cs @@ -10,6 +10,7 @@ using System.Reflection; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Plugins; using Umbraco.Tests.DynamicsAndReflection; using Umbraco.Web.Cache; using Umbraco.Web.Models; diff --git a/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs index 6d55801631..b6c0110019 100644 --- a/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs @@ -4,6 +4,7 @@ using System.Globalization; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Profiling; diff --git a/src/Umbraco.Tests/PublishedContent/DynamicDocumentTestsBase.cs b/src/Umbraco.Tests/PublishedContent/DynamicDocumentTestsBase.cs index 55489b6074..4e9a2590b0 100644 --- a/src/Umbraco.Tests/PublishedContent/DynamicDocumentTestsBase.cs +++ b/src/Umbraco.Tests/PublishedContent/DynamicDocumentTestsBase.cs @@ -9,6 +9,7 @@ using Umbraco.Core; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Dynamics; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Plugins; using Umbraco.Core.PropertyEditors; using Umbraco.Tests.TestHelpers; diff --git a/src/Umbraco.Tests/PublishedContent/DynamicXmlConverterTests.cs b/src/Umbraco.Tests/PublishedContent/DynamicXmlConverterTests.cs index 473ece3e79..5075f347e3 100644 --- a/src/Umbraco.Tests/PublishedContent/DynamicXmlConverterTests.cs +++ b/src/Umbraco.Tests/PublishedContent/DynamicXmlConverterTests.cs @@ -3,6 +3,7 @@ using System.Xml.Linq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Dynamics; +using Umbraco.Core.Xml; namespace Umbraco.Tests.PublishedContent { diff --git a/src/Umbraco.Tests/PublishedContent/DynamicXmlTests.cs b/src/Umbraco.Tests/PublishedContent/DynamicXmlTests.cs index 7288148333..0d83fdbf0c 100644 --- a/src/Umbraco.Tests/PublishedContent/DynamicXmlTests.cs +++ b/src/Umbraco.Tests/PublishedContent/DynamicXmlTests.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Dynamics; using System.Linq; +using Umbraco.Core.Xml; namespace Umbraco.Tests.PublishedContent { diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs index 4ee572ae98..facd380fc8 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs @@ -11,6 +11,7 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Web; using Umbraco.Tests.TestHelpers; using umbraco.BusinessLogic; +using Umbraco.Core.Plugins; using Umbraco.Web.PublishedCache.XmlPublishedCache; using Umbraco.Web.Security; diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index 3967afce79..0608039437 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Plugins; using Umbraco.Core.PropertyEditors; using Umbraco.Tests.TestHelpers; using Umbraco.Web; diff --git a/src/Umbraco.Tests/Resolvers/ResolverBaseTest.cs b/src/Umbraco.Tests/Resolvers/ResolverBaseTest.cs index 6aac5bc0cd..cdf24667e8 100644 --- a/src/Umbraco.Tests/Resolvers/ResolverBaseTest.cs +++ b/src/Umbraco.Tests/Resolvers/ResolverBaseTest.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Logging; +using Umbraco.Core.Plugins; using Umbraco.Core.Profiling; using Umbraco.Core._Legacy.PackageActions; diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index 4b750b6894..4b23b99e68 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -13,6 +13,7 @@ using Umbraco.Web.Mvc; using Umbraco.Web.Routing; using Umbraco.Web.WebApi; using umbraco.BusinessLogic; +using Umbraco.Core.Plugins; using Umbraco.Core.Profiling; using Umbraco.Core.Strings; diff --git a/src/Umbraco.Tests/Security/BackOfficeCookieManagerTests.cs b/src/Umbraco.Tests/Security/BackOfficeCookieManagerTests.cs index 9536813b1c..dba759cf49 100644 --- a/src/Umbraco.Tests/Security/BackOfficeCookieManagerTests.cs +++ b/src/Umbraco.Tests/Security/BackOfficeCookieManagerTests.cs @@ -6,6 +6,7 @@ using Microsoft.Owin; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; diff --git a/src/Umbraco.Tests/Services/MacroServiceTests.cs b/src/Umbraco.Tests/Services/MacroServiceTests.cs index b1a0c763da..517d288ef9 100644 --- a/src/Umbraco.Tests/Services/MacroServiceTests.cs +++ b/src/Umbraco.Tests/Services/MacroServiceTests.cs @@ -2,6 +2,7 @@ using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; diff --git a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs index 0534599173..0f70b769c4 100644 --- a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs +++ b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs @@ -9,6 +9,7 @@ using System.Threading; using NPoco; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence; diff --git a/src/Umbraco.Tests/Templates/TemplateRepositoryTests.cs b/src/Umbraco.Tests/Templates/TemplateRepositoryTests.cs index be0ee5d824..5df709bc59 100644 --- a/src/Umbraco.Tests/Templates/TemplateRepositoryTests.cs +++ b/src/Umbraco.Tests/Templates/TemplateRepositoryTests.cs @@ -8,6 +8,7 @@ using System.Text; using System.Threading.Tasks; using NPoco; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index dc49b6c20e..23d5ab29c9 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -26,6 +26,7 @@ using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.XmlPublishedCache; using Umbraco.Web.Security; using Umbraco.Core.Events; +using Umbraco.Core.Plugins; using File = System.IO.File; namespace Umbraco.Tests.TestHelpers diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index d36042e180..f85572aabf 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -23,6 +23,7 @@ using Umbraco.Web; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Events; +using Umbraco.Core.Plugins; using Umbraco.Web.DependencyInjection; namespace Umbraco.Tests.TestHelpers diff --git a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs index 80b39e51eb..672c682908 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Profiling; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Persistence; +using Umbraco.Core.Plugins; namespace Umbraco.Tests.TestHelpers { diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs index bcd5c3dd8f..1b22be3d3c 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs @@ -8,6 +8,7 @@ using System.Web.SessionState; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Core.Plugins; using Umbraco.Web; namespace Umbraco.Tests.TestHelpers.Stubs diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 1c0f7c1b82..a45a07db9e 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using NPoco; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; diff --git a/src/Umbraco.Tests/TestHelpers/TestProfiler.cs b/src/Umbraco.Tests/TestHelpers/TestProfiler.cs index a4a6a7c76e..46b798613d 100644 --- a/src/Umbraco.Tests/TestHelpers/TestProfiler.cs +++ b/src/Umbraco.Tests/TestHelpers/TestProfiler.cs @@ -1,6 +1,7 @@ using System; using StackExchange.Profiling; using StackExchange.Profiling.SqlFormatters; +using Umbraco.Core.Logging; using Umbraco.Core.Profiling; namespace Umbraco.Tests.TestHelpers diff --git a/src/Umbraco.Tests/UI/LegacyDialogTests.cs b/src/Umbraco.Tests/UI/LegacyDialogTests.cs index e85683dcbb..8cd01c64a7 100644 --- a/src/Umbraco.Tests/UI/LegacyDialogTests.cs +++ b/src/Umbraco.Tests/UI/LegacyDialogTests.cs @@ -8,6 +8,7 @@ using Umbraco.Web.UI; using umbraco; using umbraco.BusinessLogic; using umbraco.cms.presentation.user; +using Umbraco.Core.Plugins; using Umbraco.Web._Legacy.UI; namespace Umbraco.Tests.UI diff --git a/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs b/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs index f99272ad85..d244ea476f 100644 --- a/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs @@ -8,6 +8,7 @@ using System.Web.Routing; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Profiling; diff --git a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs index 667f214a37..d27ff2118a 100644 --- a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs @@ -7,6 +7,7 @@ using System.Web.Security; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Dictionary; using Umbraco.Core.Logging; diff --git a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs index f853a90c89..5d9a9ed69c 100644 --- a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs @@ -7,6 +7,7 @@ using Moq; using NUnit.Framework; using umbraco.BusinessLogic; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Events; using Umbraco.Core.Logging; diff --git a/src/Umbraco.Tests/Web/WebExtensionMethodTests.cs b/src/Umbraco.Tests/Web/WebExtensionMethodTests.cs index 3978d53977..38b6e3a13a 100644 --- a/src/Umbraco.Tests/Web/WebExtensionMethodTests.cs +++ b/src/Umbraco.Tests/Web/WebExtensionMethodTests.cs @@ -10,6 +10,7 @@ using System.Web.Routing; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Profiling; diff --git a/src/Umbraco.Tests/XmlHelperTests.cs b/src/Umbraco.Tests/XmlHelperTests.cs index 9cb3869ffc..00a86db800 100644 --- a/src/Umbraco.Tests/XmlHelperTests.cs +++ b/src/Umbraco.Tests/XmlHelperTests.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Configuration; using umbraco; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Xml; using Umbraco.Tests.Models; namespace Umbraco.Tests diff --git a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs index 6c8a1ca611..9953d8709b 100644 --- a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs +++ b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Events; using Umbraco.Core.Models; using umbraco; using umbraco.cms.businesslogic.web; +using Umbraco.Core.Cache; using Umbraco.Core.Persistence.Repositories; namespace Umbraco.Web.Cache diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 59ccac2de8..203cee7dfc 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -27,6 +27,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Manifest; using Umbraco.Core.Models; using Umbraco.Core.Models.Identity; +using Umbraco.Core.Plugins; using Umbraco.Core.Security; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index c161e32ec6..d913a405c4 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -11,6 +11,7 @@ using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Dynamics; using Umbraco.Core.IO; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Profiling; using Umbraco.Web.Models; diff --git a/src/Umbraco.Web/Install/InstallStatusTracker.cs b/src/Umbraco.Web/Install/InstallStatusTracker.cs index 971c64d2ed..b8bdec9c2c 100644 --- a/src/Umbraco.Web/Install/InstallStatusTracker.cs +++ b/src/Umbraco.Web/Install/InstallStatusTracker.cs @@ -8,6 +8,7 @@ using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using Umbraco.Core; +using Umbraco.Core.Collections; using Umbraco.Core.IO; using Umbraco.Web.Install.Models; diff --git a/src/Umbraco.Web/LightInjectExtensions.cs b/src/Umbraco.Web/LightInjectExtensions.cs index 50b42adec9..23fa1dbc81 100644 --- a/src/Umbraco.Web/LightInjectExtensions.cs +++ b/src/Umbraco.Web/LightInjectExtensions.cs @@ -3,6 +3,7 @@ using System.Web.Http.Controllers; using System.Web.Mvc; using LightInject; using Umbraco.Core; +using Umbraco.Core.Plugins; namespace Umbraco.Web { diff --git a/src/Umbraco.Web/Mvc/DefaultRenderMvcControllerResolver.cs b/src/Umbraco.Web/Mvc/DefaultRenderMvcControllerResolver.cs index eb55edc3e4..e53b20440f 100644 --- a/src/Umbraco.Web/Mvc/DefaultRenderMvcControllerResolver.cs +++ b/src/Umbraco.Web/Mvc/DefaultRenderMvcControllerResolver.cs @@ -6,6 +6,7 @@ using System.Text; using System.Web.Mvc; using Umbraco.Core; using Umbraco.Core.ObjectResolution; +using Umbraco.Core.Plugins; namespace Umbraco.Web.Mvc { diff --git a/src/Umbraco.Web/Mvc/PluginControllerArea.cs b/src/Umbraco.Web/Mvc/PluginControllerArea.cs index c6b3e7fe1e..488c4000b8 100644 --- a/src/Umbraco.Web/Mvc/PluginControllerArea.cs +++ b/src/Umbraco.Web/Mvc/PluginControllerArea.cs @@ -5,6 +5,7 @@ using System.Web.Mvc; using System.Web.Routing; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.Plugins; using Umbraco.Web.WebApi; namespace Umbraco.Web.Mvc diff --git a/src/Umbraco.Web/Mvc/ProfilingView.cs b/src/Umbraco.Web/Mvc/ProfilingView.cs index 59d85ba62c..d0913a5259 100644 --- a/src/Umbraco.Web/Mvc/ProfilingView.cs +++ b/src/Umbraco.Web/Mvc/ProfilingView.cs @@ -1,5 +1,6 @@ using System.IO; using System.Web.Mvc; +using Umbraco.Core.Logging; namespace Umbraco.Core.Profiling { diff --git a/src/Umbraco.Web/Mvc/ProfilingViewEngine.cs b/src/Umbraco.Web/Mvc/ProfilingViewEngine.cs index 7c5b7a2ce3..9b1f4e7528 100644 --- a/src/Umbraco.Web/Mvc/ProfilingViewEngine.cs +++ b/src/Umbraco.Web/Mvc/ProfilingViewEngine.cs @@ -1,4 +1,5 @@ using System.Web.Mvc; +using Umbraco.Core.Logging; namespace Umbraco.Core.Profiling { diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index d7ce36b4a4..a3b7d9def8 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -14,6 +14,7 @@ using Umbraco.Web.Models; using Umbraco.Web.Routing; using umbraco.cms.businesslogic.template; using System.Collections.Generic; +using Umbraco.Core.Plugins; namespace Umbraco.Web.Mvc { diff --git a/src/Umbraco.Web/Mvc/UrlHelperExtensions.cs b/src/Umbraco.Web/Mvc/UrlHelperExtensions.cs index b1cf5be546..3f2d5c7a3b 100644 --- a/src/Umbraco.Web/Mvc/UrlHelperExtensions.cs +++ b/src/Umbraco.Web/Mvc/UrlHelperExtensions.cs @@ -3,6 +3,7 @@ using System.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.IO; +using Umbraco.Core.Xml; namespace Umbraco.Web.Mvc { diff --git a/src/Umbraco.Web/PluginManagerExtensions.cs b/src/Umbraco.Web/PluginManagerExtensions.cs index 1e54c0f386..66af41c5ac 100644 --- a/src/Umbraco.Web/PluginManagerExtensions.cs +++ b/src/Umbraco.Web/PluginManagerExtensions.cs @@ -9,6 +9,7 @@ using Umbraco.Web.Trees; using Umbraco.Web.WebApi; using umbraco; using umbraco.cms.presentation.Trees; +using Umbraco.Core.Plugins; using Umbraco.Web.Models.Trees; using Umbraco.Web._Legacy.Actions; diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/ImageCropDataSetConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/ImageCropDataSetConverter.cs index 82278676cd..6e9049ef77 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/ImageCropDataSetConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/ImageCropDataSetConverter.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using Newtonsoft.Json.Linq; using Umbraco.Core; +using Umbraco.Core.Plugins; using Umbraco.Web.Models; namespace Umbraco.Web.PropertyEditors.ValueConverters diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs index d7caf7a83f..6d8e9b0229 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs @@ -4,6 +4,7 @@ using System.Xml.Serialization; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Xml; using Umbraco.Web.Models; namespace Umbraco.Web.PublishedCache.XmlPublishedCache diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 16f42b8197..97006bbeae 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -5,6 +5,7 @@ using System.Xml.XPath; using Umbraco.Core; using Umbraco.Core.Dynamics; using Umbraco.Core.Models; +using Umbraco.Core.Plugins; using Umbraco.Core.Xml; using Umbraco.Web.Models; using Umbraco.Web.PublishedCache; diff --git a/src/Umbraco.Web/Security/Identity/AuthenticationOptionsExtensions.cs b/src/Umbraco.Web/Security/Identity/AuthenticationOptionsExtensions.cs index 7074a6db9f..fa31e5f888 100644 --- a/src/Umbraco.Web/Security/Identity/AuthenticationOptionsExtensions.cs +++ b/src/Umbraco.Web/Security/Identity/AuthenticationOptionsExtensions.cs @@ -3,6 +3,7 @@ using Microsoft.Owin; using Microsoft.Owin.Security; using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Core.Plugins; namespace Umbraco.Web.Security.Identity { diff --git a/src/Umbraco.Web/Services/ApplicationTreeService.cs b/src/Umbraco.Web/Services/ApplicationTreeService.cs index 9bfcdc2cac..a101c367dc 100644 --- a/src/Umbraco.Web/Services/ApplicationTreeService.cs +++ b/src/Umbraco.Web/Services/ApplicationTreeService.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Plugins; using Umbraco.Core.Services; using Umbraco.Web.Trees; diff --git a/src/Umbraco.Web/Services/SectionService.cs b/src/Umbraco.Web/Services/SectionService.cs index 39f7aa9d5c..b0f82a17cc 100644 --- a/src/Umbraco.Web/Services/SectionService.cs +++ b/src/Umbraco.Web/Services/SectionService.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Plugins; using Umbraco.Core.Services; using Umbraco.Web.Models.Trees; using File = System.IO.File; diff --git a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs index 4ab3c12a51..070ee2c8dc 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs @@ -18,6 +18,7 @@ using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using umbraco.BusinessLogic; using umbraco.cms.presentation.Trees; +using Umbraco.Core.Plugins; using ApplicationTree = Umbraco.Core.Models.ApplicationTree; using IAuthorizationFilter = System.Web.Http.Filters.IAuthorizationFilter; using UrlHelper = System.Web.Http.Routing.UrlHelper; diff --git a/src/Umbraco.Web/Trees/LegacyBaseTreeAttribute.cs b/src/Umbraco.Web/Trees/LegacyBaseTreeAttribute.cs index fdac14dd1a..354524dba4 100644 --- a/src/Umbraco.Web/Trees/LegacyBaseTreeAttribute.cs +++ b/src/Umbraco.Web/Trees/LegacyBaseTreeAttribute.cs @@ -1,6 +1,7 @@ using System; using Umbraco.Core; using umbraco.cms.presentation.Trees; +using Umbraco.Core.Plugins; namespace Umbraco.Web.Trees { diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index eb7532bb0e..04346763ce 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -20,6 +20,7 @@ using Umbraco.Web.Editors; using Umbraco.Web.Routing; using Umbraco.Web.Security; using umbraco; +using Umbraco.Core.Collections; using Umbraco.Core.Sync; using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; using ObjectExtensions = Umbraco.Core.ObjectExtensions; diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index 36170627d9..bcef8fd6bf 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -7,6 +7,7 @@ using System.Web.Http.Filters; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Plugins; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.WebApi.Filters diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 1486d77774..476248e195 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -46,7 +46,7 @@ using Umbraco.Web._Legacy.Actions; using Action = System.Action; using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; using ProfilingViewEngine = Umbraco.Core.Profiling.ProfilingViewEngine; -using TypeHelper = Umbraco.Core.TypeHelper; +using TypeHelper = Umbraco.Core.Plugins.TypeHelper; namespace Umbraco.Web diff --git a/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs b/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs index 31ef4d23f2..f3ae8228a4 100644 --- a/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs +++ b/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs @@ -11,6 +11,7 @@ using Examine.Providers; using Lucene.Net.Search; using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Core.Plugins; using Umbraco.Web.Search; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; diff --git a/src/Umbraco.Web/_Legacy/Actions/Action.cs b/src/Umbraco.Web/_Legacy/Actions/Action.cs index 4b364d4cc1..611e45bafd 100644 --- a/src/Umbraco.Web/_Legacy/Actions/Action.cs +++ b/src/Umbraco.Web/_Legacy/Actions/Action.cs @@ -5,8 +5,9 @@ using System.Text.RegularExpressions; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Plugins; using Umbraco.Core.Services; -using TypeFinder = Umbraco.Core.TypeFinder; +using TypeFinder = Umbraco.Core.Plugins.TypeFinder; namespace Umbraco.Web._Legacy.Actions { diff --git a/src/Umbraco.Web/_Legacy/PackageActions/PackageHelper.cs b/src/Umbraco.Web/_Legacy/PackageActions/PackageHelper.cs index c2afbda5a7..fdba5c1526 100644 --- a/src/Umbraco.Web/_Legacy/PackageActions/PackageHelper.cs +++ b/src/Umbraco.Web/_Legacy/PackageActions/PackageHelper.cs @@ -1,6 +1,7 @@ using System; using System.Xml; using Umbraco.Core; +using Umbraco.Core.Xml; namespace Umbraco.Web._Legacy.PackageActions { diff --git a/src/Umbraco.Web/_Legacy/PackageActions/addDashboardSection.cs b/src/Umbraco.Web/_Legacy/PackageActions/addDashboardSection.cs index ffe32c376e..1bdcd29bca 100644 --- a/src/Umbraco.Web/_Legacy/PackageActions/addDashboardSection.cs +++ b/src/Umbraco.Web/_Legacy/PackageActions/addDashboardSection.cs @@ -2,6 +2,7 @@ using System; using System.Xml; using Umbraco.Core; using Umbraco.Core.IO; +using Umbraco.Core.Xml; using Umbraco.Core._Legacy.PackageActions; namespace Umbraco.Web._Legacy.PackageActions diff --git a/src/Umbraco.Web/_Legacy/PackageActions/addProxyFeedHost.cs b/src/Umbraco.Web/_Legacy/PackageActions/addProxyFeedHost.cs index b6e2e0d9d9..fabf637166 100644 --- a/src/Umbraco.Web/_Legacy/PackageActions/addProxyFeedHost.cs +++ b/src/Umbraco.Web/_Legacy/PackageActions/addProxyFeedHost.cs @@ -1,6 +1,7 @@ using System.Xml; using Umbraco.Core; using Umbraco.Core.IO; +using Umbraco.Core.Xml; using Umbraco.Core._Legacy.PackageActions; namespace Umbraco.Web._Legacy.PackageActions diff --git a/src/Umbraco.Web/_Legacy/PackageActions/addStringToHtmlElement.cs b/src/Umbraco.Web/_Legacy/PackageActions/addStringToHtmlElement.cs index 2811f084d9..788cb0bd83 100644 --- a/src/Umbraco.Web/_Legacy/PackageActions/addStringToHtmlElement.cs +++ b/src/Umbraco.Web/_Legacy/PackageActions/addStringToHtmlElement.cs @@ -3,6 +3,7 @@ using System.Xml; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Xml; using Umbraco.Core._Legacy.PackageActions; namespace Umbraco.Web._Legacy.PackageActions diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs index 540334664e..99120deee0 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -20,6 +20,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Profiling; using Umbraco.Core.Services; using Umbraco.Core.Strings; +using Umbraco.Core.Xml; using Umbraco.Web; using Umbraco.Web.PublishedCache.XmlPublishedCache; using Umbraco.Web.Scheduling; diff --git a/src/Umbraco.Web/umbraco.presentation/library.cs b/src/Umbraco.Web/umbraco.presentation/library.cs index 992f546517..4559fc370f 100644 --- a/src/Umbraco.Web/umbraco.presentation/library.cs +++ b/src/Umbraco.Web/umbraco.presentation/library.cs @@ -28,6 +28,7 @@ using umbraco.cms.businesslogic; using umbraco.cms.businesslogic.web; using umbraco.DataLayer; using Umbraco.Core.IO; +using Umbraco.Core.Xml; using Language = umbraco.cms.businesslogic.language.Language; using Media = umbraco.cms.businesslogic.media.Media; using Member = umbraco.cms.businesslogic.member.Member; diff --git a/src/Umbraco.Web/umbraco.presentation/macro.cs b/src/Umbraco.Web/umbraco.presentation/macro.cs index 3d3f505844..93418bad18 100644 --- a/src/Umbraco.Web/umbraco.presentation/macro.cs +++ b/src/Umbraco.Web/umbraco.presentation/macro.cs @@ -41,6 +41,7 @@ using Content = umbraco.cms.businesslogic.Content; using Macro = umbraco.cms.businesslogic.macro.Macro; using MacroErrorEventArgs = Umbraco.Core.Events.MacroErrorEventArgs; using System.Linq; +using Umbraco.Core.Xml; using File = System.IO.File; using Member = umbraco.cms.businesslogic.member.Member; diff --git a/src/Umbraco.Web/umbraco.presentation/template.cs b/src/Umbraco.Web/umbraco.presentation/template.cs index 24f7de4aac..436b7ad96a 100644 --- a/src/Umbraco.Web/umbraco.presentation/template.cs +++ b/src/Umbraco.Web/umbraco.presentation/template.cs @@ -18,6 +18,7 @@ using umbraco.DataLayer; using umbraco.BusinessLogic; using Umbraco.Core.IO; using System.Web; +using Umbraco.Core.Xml; namespace umbraco { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs index dadd172903..0bbd6fc5ec 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs @@ -3,6 +3,7 @@ using System.Linq; using Umbraco.Core; using Umbraco.Web; using umbraco.BusinessLogic; +using Umbraco.Core.Plugins; namespace umbraco.cms.presentation.Trees { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dashboard/FeedProxy.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dashboard/FeedProxy.aspx.cs index eca79aa26c..b6f1f1a0b6 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dashboard/FeedProxy.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dashboard/FeedProxy.aspx.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Net; using System.Net.Mime; using Umbraco.Core.IO; +using Umbraco.Core.Xml; namespace dashboardUtilities { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/search.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/search.aspx.cs index 53410945e1..ab04f98224 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/search.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/search.aspx.cs @@ -11,6 +11,7 @@ using Examine; using Examine.LuceneEngine.SearchCriteria; using System.Linq; using Umbraco.Core; +using Umbraco.Core.Xml; namespace umbraco.presentation.dialogs diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/translation/xml.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/translation/xml.aspx.cs index 705feea628..441d53f169 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/translation/xml.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/translation/xml.aspx.cs @@ -17,6 +17,7 @@ using umbraco.BusinessLogic; using umbraco.cms.businesslogic.task; using umbraco.cms.businesslogic.web; using Umbraco.Core.IO; +using Umbraco.Core.Xml; namespace umbraco.presentation.translation { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs index 254c9bbcbd..634a0edce5 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Sync; using Umbraco.Core.Security; +using Umbraco.Core.Xml; namespace umbraco.presentation.webservices { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/Developer.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/Developer.asmx.cs index 978343b7bb..127717b6ea 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/Developer.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/Developer.asmx.cs @@ -5,6 +5,7 @@ using Umbraco.Core; using Umbraco.Web.WebServices; using umbraco.BusinessLogic; using umbraco.presentation.webservices; +using Umbraco.Core.Xml; namespace umbraco.webservices { diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index 86f8487a56..9982420e2d 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -14,6 +14,7 @@ using Examine.LuceneEngine; using Examine.LuceneEngine.Config; using UmbracoExamine.Config; using Lucene.Net.Analysis; +using Umbraco.Core.Xml; using IContentService = Umbraco.Core.Services.IContentService; using IMediaService = Umbraco.Core.Services.IMediaService; diff --git a/src/umbraco.cms/businesslogic/Dictionary.cs b/src/umbraco.cms/businesslogic/Dictionary.cs index 6bf76ccb12..f043ddca80 100644 --- a/src/umbraco.cms/businesslogic/Dictionary.cs +++ b/src/umbraco.cms/businesslogic/Dictionary.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Services; using umbraco.DataLayer; using umbraco.BusinessLogic; using System.Runtime.CompilerServices; +using Umbraco.Core.Xml; using Language = umbraco.cms.businesslogic.language.Language; namespace umbraco.cms.businesslogic diff --git a/src/umbraco.cms/businesslogic/Packager/Installer.cs b/src/umbraco.cms/businesslogic/Packager/Installer.cs index 35b29c6678..489306c394 100644 --- a/src/umbraco.cms/businesslogic/Packager/Installer.cs +++ b/src/umbraco.cms/businesslogic/Packager/Installer.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Logging; using umbraco.cms.businesslogic.web; using System.Diagnostics; using Umbraco.Core.Models; +using Umbraco.Core.Xml; using File = System.IO.File; using Macro = umbraco.cms.businesslogic.macro.Macro; using Template = umbraco.cms.businesslogic.template.Template; diff --git a/src/umbraco.cms/businesslogic/Packager/data.cs b/src/umbraco.cms/businesslogic/Packager/data.cs index eb2113ef23..a0bb3072f6 100644 --- a/src/umbraco.cms/businesslogic/Packager/data.cs +++ b/src/umbraco.cms/businesslogic/Packager/data.cs @@ -6,6 +6,7 @@ using System.IO; using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Xml; namespace umbraco.cms.businesslogic.packager { diff --git a/src/umbraco.cms/businesslogic/macro/Macro.cs b/src/umbraco.cms/businesslogic/macro/Macro.cs index ec81b19fc3..950f28812a 100644 --- a/src/umbraco.cms/businesslogic/macro/Macro.cs +++ b/src/umbraco.cms/businesslogic/macro/Macro.cs @@ -15,6 +15,7 @@ using Umbraco.Core.Services; using umbraco.DataLayer; using umbraco.BusinessLogic; using System.Linq; +using Umbraco.Core.Xml; namespace umbraco.cms.businesslogic.macro { diff --git a/src/umbraco.cms/businesslogic/macro/MacroProperty.cs b/src/umbraco.cms/businesslogic/macro/MacroProperty.cs index c5e19b1702..53cc1d0120 100644 --- a/src/umbraco.cms/businesslogic/macro/MacroProperty.cs +++ b/src/umbraco.cms/businesslogic/macro/MacroProperty.cs @@ -7,6 +7,7 @@ using Umbraco.Core.PropertyEditors; using umbraco.DataLayer; using umbraco.BusinessLogic; using System.Collections.Generic; +using Umbraco.Core.Xml; namespace umbraco.cms.businesslogic.macro diff --git a/src/umbraco.cms/businesslogic/member/Member.cs b/src/umbraco.cms/businesslogic/member/Member.cs index 30bcd2e252..5ce35f0050 100644 --- a/src/umbraco.cms/businesslogic/member/Member.cs +++ b/src/umbraco.cms/businesslogic/member/Member.cs @@ -18,6 +18,7 @@ using System.Security.Cryptography; using System.Linq; using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; +using Umbraco.Core.Xml; namespace umbraco.cms.businesslogic.member { diff --git a/src/umbraco.cms/businesslogic/template/Template.cs b/src/umbraco.cms/businesslogic/template/Template.cs index e9aeedd2f4..e45fdec3cb 100644 --- a/src/umbraco.cms/businesslogic/template/Template.cs +++ b/src/umbraco.cms/businesslogic/template/Template.cs @@ -11,6 +11,7 @@ using System.Text.RegularExpressions; using System.Collections.Generic; using umbraco.cms.businesslogic.web; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Xml; namespace umbraco.cms.businesslogic.template { diff --git a/src/umbraco.cms/businesslogic/web/StyleSheet.cs b/src/umbraco.cms/businesslogic/web/StyleSheet.cs index aa66d8bcc6..94b079d602 100644 --- a/src/umbraco.cms/businesslogic/web/StyleSheet.cs +++ b/src/umbraco.cms/businesslogic/web/StyleSheet.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Core; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Xml; namespace umbraco.cms.businesslogic.web { From 6975642a98987ddb1ef160ed4818c910595341ec Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 May 2016 15:45:46 +0200 Subject: [PATCH 10/10] puts the scheduled publish in a uow + lock --- src/Umbraco.Core/Services/ContentService.cs | 53 +++++++++++---------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index a17e40420b..a078564636 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1224,37 +1224,42 @@ namespace Umbraco.Core.Services /// public IEnumerable> PerformScheduledPublish() { - //TODO: Do I need to move all of this logic to the repo? Or wrap this all in a unit of work? + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(Constants.Locks.ContentTree); - foreach (var d in GetContentForRelease()) - { - d.ReleaseDate = null; - var result = SaveAndPublishWithStatus(d, (int)d.GetWriterProfile(_userService).Id); - if (result.Success == false) + foreach (var d in GetContentForRelease()) { - if (result.Exception != null) + d.ReleaseDate = null; + var result = SaveAndPublishWithStatus(d, (int)d.GetWriterProfile(_userService).Id); + if (result.Success == false) { - Logger.Error("Could not published the document (" + d.Id + ") based on it's scheduled release, status result: " + result.Result.StatusType, result.Exception); + if (result.Exception != null) + { + Logger.Error("Could not published the document (" + d.Id + ") based on it's scheduled release, status result: " + result.Result.StatusType, result.Exception); + } + else + { + Logger.Warn("Could not published the document (" + d.Id + ") based on it's scheduled release. Status result: " + result.Result.StatusType); + } } - else + yield return result; + } + foreach (var d in GetContentForExpiration()) + { + try { - Logger.Warn("Could not published the document (" + d.Id + ") based on it's scheduled release. Status result: " + result.Result.StatusType); + d.ExpireDate = null; + UnPublish(d, (int)d.GetWriterProfile(_userService).Id); + } + catch (Exception ee) + { + Logger.Error($"Error unpublishing node {d.Id}", ee); + throw; } } - yield return result; - } - foreach (var d in GetContentForExpiration()) - { - try - { - d.ExpireDate = null; - UnPublish(d, (int)d.GetWriterProfile(_userService).Id); - } - catch (Exception ee) - { - Logger.Error($"Error unpublishing node {d.Id}", ee); - throw; - } + + uow.Complete(); } }